Избранное сообщение

Фетісов В. С. Комп’ютерні технології в тестуванні. Навчально-методичний посібник. 2-ге видання, перероблене та доповнене / Мои публикации

В 10-х годах я принимал участие в программе Европейского Союза Tempus "Освітні вимірювання, адаптовані до стандартів ЄС". В рамк...

Благодаря Интернету количество писателей и поэтов увеличивается в геометрической прогрессии. Поголовье читателей начинает заметно отставать.

четверг, 21 декабря 2017 г.

Как Android запускает MainActivity / Android. Разработка приложений

Недавно я провел исследование о 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


public static void main(String[] args) {
  
  
        Looper.prepareMainLooper();
        ActivityThread thread = new ActivityThread();
        thread.attach(false);
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        
        
        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)



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;
 
 for (;;) {
  Message msg = queue.next(); 
        if (msg == null) {
            
            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, должны переопределить этот метод.


private class H extends Handler {
    
        public void handleMessage(Message msg) {
                
                switch (msg.what) {
                    case LAUNCH_ACTIVITY: {
                        
                        handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                        ...
                        
                    }
                }
    }
}

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

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

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

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


private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
        
        Activity a = performLaunchActivity(r, customIntent);
        
}

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

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


public void callActivityOnCreate(Activity activity, Bundle icicle) {
    
    
    prePerformCreate(activity); 
    activity.performCreate(icicle); 
    postPerformCreate(activity);
}

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

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

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

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

Комментариев нет:

Отправить комментарий