Комментарии 11
При угрозе уничтожения активности (но не процесса!)
Расскажите, как так может получаться? Система может прибить активность, но не процесс? Зачем?
Спасибо за интерес к статье. Принцип тут в следующем, процесс находится в верху цепочки, ниже него идет как раз activity, внутри которой например фрагменты. Когда надо освободить ресурсы, например из-за бездействия юзера, то можно убить главную Activity. Спасибо за интерес к статье. Принцип тут в следующем, процесс находится в верху цепочки, ниже него идет как раз activity, внутри которой например фрагменты. Когда надо освободить ресурсы, например из-за бездействия юзера, то можно убить главную активность, но процесс будет убит только в критическом случае. Перед убийством activity система пытается сохранить данные в bundle, а также в savedStateHandle внутри viewmodel. Если сохранить данные удалось( в большинстве случаев), то при возврате к процессу система воссоздаст её при помощи bundle и класса ActivityRecord, на основании которого Activity получит нужные параметры. Кейсы в которых система может уничтожить только Activity: 1) Поворот экрана 2) При нехватке памяти для конкретной активности, но достаточном количестве ресурсов для работы процесса 3) Смена языка 4) Смена темы, но процесс будет убит только в критическом случае. Перед убийством activity система пытается сохранить данные в bundle, а также в savedStateHandle внутри viewmodel. Если сохранить данные удалось( в большинстве случаев), то при возврате к процессу система воссоздаст её при помощи bundle и класса ActivityRecord, на основании которого Activity получит нужные параметры. Кейсы в которых система может уничтожить только Activity: 1) Поворот экрана 2) При нехватке памяти для конкретной активности, но достаточном количестве ресурсов для работы процесса 3) Смена языка 4) Смена темы
У вас комментарий как-то странно сдвоился. Текст повторяется.
То есть получается, что закрытие активности освобождает память, даже если процесс остаётся жив?
Вероятно, комментарий, как и статья, написан нейронкой.
Да, освобождает часть памяти занимаемой активностью. В целом возможны 2 сценария: 1) Наиболее вероятный - уничтожение всего процесса 2) Уничтожение отдельного компонента( например Activity).
Главное в этом, что система в обоих случаях сохранит данные в bundle и сможет все восстановить.
Решение система принимает сама в зависимости от версии и реализации.
Некоторые считают, что в Android нет механизмов освобождения памяти, которые останавливают фоновые Activity в пределах процесса при нехватке ресурсов. Т.е. механизмы освобождения памяти работают только между процессами. На самом деле, такие механизмы есть:
В настройках разработчика отладочная опция "Do not keep activities" включает уничтожение фоновых activity
Система может инициировать убийство фоновых Activity, если занято больше 3/4 от максимального разрешенного размера хипа (по этому пункту могу дать ссылку в исходном коде AOSP и пример, как я воспроизвел этот случай).
Спасибо. В дев настройки мало кто из масс пользователей лезет, разве нет? Про хип логично, конечно.
Ссылку не надо 😀 не пойму.
А подскажите, если знаете, как это работает - когда открыта цепочка activity. Типо A -> B -> C. Я слышал расхожие мнения, и что ситуация когда A и B destroyed а C жива невозможна, и что сначала выгрузятся они, а C последней, и что при убийстве активити - выгружаются только все скопом.
Это как раз про стек activity. Ситуация, когда A,B - destroyed, а C - resumed точно возможна (без перезапусков процессов и прочего).
Ниже код, которым эту ситуацию можно воспроизвести. Суть: запускаем друг за другом 3 activity и в каждой фоном забиваем 3/4 свободной памяти:
val lowmemThreshold get() = 3 * Runtime.getRuntime().maxMemory() / 4
val Activity.activityNo get() = intent.getIntExtra("ACTIVITY_NO", 1)
class MainActivity : Activity() {
val garbage: MutableList<ByteArray> = LinkedList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.i("MEMTEST", "onCreate: $activityNo")
// Запускаем стек из activity 1-2-3
if (activityNo < 3) {
Handler(Looper.getMainLooper()).postDelayed({
startActivity(Intent(this@MainActivity, MainActivity::class.java).apply {
putExtra("ACTIVITY_NO", activityNo + 1);
})
}, 5000)
}
// В каждой activity фоном медленно забиваем 3/4 памяти
object : Thread() {
override fun run() {
sleep(1000)
repeat(((lowmemThreshold + 1048575) / 1048576).toInt()) {
garbage += ByteArray(1048576)
sleep(10)
}
}
}.start()
}
override fun onDestroy() {
super.onDestroy()
Log.e("MEMTEST", "onDestroy: $activityNo")
}
}
Если запустить, то в логах увидим следующее:
onCreate: 1
onCreate: 2
onDestroy: 1
onCreate: 3
onDestroy: 2
Т.е. activity 1,2 - destroyed, а 3 - resumed.
Статус стека activity также можно проверить через adb коммандой adb shell dumpsys activity -p <package_name> activities
:
adb shell dumpsys activity -p testactivitybackgroundkill activities | grep -e 'Hist #' -e 'state='
* Hist #2: ActivityRecord{2ce6967 testactivitybackgroundkill/.MainActivity t45}
state=RESUMED stopped=false delayedResume=false finishing=false
* Hist #1: ActivityRecord{cec9b87 testactivitybackgroundkill/.MainActivity t45}
state=DESTROYED stopped=true delayedResume=false finishing=false
* Hist #0: ActivityRecord{bda9160 testactivitybackgroundkill/.MainActivity t45}
state=DESTROYED stopped=true delayedResume=false finishing=false
А логах adb logcat
буфера events
можно отследить события срабатывания кода, уничтожающего activity:
adb logcat -b events | fgrep 'low-mem'
I wm_destroy_activity: [0,198873440,45,testactivitybackgroundkill/.MainActivity,low-mem]
I wm_destroy_activity: [0,216832903,45,testactivitybackgroundkill/.MainActivity,low-mem]
Под капотом Android: как создаются, управляются и восстанавливаются процессы приложений в системе