Closed kawasin73 closed 3 years ago
スマホは1画面に1つの機能を実装するのが基本。画面遷移が多くなる。
スワップアウトが無効になっているから、マルチタスクの状況はサーバーとは異なる。 その代わり、Activity は半手動でスワップアウトのような機構を実現している。 たくさんのアプリが連携する。その分同時に複数のアプリが動く。
明示的インテント
ACTION_VIEW インテント、暗黙的インテント
FLAG_ACTIVITY_NEW_TASK
フラグをつけると、新しいスタックが作られる。
ACTION_PICK インテントと onActivityResult()
この後の章の概要なので割愛
Binder は Android の中の分散オブジェクトの仕組み。プロセスや言語を気にせず呼び出せる。システムサービスは Binder を利用して機能を提供する システムサービスは1つのプロセスに複数存在できる。1つのプロセスにまとめることで効率的になる
init.rc から起動される
init.rc ファイルは複数のディレクトリに置かれている。
init.rc でソケットを作成するオプションがある。
かつてはプロセスという概念がなかった。プロセスの実現には MMU が必須。
PC の場合はユーザーがインストールしたソフトウェアは信頼できるものという前提がある。管理者がいるから? スマホのユーザーは知識がないので信頼できるかわからない。
新しいセキュリティモデルを作るのは長い道のり。バグをあぶり出すための時間が必要。Linux のマルチユーザーのセキュリティモデルは長い実績がある。Android ではアプリ同士は信頼しない。
PackageManagerService がインストール処理を行う。
/data/data/<パッケージ名>
というディレクトリを作成し、割り振られた uid に chown するuid の範囲は frameworks/base/core/java/android/os/Process.java
で定義されていて、10000 ~ 19999。
使われている uid は frameworks/base/services/core/java/com/android/server/pm/Settings.java
の mUserIds
の ArrayList に保持されている。
複数のアプリで権限を共有したいときは AndroidManifest.xml の sharedUserId を指定して署名すると、アプリの uid は同じものが割り当てられる。
chown などだけをする installd プロセス。
init プロセスから直接 fork される
Linux の capability の仕組み。
installd はソケットから受け取った情報をもとに mkdir と chown をする。
SharePreferences や SQLite の実ファイル。独自のファイルは、/data/data/<パッケージ名>/files
の下に置くことになっている。Context#getFilesDir()
で取れる。
installd は dexopt コマンドも受け取る。コンパイル方式の変更によって installd でやることも変わってきている。
全てのアプリの情報を持つ。
/data/system/packages.xml
にある。壊れている場合は /data/system/packages-backup.xml
が使われる。
package 要素の他にも、permissions や shared-user もある。
Android アプリは NDK を使って一部をネイティブで開発して Java からそれを呼ぶことができる。該当するアーキテクチャのバイナリを FAT バイナリから取り出して lib ディレクトリにコピーする
system ユーザーのみが読み書きできる。
アクション指定して暗黙的インテントでどのアプリとも連携できる
アプリは拡張子が apk の zip アーカイブ。 インテントの解決は、PackageManagerService の resolveIntent() メソッドで行われる。
PackageManagerService は起動時に packages.xml を読み込み、全ての apk ファイルの AndroidManifest.xml から intent-filter をリストに登録する。アプリがインストールした時も。
全体像の説明
lmkd にメッセージを送ることで oom_score_adj を更新し、そしてこのスコアに応じて、メモリ不足の時には lowmemorykiller ドライバや OOM Killer がスコアの高いプロセスを kill することでメモリを解放する。 Linux のメモリ不足の仕組みだけだと運任せになるので2段構えになっている。
Linux カーネルは oom_score を使う。経験則によって重要そうでなくてメモリをたくさん持っているプロセスを殺す。ユーザーランドから指定するための仕組みが oom_score_adj
oom_score_adj は特権のあるユーザーのみ設定可能。そのプロセスが lmkd
デバイスドライバとして実装。Linux がプロセスを kill する前に不要なプロセスを殺す Android の機能 このドライバも oom_score_adj を参照して殺すプロセスを決める。
demand paging ユーザプロセスがメモリを確保・解放しても実際に割り当てられたり解放されているとは限らない。 Linux のメモリアロケータは大きく2つに分けられる。
解放のタイミング
slab アロケータのメモリもゾーンアロケータと同じタイミングで回収が試みられる。 メモリを回収した後もメモリが不足した場合、緊急事態として OOM Killer が発動
/proc/<process id>/oom_score
で確認できる。SIGKILL で kill する。Linux の OOM Killer に頼るには Android のアプリの kill は頻度が高すぎる。
/proc/<process id>/oom_score_adj
には -1000 から 1000 までの値を設定可能。 root 権限が必要。小さいほど kill されなくなる。
Low Memory Killer は、Android 上で OOM Killer をカスタマイズしている仕組みや、もっと前の段階でメモリを空ける仕組み
Linux ではカーネルはファイルシステムの知識を持たず、キャッシュの解放はファイルシステム自身がコールバックを呼び出すようになっている。これが shrinker
このコールバックを使って裏に回った Activity のプロセスを kill するのに活用
メモリ逼迫でカーネルが shrink_slab()
を呼び出す。これがディスクキャッシュを削減するタイミング。このタイミングのコールバックを登録するのが register_shinker()
というインカーネル API。登録されるコールバックを shinker と呼ぶ。
Android 独自の lowmemorykiller というカーネルモジュールが shrinker を使って裏の Activity を kill する。
oom_score_adj を元に kill する Activity を選ぶ。その時のメモリの切迫具合に応じて段階的に kill される。
/sys/module/lowmemorykiller/parameters/minfree
/sys/module/lowmemorykiller/parameters/adj
lmkd が adj と minfree のパラメータ変更、oom_score_adj の変更を行う。root 権限。
/dev/socket/lmkd
というソケットに読み書きするのは ActivityManagerService だけ。
lmkd へのコマンドは、2つだけ。プロトコルは4バイト区切りで、最初の 4 バイトがコマンドで、続くブロックがその引数。
画面が小さいので一つの画面に一つの役割を持たせるのが一般的。そのため、画面遷移が必然的に頻繁に起こる。 ある画面を再利用したくなる。前後の遷移と画面を切り離すことが重要。C などの関数がコールスタックで実現されるのに似ている。
タスクとは複数の Activity が連携して1つの仕事をこなす単位。 バックキーを押して辿ることのできる Activity を含む
基本的には新しい Activity が別のタスクになるのかは system がよしなに判断してくれる。launchMode で制御もできる
ActivityStack
ActivityStack にはホーム画面用とそれ以外用(アプリの)の2つの ActivityStack インスタンスがある。一般的なタスクは作成される都度アプリの ActivityStack に載せられる。 タスク内の Acitivity の順序は変えられない。タスクの順番はアクセスするたびに ActivityStack の一番上に移動する ActivityStack はタスクのリストを保持している。 一旦ホーム画面に行った後に別のタスクにアクセスすると戻り先が Home であるというフラグが立つ。
launchMode は AndroidManifest.xml の
Intent#addFrags()
で設定。
何も設定していない場合。
例えば Chrome 一度ホーム画面を見ると戻り先のフラグが home に上書きされる。別のタスクからその Activity を起動してもすでに作られている Activity に飛ばされ、戻り先のフラグもクリアされる。 難しいので非推奨
FLAG_ACTIVITY_CLEAR_TOP はよく使う。 A-B-C-D と Activity が積まれている状態で A-B と遷移したい時。C-D を同時に消す。
-17 から +16 の間で指定。大きいほど削除していい。キャッシュされているアプリは 9 ~ 15 で時間に応じて大きくなる。見えているアプリには 0 と 1
ActivityStack には Activity ではなく、ActivityRecord が積まれる。
Activity を再開するのに必要な情報をアプリプロセスとは別に保存する。ActivityRecord は ActivityManagerService のプロセスに保持される。
Activity#attach()
で Activity と ActivityRecord が対応づけされる。
onSaveInstanceState() と onRestoreInstanceState() Bundle は Activity のあるプロセスで作成されて、Binder で ActivityManagerService に送られて ActivityRecord に保存される。
SystemServer のプロセスは -16 に設定される。これが kill されるのは相当な異常事態
パッケージ名から ProcessRecord を調べて、対象となるプロセスがあるかを確かめる。
ユーザーにアプリを起動しているかしていないかを意識させない。 そのため、アプリには終了する方法は提供しない。 アプリ開発者はライフサイクルに則っていれば、プロセスが kill されたかどうかを意識することなく実装できる
Activity のライフサイクルの各コールバックは、ActivityStack 上に ActivityRecord を操作するときに呼ばれる。 ActivityManagerService が ActivityStack を操作する。
ActivityRecord が Source of Truth になり、Activity がその写像になる。表にある Activity 以外は全てキャッシュのようなもの。
裏に行ったらいつ削除されてもいいように覚悟する。
ActivityMangerService から Activity のプロセスへ投げるメッセージは launch などの1つのメッセージだが、アプリのプロセスでは onCreate() や onStart() などの複数のライフサイクルメソッドが呼ばれる。 バックキーで一つ下の Activity を再開するとき、ActivityRecord を調べてそのプロセスがまだ生きているかを確かめる。生きている場合は resume のメッセージを送る。死んでいる場合はプロセスを生成し、launch のメッセージを Bundle 付きで送る。
Zygote からプロセスを fork。ソケットでエントリポイントとなるクラス名 (ActivityThread) や uid を受け取る。ActivityManagerService が Zygote に依頼する。
ActivityThread#main()
の時点では apk の情報はわからないので ActivityManagerService に問い合わせる。これが ActivityThread#attach()
。
attach() が終わると ActivityManagerService が ActivityThread の scheduleLaunchActivity() を呼ぶ。このときに token が作られてアプリのプロセスに渡される。token が Activity の id になる。
app_process
コマンドでバイトコードの実行環境を実行できる。2つのモードがある。
adb pm
コマンドで--zygote
オプションexception を使うのはコールスタックを一番上まで戻すため。
preloadClasses() では、/system/etc/preloaded-classes
に書かれているクラスがロードされる。
selectReadable() で server ソケットと peer ソケットを待つ。
ZygoteConnection#runOnce()
Process.start()
が Zygote にメッセージを書き込む。
SystemServer プロセスは他のプロセスよりも大きな権限が必要になるので事前に fork する。
main() を呼び出したスレッドがアプリのメインスレッドになる。
attach() は ActivityManagerService に自分のプロセスを登録する
ActivityManagerService#attachApplication()
を呼び出す。bindApplication() で ActivityManagerService から apk の情報が渡ってくる。
H で実装されている。
handleLaunchActivity()
いろんなメッセージ ID がある。
startActivity(intent)
ActivityManagerService がインテントから Activity を解決する。PackageManagerService#resolveIntent()
ActivityManagerService は ActivityRecord を作って ActivityStack に積む。
Process.start()
で Zygote にプロセス作成を依頼。
fork されたプロセスで ActivityThread.main()
が実行されて attach()
を呼び出す。apk のクラスがロードされて Activity が作成される。
このときに oom_score_adj を更新
lmkd で oom_score_adj のスコアを更新
Linux カーネルがメモリが足りなくなってきたときに shrink_slab()
を呼び出す。このコールバックから lowmemorykiller ドライバの shrinker が起動される。それで間に合わないときは OOM Killer が発動。
端末ごとに違うがよくある構成はこれ
/cache
にマウント/system
にマウント/data
にマウント基本的なフォーマットは2つとも同じ。ヘッダ、カーネル、RAM ディスク RAM ディスクにはルートになるファイルシステムが入っている ファクトリーイメージにはブートイメージの他のパーティションの初期イメージが含まれている。
Little Kernel Boot Loader 汎用的なブートローダ
通常 aboot パーティションは書き換えが困難になっているのが通例。 ブートローダの処理
Device Tree はデバイスのパラメータを記述。デバイスドライバが読み取って利用する
USB でホスト PC と通信できるモードが fastboot 大抵はボリュームダウンを押しながら電源を入れる。
init.cpp
の main() 関数から始まる
/init.rc
はハードコードされており、差し替える仕組みはない。
無限ループの部分
シグナルハンドラ (SIGCHLD_handler()
) でソケットペアにシグナルの内容を書き込む。そのソケットペアを epoll で監視する。
ソケットのハンドラでは、ServiceManager の ReapAnyOutstandingChildren() を実行。子プロセスを restart したりする。
/system/bin
の下の getprop
と setprop
で、adb shell から操作できる。
ro.
のプレフィックスは read only
ctl,
のプレフィックスは設定すると副作用を発行するだけで値を持たない。(ctl.stop
など)
SDK にはないが、listener を登録することで監視できる。
プロパティは基本的に init プロセスがオンメモリで保持する。値は /dev/__properties__
に mmap されて見ることができる。
Android の方がファイルシステムのマウントなど多くのことをやっている
EditText の情報などは onSaveInstance で自動的に保存されている
mWidnow.saveHierarchyState()
フォーカスの当たっている View などを保存する
Binder のための配慮
ただ、dispatchSaveInstanceState()
を呼ぶだけ。各 View の onSaveInstanceState()
が発動される。
フラグが SAVE_DISABLED
になっていない View が対象
目次