作者: ヨクン
まず、最初にエラーが報告されます。
Caused by androidx.fragment.app.Fragment$InstantiationException
Unable to instantiate fragment xxx: could not find Fragment constructor
このエラーの理由は、Fragment がパラメーターを使用して構築メソッドをオーバーロードする場合、パラメーターのないデフォルトの構築メソッドが実装されないためです。アクティビティがリサイクルされ、フラグメントを復元しようとして戻ってくると、エラーが報告されます。
では、リサイクルされるアクティビティをシミュレートするにはどうすればよいでしょうか?
便利で素早いメソッドは open 开发者选项
-不保留活动
であることを知っている人もいるかもしれません。これにより、アクティビティがバックグラウンドに戻るたびにリサイクルされ、このケースをテストするのに非常に便利です。
しかし、この方法とは別に、この状況を再現するにはどうすればよいでしょうか?
ここで私は方法を提案します。アプリを開いて、ホームを押してバックグラウンドに戻り、携帯電話のメモリを大量に消費する可能性のある他の大きなアプリやゲームを電話で必死に開き、電話のメモリが使い果たされるまで待つことはできますか?この状況を再現することは可能ですか?
結論は「いいえ」です。2 つの概念を混同しないでください。系统内存不足
またApp内存不足
、この 2 つが引き起こす可能性のある結果も異なります。
- システムメモリが不十分 -> アプリケーションプロセスを強制終了します
- アプリのメモリが不足しています -> バックグラウンドアクティビティを強制終了します
まず、Android フレームワークがプロセスの作成と管理をカプセル化していることは明らかであり、APP 開発者にとって必要なのは、Android の 4 つの主要コンポーネントの使用方法だけを理解することだけです。Activity、Service、ContentProvider、および BroadcastReceiver のいずれかのコンポーネントが開始されると、それが実行するプロセスが存在する場合は、そのコンポーネントが直接使用されます。存在しない場合、フレームワーク コードは自動的に startProcessLocked を呼び出してプロセスを作成します。したがって、このプロセスは APP にとってほとんど透過的ですが、Android システムを深く理解するにはプロセスを理解することが重要です。
1. システムメモリが不足しています -> アプリケーションプロセスを強制終了します
1.1. LKM の概要
Android の最下層は依然として Linux ベースであり、Linux では、メモリが少ない場合、一部のプロセスを強制終了してメモリを解放する oom キラーが存在し、Android はこれに基づいていくつかの調整を行っていますlowmemorykiller
。結局のところ、携帯電話のメモリは比較的限られており、Android の APP は使用されなくなってもすぐに強制終了されないため、プロセスのActivityManagerService
スケジューリングや上位層のプロセスの強制終了には多くの方法がありますが、結局のところ、実際の状況は携帯電話の残りのメモリを考慮する必要があります。lowmemorykiller の役割は、メモリが不足しているときに、強制ActivityManagerService
終了する時間がなかったものの、ユーザーにとってそれほど重要ではないいくつかのプロセスを電話の通常の操作。
lowmemkiller にはいくつかの重要な概念が含まれています:
/sys/module/lowmemorykiller/parameters/minfree : 内部は「,」で区切られた一連の数値であり、各数値はメモリ レベルを表します
/sys/module/lowmemorykiller/parameters/ adj : 対応する上記の一連の数値に対して、各配列はプロセスの優先度レベルを表します。
比例:
/sys/module/lowmemorykiller/parameters/minfree:18432, 23040, 27648, 32256, 55296, 80640
/sys/module/lowmemorykiller/parameters/adj: 0, 100, 200, 300, 900, 906
代表とは、2 つの数値セットが 1 対 1 に対応することを意味します。
- 携帯電話のメモリが
80640
それ以下の場合、906
優先度以上のプロセスを強制終了します - メモリが少ない場合
55296
、優先度900
以上のプロセスを強制終了します
各携帯電話の設定は異なる場合があります。手元の携帯電話を確認できます。root が必要です。
1.2. ADJの見方
プロセスを確認するにはどうすればよいですかADJ
? たとえば、QQ を見たい場合adj
-> adb shell ps | grep "qq"
UID PID PPID C STIME TTY TIME CMD
u0_a140 9456 959 2 10:03:07 ? 00:00:22 com.tencent.mobileqq
u0_a140 9987 959 1 10:03:13 ? 00:00:07 com.tencent.mobileqq:mini3
u0_a140 16347 959 0 01:32:48 ? 00:01:12 com.tencent.mobileqq:MSF
u0_a140 21475 959 0 19:47:33 ? 00:01:25 com.tencent.mobileqq:qzone
# 看到QQ的PID为 9456,这个时候打开QQ,让QQ来到前台
-> adb shell cat /proc/9456/oom_score_adj
0
# 随便打开一个其他的App
-> adb shell cat /proc/9456/oom_score_adj
700
# 再随便打开另外一个其他的App
-> adb shell cat /proc/9456/oom_score_adj
900
0
adj は、フォアグラウンドにある場合、バックグラウンドにある場合700
、バックグラウンドに戻って他のアプリを開いた後など、ユーザーの行動に応じて常に変化していることがわかります900
。
一般的な ADJ レベルは次のとおりです。
ADJレベル | 価値 | 意味 |
---|---|---|
NATIVE_ADJ | -1000 | ネイティブプロセス |
SYSTEM_ADJ | -900 | system_server プロセスのみを参照します |
PERSISTENT_PROC_ADJ | -800 | システム永続プロセス |
PERSISTENT_SERVICE_ADJ | -700 | システムまたは永続的なプロセスに関連付けられている |
FOREGROUND_APP_ADJ | 0 | フォアグラウンドプロセス |
VISIBLE_APP_ADJ | 100 | 目に見えるプロセス |
PERCEPTIBLE_APP_ADJ | 200 | バックグラウンドでの音楽再生などのプロセス認識 |
BACKUP_APP_ADJ | 300 | バックアッププロセス |
HEAVY_WEIGHT_APP_ADJ | 400 | 重量級プロセス |
サービス_ADJ | 500 | サービスプロセス |
HOME_APP_ADJ | 600 | ホームプロセス |
PREVIOUS_APP_ADJ | 700 | 前工程 |
SERVICE_B_ADJ | 800 | Bリストのサービス |
CACHED_APP_MIN_ADJ | 900 | 非表示プロセスの adj の最小値 |
CACHED_APP_MAX_ADJ | 906 | 非表示プロセスの adj の最大値 |
したがって、システムメモリが不足すると、プロセス全体が強制終了され、スキンが失われ、Activity が消えてしまいます。
2. アプリのメモリが不足している -> バックグラウンドアクティビティを強制終了します
上記の分析は、プロセスを直接強制終了する場合です。プロセスが強制終了されると、メモリ状況が取り返しのつかない状態に達したことを意味します。メモリ リークとは別に、フレームワークには、メモリ リークの状況を回避するためのいくつかの戦略も必要です。メモリが利用可能です。次に、FW でアクティビティをリサイクルするロジック (コードベース Android-30) を見てみましょう。
Android Studio では、パッケージ名の下にソース
com.android.internal
コードを表示できません。Shift キーをダブルクリックして、右上隅を確認してくださいInclude non-prject Items
。
エントリActivityThread
が配置されるメソッドattach
、ActivityThread はアプリのエントリ プログラムであり、main
メソッド内で作成および呼び出されますatttach
。
// ActivityThread.java
private void attach(boolean system, long startSeq) {
...
// Watch for getting close to heap limit.
BinderInternal.addGcWatcher(new Runnable() {
@Override public void run() {
// mSomeActivitiesChanged在生命周期变化的时候会修改为true
if (!mSomeActivitiesChanged) {
return;
}
Runtime runtime = Runtime.getRuntime();
long dalvikMax = runtime.maxMemory();
long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
if (dalvikUsed > ((3*dalvikMax)/4)) {
mSomeActivitiesChanged = false;
try {
ActivityTaskManager.getService().releaseSomeActivities(mAppThread);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
}
});
...
}
ここに焦点を当てますBinderInternal.addGcWatcher
。明確にする必要があるいくつかの点を次に示します。
addGcWatcher
この Runnable は何のためにあるのか、いつ実行されるのか。- ここでの理解方法
maxMemory() / totalMemory() / freeMemory()
、値の意味は何ですか releaseSomeActivities()
何が行われたのか、またアクティビティをリサイクルするロジックは何なのか。
mSomeActivitiesChanged
もう 1 つの小さな点は、このフラグ ビットは、検出作業があまり頻繁に実行されず、検出が必要になったreleaseSomeActivities
後に割り当てがあることを示すためにここで使用されていることですmSomeActivitiesChanged = false;
。そして、すべてのmSomeActivitiesChanged = true
操作は、handleStartActivity/handleResumeActivity...
Activity ステートメント サイクルでこれらの操作を待機しています。Activity ステートメントのサイクルが変更された後にのみ、リサイクルする必要があるかどうかの検出を継続するように制御されます。
2.1. Gcウォッチャー
BinderInternal.addGcWatcher
これは静的メソッドであり、関連するコードは次のとおりです。
public class BinderInternal {
private static final String TAG = "BinderInternal";
static WeakReference<GcWatcher> sGcWatcher = new WeakReference<GcWatcher>(new GcWatcher());
static ArrayList<Runnable> sGcWatchers = new ArrayList<>();
static Runnable[] sTmpWatchers = new Runnable[1];
static final class GcWatcher {
@Override
protected void finalize() throws Throwable {
handleGc();
sLastGcTime = SystemClock.uptimeMillis();
synchronized (sGcWatchers) {
sTmpWatchers = sGcWatchers.toArray(sTmpWatchers);
}
for (int i=0; i<sTmpWatchers.length; i++) {
if (sTmpWatchers[i] != null) {
sTmpWatchers[i].run();
}
}
sGcWatcher = new WeakReference<GcWatcher>(new GcWatcher());
}
}
public static void addGcWatcher(Runnable watcher) {
synchronized (sGcWatchers) {
sGcWatchers.add(watcher);
}
}
...
}
2 つの重要な役割:sGcWatchers
とsGcWatcher
sGcWatchers
呼び出しが保存された後に実行する必要がある RunnableBinderInternal.addGcWatcher
(つまり、アクティビティを強制終了するかどうかを検出する Runnable)。sGcWatcher
これはインストールされたnew GcWatcher()
弱参照です。
弱参照のルールは、オブジェクトにそれを参照するための弱参照が 1 つしかない場合、そのオブジェクトは GC 中にリサイクルされるということです。それを参照するための sGcWatcher の弱い参照しか存在しないことは明らかなのでnew
、 GC がこのオブジェクトをリサイクルするたびに、リサイクルするときにこのオブジェクトのメソッドが呼び出され、以前に登録された Runnable が実行されます。方法。ここでは削除された Runnable がないことに注意してください。つまり、実行が何回実行されたとしても、最初に渡された Runnable は常に存在します。GcWatcher()
GcWatcher
finalize()
finalize()
sGcWatcher
addGcWatcher(Runnable watcher)
システム全体でaddGcWatcher
呼び出し場所が 1 つしかないのに、sGcWatchers
実際にはリストが存在するのはなぜですか? 自分でそのようなコードを書いて、システムの現在の BinderInternal を取得する方法を考えたときに、少し理解できました。誰かが積極的に呼び出して GcWatcher を大量に取得することを恐れているのだと思います。システムに障害が発生する可能性があるためaddGcWatcher
、リストを作成しました。。
2.2. アプリの使用可能なメモリ
上記の Runnable は、現在のシステムにメモリが不足していることをどのように検出するのでしょうか? 次のコードを通して
Runtime runtime = Runtime.getRuntime();
long dalvikMax = runtime.maxMemory();
long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
if (dalvikUsed > ((3*dalvikMax)/4)) { ... }
変数名を見ると、使用メモリが総メモリの 3/4 に達したら何かを行うことがわかります。これらのメソッドのコメントは次のとおりです。
/**
* Returns the amount of free memory in the Java Virtual Machine.
* Calling the gc method may result in increasing the value returned by freeMemory.
* @return an approximation to the total amount of memory currently available for future allocated objects, measured in bytes.
*/
public native long freeMemory();
/**
* Returns the total amount of memory in the Java virtual machine.
* The value returned by this method may vary over time, depending on the host environment.
* @return the total amount of memory currently available for current and future objects, measured in bytes.
*/
public native long totalMemory();
/**
* Returns the maximum amount of memory that the Java virtual machine will attempt to use.
* If there is no inherent limit then the value java.lang.Long#MAX_VALUE will be returned.
* @return the maximum amount of memory that the virtual machine will attempt to use, measured in bytes
*/
public native long maxMemory();
まず、各アプリで使用可能なメモリの量を確認します。これらのランタイム値は誰が管理しますか?
adb shell getprop | grep "dalvik.vm.heap" コマンドを使用すると、携帯電話によって各仮想マシン プロセスに割り当てられたヒープ構成情報を表示できます。
`yocn@yocn ~ % adb shell getprop | grep "dalvik.vm.heap"
[dalvik.vm.heapgrowthlimit]: [256m]
[dalvik.vm.heapmaxfree]: [8m]
[dalvik.vm.heapminfree]: [512k]
[dalvik.vm.heapsize]: [512m]
[dalvik.vm.heapstartsize]: [8m]
[dalvik.vm.heaptargetutilization]: [0.75]` </pre>
これらの値は何を意味するのでしょうか?
- [dalvik.vm.heapgrowthlimit] および [dalvik.vm.heapsize] は、現在のアプリケーション プロセスによって割り当てられるメモリの最大制限です。アプリケーションで android:largeHeap="true" を宣言した場合、通常、 heapgrowthlimit < heapsize になります。マニフェスト内のタグ、APP はヒープサイズまで OOM、それ以外の場合はヒープ拡張制限に達すると OOM
- [dalvik.vm.heapstartsize] Java ヒープの初期サイズ。Davlik 仮想マシンが起動時にシステムに適用する物理メモリのサイズを指定します。その後、必要に応じてシステムに物理メモリを徐々に適用し、最終的にはシステムに適用されます。 MAXに達する
- [dalvik.vm.heapminfree] GC 後のヒープの最小空き値
- [dalvik.vm.heapmaxfree] ヒープの最大空き値
- [dalvik.vm.heaptargetutilization] ヒープ ターゲット使用率
さらにわかりにくいのは heapminfree、heapmaxfree、heaptargetutilization ですが、上記の方法によると、heapminfree
<<の条件でheaptargetutilizationに近づけます。freeMemory()
heapmaxfree
(totalMemory() - freeMemory()) / totalMemory()
したがって、最初のコードは、現在使用されているメモリが割り当てられたメモリの 3/4 に達すると、いくつかのアクティビティを強制終了するために呼び出されるということですreleaseSomeActivities
。
2.3. releaseいくつかのアクティビティ
releaseSomeActivities は API 29 の前後で大きく異なります。それらを個別に見てみましょう。
2.3.1. API 28 に基づく releaseSomeActivities バージョンは次のように実装されます。
// step①:ActivityManagerService.java
@Override
public void releaseSomeActivities(IApplicationThread appInt) {
synchronized(this) {
final long origId = Binder.clearCallingIdentity();
try {
ProcessRecord app = getRecordForAppLocked(appInt);
mStackSupervisor.releaseSomeActivitiesLocked(app, "low-mem");
} finally {
Binder.restoreCallingIdentity(origId);
}
}
}
// step②:ActivityStackSupervisor.java
void releaseSomeActivitiesLocked(ProcessRecord app, String reason) {
TaskRecord firstTask = null;
ArraySet<TaskRecord> tasks = null;
for (int i = 0; i < app.activities.size(); i++) {
ActivityRecord r = app.activities.get(i);
// 如果当前有正在销毁状态的Activity,Do Nothing
if (r.finishing || r.state == DESTROYING || r.state == DESTROYED) {
return;
}
// 只有Activity在可以销毁状态的时候才继续往下走
if (r.visible || !r.stopped || !r.haveState || r.state == RESUMED || r.state == PAUSING
|| r.state == PAUSED || r.state == STOPPING) {
continue;
}
if (r.task != null) {
if (firstTask == null) {
firstTask = r.task;
} else if (firstTask != r.task) {
// 2.1 只有存在两个以上的Task的时候才会到这里
if (tasks == null) {
tasks = new ArraySet<>();
tasks.add(firstTask);
}
tasks.add(r.task);
}
}
}
// 2.2 只有存在两个以上的Task的时候才不为空
if (tasks == null) {
if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Didn't find two or more tasks to release");
return;
}
// If we have activities in multiple tasks that are in a position to be destroyed,
// let's iterate through the tasks and release the oldest one.
// 2.3 遍历找到ActivityStack释放最旧的那个
final int numDisplays = mActivityDisplays.size();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
// Step through all stacks starting from behind, to hit the oldest things first.
// 从后面开始遍历,从最旧的开始匹配
for (int stackNdx = 0; stackNdx < stacks.size(); stackNdx++) {
final ActivityStack stack = stacks.get(stackNdx);
// Try to release activities in this stack; if we manage to, we are done.
// 尝试在这个stack里面销毁这些Activities,如果成功就返回。
if (stack.releaseSomeActivitiesLocked(app, tasks, reason) > 0) {
return;
}
}
}
}
上記のコードはコメント化されていますが、注意すべき点を整理してみましょう。プロセス全体の傾向が把握tasks
できる
- 2.1 および 2.2: 最初のサイクルは、 firstTask に値を割り当てます。 firstTask != r.task の場合にのみ値を割り当て
tasks
、tasks
その後もその操作を続けます。したがって、単一スタックのアプリケーションはリサイクルされず、tasks
null の場合は直接返され、何も行われません。 - 2.3: 実際、この長い double for ループには、最初のステップのトラバーサルは含まれていません
tasks
。アクティビティの実際のリリースは、ActivityStack 内にあるため、これらのタスクに対応する ActivityStack を見つけて、ActivityStack がタスクを破棄するまで待ちます。それらは正常に破壊されます。
引き続き releaseSomeActivitiesLocked を参照してください。
// step③ ActivityStack.java
final int releaseSomeActivitiesLocked(ProcessRecord app, ArraySet<TaskRecord> tasks, String reason) {
// Iterate over tasks starting at the back (oldest) first.
int maxTasks = tasks.size() / 4;
if (maxTasks < 1) {
maxTasks = 1;
}
// 3.1 maxTasks至少为1,至少清理一个
int numReleased = 0;
for (int taskNdx = 0; taskNdx < mTaskHistory.size() && maxTasks > 0; taskNdx++) {
final TaskRecord task = mTaskHistory.get(taskNdx);
if (!tasks.contains(task)) {
continue;
}
int curNum = 0;
final ArrayList<ActivityRecord> activities = task.mActivities;
for (int actNdx = 0; actNdx < activities.size(); actNdx++) {
final ActivityRecord activity = activities.get(actNdx);
if (activity.app == app && activity.isDestroyable()) {
destroyActivityLocked(activity, true, reason);
if (activities.get(actNdx) != activity) {
// Was removed from list, back up so we don't miss the next one.
// 3.2 destroyActivityLocked后续会调用TaskRecord.removeActivity(),所以这里需要将index--
actNdx--;
}
curNum++;
}
}
if (curNum > 0) {
numReleased += curNum;
// 移除一个,继续循环需要判断 maxTasks > 0
maxTasks--;
if (mTaskHistory.get(taskNdx) != task) {
// The entire task got removed, back up so we don't miss the next one.
// 3.3 如果整个task都被移除了,这里同样需要将获取Task的index--。移除操作在上面3.1的destroyActivityLocked,移除Activity过程中,如果task为空了,会将task移除
taskNdx--;
}
}
}
return numReleased;
}
-
3.1: ActivityStack は maxTasks を使用して、最大でも task.size() / 4 がクリーンアップされ、少なくとも 1 つの TaskRecord がクリーンアップされ、同時にフォアグラウンドに表示される少なくとも 1 つの TaskRecord が保持されるようにします。 TaskRecordが2つある場合は、前のものがクリーンアップされ、前景表示が維持されます。これに対して、3つある場合は、最も古いものが効果的にクリーンアップされているか、つまりクリーンアップされたActivityがあるかどうかを確認します。 、そうであれば、1 つだけをクリーンアップし、2 つを保持します。そうでない場合は、2 番目に古いものをクリーンアップし続け、A の前景ディスプレイを保持します。4 つある場合は同様に、5 つある場合は、少なくとも 2 つをクリーンアップします。一般的な APP では、TaskRecord が 3 つを超えることはほとんどありません。
-
3.2: ここでのクリーンアップのロジックは非常に明確です。for ループで、目的のアクティビティが見つかった場合はクリーンアップされますが、ここには actNdx があります – なぜでしょうか? コメントには、アクティビティがリストから削除されたことが示されています。引き続き下に進むには、index- が必要ですが、このメソッドには lsit からアクティビティを削除する操作がありません。アクティビティは destroyActivityLocked メソッド内にある必要があります。追跡し続けると、いつでも追跡できます
TaskRecord.java#removeActivity()
。現在の TaskRecord の mActivities から削除されるため、index– が必要です。 -
3.3: 上記の actNdx– を理解すると、インデックスが存在する理由もわかります
ActivityStack.java#removeActivityFromHistoryLocked()
。
if (lastActivity) {
removeTask(task, reason, REMOVE_TASK_MODE_DESTROYING);
}
タスクにアクティビティがない場合は、タスクを削除する必要があります。
上記はAPI 28に基づくreleaseSomeActivitiesの分析です。
2.3.2. 29 以降のバージョンに基づく releaseSomeActivities の実装は次のとおりです。
// ActivityTaskManagerService.java
@Override
public void releaseSomeActivities(IApplicationThread appInt) {
synchronized (mGlobalLock) {
final long origId = Binder.clearCallingIdentity();
try {
final WindowProcessController app = getProcessController(appInt);
app.releaseSomeActivities("low-mem");
} finally {
Binder.restoreCallingIdentity(origId);
}
}
}
// WindowProcessController.java
void releaseSomeActivities(String reason) {
// Examine all activities currently running in the process. Candidate activities that can be destroyed.
// 检查进程里所有的activity,看哪些可以被关掉
ArrayList<ActivityRecord> candidates = null;
if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Trying to release some activities in " + this);
for (int i = 0; i < mActivities.size(); i++) {
final ActivityRecord r = mActivities.get(i);
// First, if we find an activity that is in the process of being destroyed,
// then we just aren't going to do anything for now; we want things to settle
// down before we try to prune more activities.
// 首先,如果我们发现一个activity正在执行关闭中,在关掉这个activity之前什么都不做
if (r.finishing || r.isState(DESTROYING, DESTROYED)) {
if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Abort release; already destroying: " + r);
return;
}
// Don't consider any activities that are currently not in a state where they can be destroyed.
// 如果当前activity不在可关闭的state的时候,不做处理
if (r.mVisibleRequested || !r.stopped || !r.hasSavedState() || !r.isDestroyable()
|| r.isState(STARTED, RESUMED, PAUSING, PAUSED, STOPPING)) {
if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Not releasing in-use activity: " + r);
continue;
}
if (r.getParent() != null) {
if (candidates == null) {
candidates = new ArrayList<>();
}
candidates.add(r);
}
}
if (candidates != null) {
// Sort based on z-order in hierarchy.
candidates.sort(WindowContainer::compareTo);
// Release some older activities
int maxRelease = Math.max(candidates.size(), 1);
do {
final ActivityRecord r = candidates.remove(0);
r.destroyImmediately(true /*removeFromApp*/, reason);
--maxRelease;
} while (maxRelease > 0);
}
}
新しいバージョンは、API 29 で新たにreleaseSomeActivities
追加されActivityTaskManagerService.java
、AMS の機能の一部を担うこのクラスに配置されます。タスク スタックに基づいてアクティビティを再利用する API 28 の戦略と比較して、新しいバージョンの戦略はシンプルかつ明確であり、さらに抜本的です。
すべてのアクティビティを走査し、破壊可能な状態にないアクティビティを計画し、アクティビティの積み重ねの順序、つまり Z 軸の古いものから新しいものへの順序に従ってアクティビティを破棄します。
興味のある読者は、独自のテスト コードを作成し、API 28 および API 28+ の携帯電話でテストして、リサイクル戦略が上記の分析と一致しているかどうかを確認できます。
要約:
- システムメモリが不足している場合、LMK はメモリ設定項目に従ってプロセスを強制終了し、メモリを解放します。
- 強制終了する場合、プロセスの ADJ ルールに従って強制終了されます。
- アプリのメモリが不足している場合、GcWatcher はアクティビティをいつリサイクルするかを決定します。
- getprop コマンドを使用すると、現在の携帯電話の JVM メモリ割り当てと OOM 構成を表示できます。
- releaseSomeActivities は API 28 と API 28+ で大きく異なり、以前のバージョンではタスクの数に応じてクリーンアップするタスクが決定されます。上位バージョンは単純かつ失礼で、アクティビティを走査し、z オーダーに従って並べ替え、古いアクティビティを最初にリリースします。