Недавно я провел исследование о main() методе в Java и то, как он служит точкой входа для любого приложения Java. Это заставило меня задуматься, а как насчет Android-приложений? Есть ли у них основной метод? Как они загружаются? Что происходит за кулисами до выполнения onCreate()? Майкл Бэйли очень подробно рассказал о том, как работает Main Thread, так что это быстрый обзор его доклада плюс дополнительная информация из Android Open Source Project (AOSP).

В этой статье мы рассмотрим:

  1. Что происходит от нажатия на иконку приложения до запуска MainActivity
  2. Найдем основной метод приложения и узнаем, как основной поток (он же UI, он же Main Thread) получает свое назначение.
  3. Рассмотрим роль, которую играют Looper & Handler в передаче сообщений, которые в конечном итоге приводят к созданию вашей Activity.

Что происходит при запуске приложения


При запуске любого приложения, многое происходит глубоко внутри на уровне ядра, например начальная загрузка Zygote, загрузка классов в JVM, а для JVM — найти основной метод static void main(String args []) и вызывать его. В случае Android JVM находит основной метод main() в ActivityThread. Затем он вызывает main(), после чего ядро передает управление вашему приложению. Итак, мы нашли точку входа — ActivityThread, но прежде чем подробно изучить это, давайте посмотрим на дорожную карту процесса, чтобы визуализировать всю операцию.

1 Схема запуска приложения


Между вызовом метода main() и onCreate() в нашем MainActivity примерно 15 шагов, и в этой статье мы пройдем по ним. На рисунке 1 изображена общая схема запуска приложения, показывающая различные классы взаимодействия сверху и соответствующую цепочку методов. Шаги пронумерованы, и когда я обращаюсь к ним, я буду использовать следующие обозначения Process3 или Process14

image
Рисунок 1: Схема запуска приложения по шагам от вызова main() до onCreate() в MainActivity

2. Класс ActivityThread


В классе ActivityThread чуть более 6500 строк. Для краткости я определил самые важные для нас части. Давайте рассмотрим, что делает этот класс и связанный с ним основной метод, чтобы запустить нашу Activity

/**
* Code retrieved from https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ActivityThread.java
* Modifications are indicated in the comments
*/
public static void main(String[] args) {
  //Modification - Removed unrelated initializers. 
  //Android initializes some tracers, event loggers, enviroment initializers, trusted certificates and updates the process' state
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        // More logging
        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();
        throw new RuntimeException("Main thread loop unexpectedly exited");
}

Рисунок 2: Метод main() в ActivityThread, который служит точкой входа для запуска вашего приложения.

Как видно в коде: метод main() выполняет три важных дела:

1. Подготавливает основной Looper (MainLooper) (Process 2)
2. Настройка Handler'a (Process 4)
3. Вызов метода Looper.loop() в главном потоке (MainThread) (Process 6)

2.1 Подготовка main looper (Process 2–3)


Основной Looper задается вызовом Looper.prepareMainLooper() (см. Строку 8 в коде). Это отмечает текущий случайный поток, который выполняет всю работу по вызову метода main() в качестве основного потока приложений. Именно так и именно здесь определяется знаменитый главный поток для приложения в Android!

2.2 Вызов Handler'a (Process 4-5)


Внутри класса ActivityThread существует приватный внутренний класс H, да-да, все верно, просто H, который наследуется от класса Handler (см. рис. 4 и 7). В 12й строке экземпляр H-обработчика устанавливается как главный Handler потока. Что очень интересно знать о классе H, как вы сами увидите позже, это то, что он содержит более 50 определений состояния/событий, в которых может находиться ваше приложение, например LAUNCH_ACTIVITY, PAUSE_ACTIVITY, BIND_SERVICE и т.д.

2.3 Вызов метод loop() у Looper’а (Process 6–7)


После назначения главного потока в этом же главном потоке, для того чтоб мы могли в нем что-то выполнять, вызывается метод Looper.loop() (см. Строку 20). Это начинает выполнение сообщений в очереди сообщений Loopers. Теперь главный поток запущен и может начать обработку задач из очереди.

Обратите внимание, что в строке 18, если выполнение кода пойдет дальше чем Looper.loop() в 17 строке вдруг и приложение выйдет из цикла, то будет брошено исключение RuntimeException. Это говорит о том, что метод loop() в идеале никогда преждевременно не заканчивается. Мы увидим как это в следущем разделе.

3. Бесконечный loop() в Looper'е (Process 7,8,9)


/**
* AOSP
* Looper class
*/
public static void loop() {
	final Looper me = myLooper();
	if (me == null) {
		throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
	}
	final MessageQueue queue = me.mQueue;
	//code removed
	for (;;) {
		Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }            
    }
}

Рисунок 3: Код внутри метода loop() в классе Looper'e

Как мы видим в коде, в методе Looper.loop() есть очередь сообщений (строка 10) и внутри цикла вызывается queue.next(). MessageQueue заполняется Handler-'ом, о котором мы говорили в предыдущем разделе (см. Process 8). Обратите внимание на интересное описание условия в цикле for — здесь нет аргументов, только две точки с запятой говорят что это бесконечный цикл. Поэтому Looper в идеале никогда не заканчивается, если данное сообщение не null.

