Как стать автором
Поиск
Написать публикацию
Обновить

Под капотом Android: как создаются, управляются и восстанавливаются процессы приложений в системе

Уровень сложностиСредний
Время на прочтение10 мин
Количество просмотров5.4K
Всего голосов 14: ↑13 и ↓1+14
Комментарии11

Комментарии 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]





Зарегистрируйтесь на Хабре, чтобы оставить комментарий