Activity Manager Service (AMS) の起動と役割
Androidシステムにおいて、AMS(Activity Manager Service)はシステム全体の司令塔として機能し、Activity、Service、放送(Broadcast)、コンテンツプロバイダーといった主要コンポーネントの管理を担っています。
1. AMSの起動シーケンス
システムが起動すると、まずZygoteプロセスが立ち上がります。ZygoteはAndroidにおける「プロセスの雛形」であり、新しいアプリケーションが起動される際、Zygoteは自身のプロセスをフォークして新しいアプリ用の実行環境を生成します。
- プロセスの生成: システムサーバーがアプリの起動を検知すると、Zygoteに対して新しいプロセスを作成するよう要求します。
- AMSの初期化: 起動したプロセス内でAMSのインスタンスが作成され、システム内のActivityの状態、タスクスタック、サービス接続などを管理する準備を整えます。
- 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つのフェーズで管理します。
- Running: ユーザーと対話中の最前面の状態。
- Paused: フォーカスは失っているが、画面上に一部見えている状態。
- Stopped: 完全に隠れているが、メモリ上には維持されている状態。
- 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);
}