AndroidにおけるActivity Manager Service (AMS) の深層:仕組みからライフサイクル制御まで

Activity Manager Service (AMS) の起動と役割

Androidシステムにおいて、AMS(Activity Manager Service)はシステム全体の司令塔として機能し、Activity、Service、放送(Broadcast)、コンテンツプロバイダーといった主要コンポーネントの管理を担っています。

1. AMSの起動シーケンス

システムが起動すると、まずZygoteプロセスが立ち上がります。ZygoteはAndroidにおける「プロセスの雛形」であり、新しいアプリケーションが起動される際、Zygoteは自身のプロセスをフォークして新しいアプリ用の実行環境を生成します。

  1. プロセスの生成: システムサーバーがアプリの起動を検知すると、Zygoteに対して新しいプロセスを作成するよう要求します。
  2. AMSの初期化: 起動したプロセス内でAMSのインスタンスが作成され、システム内のActivityの状態、タスクスタック、サービス接続などを管理する準備を整えます。
  3. Activityの実行: ランチャーからアプリアイコンがタップされると、AMSは対象のActivityが存在するプロセスが既に実行中か確認します。存在しない場合はZygoteを介してプロセスを生成し、その上でActivityオブジェクトをインスタンス化します。

2. AMSとの通信メカニズム

Activityの起動は通常、ActivityManagerNative.getDefault()(または新しいOSバージョンではActivityManager.getService())を通じて行われます。これはBinderを利用したIPC(プロセス間通信)であり、アプリ側の要求をシステムサーバー側のAMSに転送します。AMSはリクエストを受け取ると、Activityの情報を保持するActivityRecordを作成し、適切なTaskRecord(タスクスタック)に配置します。

AMSを構成する重要データ構造

AMSは内部的に以下のデータ構造を用いてアプリの状態を追跡しています。

  • ProcessRecord: アプリケーションプロセスそのものを表す単位です。UID、パッケージ名、プロセス名、メモリ優先度(ADJ)などが含まれます。
  • ActivityRecord: 個別のActivityインスタンスに対応するデータです。Activityの状態(作成、停止など)や所属するスタック情報を保持します。
  • TaskRecord: ユーザーが「一つのアプリ」として認識するタスク(スタック)を管理します。複数のActivityRecordを順序立てて保持します。
  • IntentRecord: 起動リクエストの内容(Intent)を一時的に保持し、どのActivityを起動すべきかのマッチングに使用されます。

起動モード(LaunchMode)の活用

Activityの起動挙動は、AndroidManifestで定義するlaunchModeによって制御されます。

  • standard: デフォルトの動作。起動のたびに新しいインスタンスを作成します。
  • singleTop: 起動対象のActivityが既にスタックの最上位にある場合、再利用します(onNewIntentが呼ばれます)。
  • singleTask: タスク内にインスタンスが一つしか存在しないことを保証します。既に存在する場合は、その上のActivityをすべて破棄して最上位に移動させます。
  • singleInstance: 独自のタスクにそのActivity一つだけが存在する特殊なモードです。

以下は、Activity内でデータを安全に受け渡すためのファクトリメソッドの実装例です。

public class ContentDetailActivity extends Activity {
    private static final String EXTRA_CONTENT_ID = "extra_content_id";
    private int contentId;

    public static Intent createIntent(Context context, int id) {
        Intent intent = new Intent(context, ContentDetailActivity.class);
        intent.putExtra(EXTRA_CONTENT_ID, id);
        return intent;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getIntent() != null) {
            contentId = getIntent().getIntExtra(EXTRA_CONTENT_ID, -1);
        }
    }
}

TaskAffinityとインテントフラグ

TaskAffinityは、Activityがどのタスクに所属すべきかを示す属性です。通常はパッケージ名と同じですが、これを変更することで特定のActivityを別タスクとして分離できます。

動的にタスクの挙動を制御するには、Intentのフラグを使用します。

Intent launcher = new Intent(this, SubActivity.class);
launcher.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);

if (isExternalMode) {
    // 別のタスク属性を模倣するように動的に設定
    launcher.setPackage("com.example.custom.task");
}
startActivity(launcher);

Activityのライフサイクル遷移

AMSはActivityの状態を以下の4つのフェーズで管理します。

  1. Running: ユーザーと対話中の最前面の状態。
  2. Paused: フォーカスは失っているが、画面上に一部見えている状態。
  3. Stopped: 完全に隠れているが、メモリ上には維持されている状態。
  4. Destroyed: メモリから解放され、破棄された状態。

標準的な遷移フローは以下の通りです: onCreate()onStart()onResume() (実行中) → onPause()onStop()onDestroy()

動的解析とHook:未登録Activityの起動

通常、ActivityはAndroidManifestに登録されている必要がありますが、反射(Reflection)とAMSのプロキシを利用して、システム側のチェックを回避する手法(Hook)が存在します。これは主にプラグインフレームワークなどで利用されます。

1. ProxyActivityの準備: マニフェストに登録済みのダミーActivityを用意します。

2. AMSのインターセプト: ActivityThread内のmInstrumentationをリフレクションで取得し、execStartActivityメソッドなどをフックします。

try {
    Class threadClazz = Class.forName("android.app.ActivityThread");
    Method currentThreadMethod = threadClazz.getDeclaredMethod("currentActivityThread");
    Object currentThread = currentThreadMethod.invoke(null);

    Field instrumentationField = threadClazz.getDeclaredField("mInstrumentation");
    instrumentationField.setAccessible(true);
    
    // カスタムInstrumentationをセットして起動プロセスを監視・変更
    Instrumentation original = (Instrumentation) instrumentationField.get(currentThread);
    instrumentationField.set(currentThread, new CustomInstrumentation(original));
} catch (Exception e) {
    e.printStackTrace();
}

この手法により、IntentがAMSに届く直前に「ターゲットActivity」を「登録済みのProxyActivity」に差し替え、起動後に再びターゲットへ戻すといった操作が可能になります。

プロセス優先度(ADJ)とメモリ管理

Androidはメモリが不足すると、oom_adj(Out of Memory Adjustment)値に基づいてプロセスを終了させます。最前面のActivityを持つプロセスはADJ値が低く(優先度が高く)、バックグラウンドで長時間放置されたタスクはADJ値が高くなります。

アプリのタスク情報をカスタマイズし、最近使ったタスク画面での見栄えを調整するには、TaskDescriptionを使用します。

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
    ActivityManager.TaskDescription td = new ActivityManager.TaskDescription(
            "CustomLabel", 
            BitmapFactory.decodeResource(getResources(), R.drawable.app_icon),
            Color.BLUE
    );
    setTaskDescription(td);
}

タグ: Android AMS Activity Framework Java

8月9日 20:25 投稿