Итак, теперь мы определили главный поток, выполняемый благодаря Looper, мы также видели, что Handler добавляет сообщения в цикл Looper.loops() и обрабатывает сообщения. Давайте посмотрим, как они вместе вызывают нашу Activity.

4. Запуск MainActivity (Process 10 to 15)


Важно помнить, что этот бесконечный цикл и обработка сообщений выполнялись в main() методе класса ActivityThread, потому что именно там они были вызваны (см. в коде строки с 12 по 17). Мы поверхностно просмотрели Loopers, MessageQueues и Handlers, чтобы вникнуть в контекст. Итак, давайте вернемся к классу ActivityThread, в частности, к внутреннему классу H, о котором мы говорили ранее, который действует как основной Handler главного потока.

Итак, у нас есть Looper, передающий сообщения нашему Handler'у, давайте узнаем, как эти сообщения обрабатываются. Это делается внутри класса H. Этот класс содержит метод handleMessage(Message msg). Помните, что все классы, которые наследуются от Handler, должны переопределить этот метод.

/**
* Retrieved from AOSP
* H class embedded in the ActivityThread class
*/
private class H extends Handler {
    //Several Application State Identifiers ...
        public void handleMessage(Message msg) {
                //other code
                switch (msg.what) {
                    case LAUNCH_ACTIVITY: {
                        //create Activity records
                        handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                        ...
                        //handle other cases e.g ResumeActivity, PauseActivity, BindService, UnbindService etc.
                    }
                }
    }
}

Рисунок 4: Приватный внутренний класс H и его handleMessage() метод

Как видно в коде, в 8й строке есть оператор switch, в котором определяется обработка входящего сообщения по его содержимому.

Один из случаев (cases) включает в себя запуск активности (строка 11), что интересно, так это то, что этот метод предназначен для обработки около 50 случаев, которые варьируются от возобновления, приостановки, запуска Activity, привязки Service'ов, обработки Receiver'ов, предоставления предупреждений lowMemory или trimMemory, когда память устройства заполняется и т. д.

В case LAUNCH_ACTIVITY вызывается метод handleLaunchActivity(), как показано в строке 13, см Process11 на схеме. Затем этот метод вызывает другой метод, называемый performLaunchActivity(), который возвращает объект Activity (см. Рис. 5, строка 7).

/**
* Retrieved from AOSP.
* ActivityThread class
*/
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        //... initialize graphics,do some logging, call GC if need be, etc
        Activity a = performLaunchActivity(r, customIntent);
        //... handle how to resume an existing activity
}

Рисунок 5: Метод handleLaunchActivity() в котором создается Activity

Метод performLaunchActivity() добавляет в Activity важную информацию, такую как Instrumentation, Context, Component, а также Intent; а также задает Application. Затем этот метод вызывает Instrumentation.callActivityOnCreate() (Process 13), который является последним этапом перед вызовом метода onCreate() в Activity (Process 14-15, см. Рисунок 5 (код), строки 8-10).

/**
 * @Retrieved from AOSP
 * Instrumentation class
 */
public void callActivityOnCreate(Activity activity, Bundle icicle) {
    // Заметьте что Activity уже создана в prepareLaunchActivity().
    // все что нужно жто вызвать onCreate()
    prePerformCreate(activity); // подготовка Activity
    activity.performCreate(icicle); // вызов onCreate()
    postPerformCreate(activity);
}

Рисунок 6: Класс Instrumentation наконец запускает Activity

На данный момент ваша Activity загружена c множеством полезных переменных и методов, которые можно использовать для создания вашего нового удивительного приложения для Android! Все это благодаря ActivityThread, умной работе Handler'a и Looper'a, и огромному классу Activity в 7600 строк кода, который позволяет аттачить фрагменты, получить контекст и легко управлять View's — и много еще чего.

Примерно так наша Activity и создается!

Оригинал статьи здесь.

Комментарии (6)


  1. Revertis
    20.12.2017 14:18

    Пропущен такой важный аспект приложения, как Application.onCreate().
    Он выполняется перед запуском активитей, ресиверов и так далее.


    1. shevartsoft Автор
      20.12.2017 14:56

      Согласен! Думаю что это происходит в том же ActivityThread классе — раз там находится метод main().


    1. Tishka17
      20.12.2017 23:42

      Но после контент провайдеров, кстати.


  1. SiliconValleyHobo
    20.12.2017 14:57

    Немножко позужу:
    На самом деле, что происходит после форка в java-мире далеко не так интересно. Вся мякотка происходит как раз до, а «магия» андроида — в момент форка. Ну, вернее, вся магия и заключается в форке, отборе прав доступа и преобразовании зиготы в приложение.
    Кому интересно — можно начинать разбираться с platform/frameworks/base/core/jni/com_android_internal_os_Zygote.cpp, nativeForkAndSpecialize(). Посмотрите, кто вызывает, и что происходит сразу после вызова. Там все очень лампово.


    1. shevartsoft Автор
      20.12.2017 14:58

      Согласен — но и там еще не «дно» абстракции — так что в этой статье обзор уровня повыше, а в nativeForkAndSpecialize() более низкий уровень.


  1. andrikeev
    20.12.2017 22:22

    Поправьте, пожалуйста, код — нету номеров строк, а в тексте ссылки с номерами