Как запустить activity на андроид
Как Android запускает MainActivity
Недавно я провел исследование о main() методе в Java и то, как он служит точкой входа для любого приложения Java. Это заставило меня задуматься, а как насчет Android-приложений? Есть ли у них основной метод? Как они загружаются? Что происходит за кулисами до выполнения onCreate()? Майкл Бэйли очень подробно рассказал о том, как работает Main Thread, так что это быстрый обзор его доклада плюс дополнительная информация из Android Open Source Project (AOSP).
В этой статье мы рассмотрим:
- Что происходит от нажатия на иконку приложения до запуска MainActivity
- Найдем основной метод приложения и узнаем, как основной поток (он же UI, он же Main Thread) получает свое назначение.
- Рассмотрим роль, которую играют Looper & Handler в передаче сообщений, которые в конечном итоге приводят к созданию вашей Activity.
Что происходит при запуске приложения
1 Схема запуска приложения
Между вызовом метода main() и onCreate() в нашем MainActivity примерно 15 шагов, и в этой статье мы пройдем по ним. На рисунке 1 изображена общая схема запуска приложения, показывающая различные классы взаимодействия сверху и соответствующую цепочку методов. Шаги пронумерованы, и когда я обращаюсь к ним, я буду использовать следующие обозначения Process3 или Process14
Рисунок 1: Схема запуска приложения по шагам от вызова main() до onCreate() в MainActivity
2. Класс ActivityThread
В классе ActivityThread чуть более 6500 строк. Для краткости я определил самые важные для нас части. Давайте рассмотрим, что делает этот класс и связанный с ним основной метод, чтобы запустить нашу Activity
Рисунок 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)
Рисунок 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, должны переопределить этот метод.
Рисунок 4: Приватный внутренний класс H и его handleMessage() метод
Как видно в коде, в 8й строке есть оператор switch, в котором определяется обработка входящего сообщения по его содержимому.
Один из случаев (cases) включает в себя запуск активности (строка 11), что интересно, так это то, что этот метод предназначен для обработки около 50 случаев, которые варьируются от возобновления, приостановки, запуска Activity, привязки Service’ов, обработки Receiver’ов, предоставления предупреждений lowMemory или trimMemory, когда память устройства заполняется и т. д.
В case LAUNCH_ACTIVITY вызывается метод handleLaunchActivity(), как показано в строке 13, см Process11 на схеме. Затем этот метод вызывает другой метод, называемый performLaunchActivity(), который возвращает объект Activity (см. Рис. 5, строка 7).
Рисунок 5: Метод handleLaunchActivity() в котором создается Activity
Метод performLaunchActivity() добавляет в Activity важную информацию, такую как Instrumentation, Context, Component, а также Intent; а также задает Application. Затем этот метод вызывает Instrumentation.callActivityOnCreate() (Process 13), который является последним этапом перед вызовом метода onCreate() в Activity (Process 14-15, см. Рисунок 5 (код), строки 8-10).
Рисунок 6: Класс Instrumentation наконец запускает Activity
На данный момент ваша Activity загружена c множеством полезных переменных и методов, которые можно использовать для создания вашего нового удивительного приложения для Android! Все это благодаря ActivityThread, умной работе Handler’a и Looper’a, и огромному классу Activity в 7600 строк кода, который позволяет аттачить фрагменты, получить контекст и легко управлять View’s — и много еще чего.
Как запустить activity на андроид
Краткое описание:
Запуск активити (activity) приложений.
Описание из Google Play:
Это Android приложение запускает скрытые действия для установленных приложений и создает ярлыки для них на домашнем экране.
Требуется Android: 4.4 и выше
Русский интерфейс: Да (с версии 1.14.4 русская локализация разработчиком была удалена)
Версия: 1.9.2 GP (Katafrakt)
Версия: 1.6.1 beta Activity Launcher (Пост HRistose #61548508)
Версия: 1.9.1 GP (Katafrakt)
Версия: 1.9.0 GP (Katafrakt)
Версия: 1.7 Сообщение №42, автор gar_alex
Версия: 1.5.1 Activity Launcher_1.5.1.apk ( 277.43 КБ )
Сообщение отредактировал Chudmin — 20.06.22, 13:01
Полезная вещь, так и можно инженерное меню открыть не зная USSD-команды 🙂
Сообщение отредактировал VadimW — 12.03.17, 02:24
Офигеть. Как я раньше мимо нее прошел. Есть аналоги конечно, там можно и свою иконку на ярлык поставить, но тут есть доступ к некоторым настройкам, а там нет.
Сообщение отредактировал Solidol790 — 13.03.17, 16:48
Мне Activity Launcher больше понравился за счёт того, что для запуска активити нужно сделать один тык, а не два. При переборе скрытых системных активити, когда многие их них не запускаются, это очень экономит время.
Сообщение отредактировал Dimo4ka_2012 — 15.03.17, 12:53
Товарищи, хочу запускать активити окна создания заметки Google Keep с помощью 1 или 2-х касаний по экрану для быстрого создания заметки. Подскажите софт наподобие плавающей кнопки, в которую можно запихнуть этот ярлык. Желательно без смены лаунчера.
В идеале назначить действие на кнопку действия (Дом, назад, меню — долгое нажатие).
Сообщение отредактировал Самурайкай — 15.03.17, 18:30
Отрисовка первого кадра Android-приложения
Всем приветЪ! Этот пост является продолжением поста про глубокое погружение в процесс загрузки-запуска Android-приложения. Сегодня мы пойдем чуть дальше и обсудим момент когда главная Activity приложения запущена и система должна отрисовать первый кадр. Прошу под кат.
Следуя официальной документации запущенный процесс приложения отвечает за выполнение следующих шагов:
- Создание объекта класса Application.
- Запуск основного потока(MainThread aka UiThread).
- Создание стартового Activity, который указан в манифесте.
- Расширение(раздутие, inflating) вьюшек. То есть создание вьюшек, которые прописаны в xml-файле.
- Планировка размеров(View.measure()) и размещения(View.layout()) вьюшек на экране.
- Выполнение начальной отрисовки.
После того как был отрисован первый кадр, системный процесс заменяет отображаемое фоновое окно, заменяя его на Activity приложения. Теперь пользователь может взаимодействовать с приложением.
А теперь давайте поподробнее обо всех шагах.
Старт главного потока
В предыдущем посте мы узнали:
- Когда запускается процесс приложения, он вызывает метод ActivityThread.main(), который делает блокирующий IPC-запрос к методу ActivityManagerService.attachApplication() в процессе system_server.
- system_server делает IPC-вызов в процессе приложения метода ActivityThread.bindApplication(), который ставит в очередь сообщение BIND_APPLICATION в MessageQueue главного потока.
- Когда IPC-вызов метода ActivityManagerService.attachApplication() завершен, ActivityThread.main() вызывает Looper.loop(), который будет зациклен навсегда(пока приложение работает) и будет обрабатывать сообщения поступающие в MessageQueue.
- Первое сообщение, которое будет обработано это BIND_APPLICATION. В этот момент будет вызван метод ActivityThread.handleBindApplication(), который загрузит APK и другие компоненты приложения.
Важный момент: ничего не происходит в главном потоке процесса приложения пока не выполнится IPC-вызов метода ActivityManagerService.attachApplication().
Планируем запуск Activity
Давайте посмотрим что происходит в процессе system_server после вызова метода ActivityThread.bindApplication():
Строка которая релевантна запуску Activity — mAtmInternal.attachApplication(. ). Метод вызывает ActivityTaskManagerService.attachApplication(), который в свою очередь вызывает метод RootActivityContainer.attachApplication():
Код делает следующее:
- Обходит каждый дисплей.
- Получает стек сфокусированных Activity для этого дисплея.
- Проходит по каждому Activity целевого стека Activity.
- Если Activity принадлежит к запущенному процессу, то вызывается метод ActivityStackSupervisor.realStartActivityLocked(). Обратите внимание, что параметр andResume будет иметь значение true если Activity находится на вершине стэка.
Вот как выглядит метод ActivityStackSupervisor.realStartActivityLocked():
Все вызовы методов, которые мы просмотрели происходят в системном процессе system_server. Метод ClientLifecycleManager.scheduleTransaction() делает IPC-вызов ActivityThread.scheduleTransaction() в процессе приложения, который вызывает ClientTransactionHandler.scheduleTransaction(), чтобы положить в очередь сообщение EXECUTE_TRANSACTION:
При обработке сообщения EXECUTE_TRANSACTION происходит вызов метода TransactionExecutor.execute().
Теперь можно обновить диаграмму:
Фактический запуск Activity
Метод TransactionExecutor.execute() вызывает TransactionExecutor.
performLifecycleSequence(), который в свою очередь делает коллбэк в ActivityThread для создания(create), запуска(start) и продолжения(resume) Activity:
Первый кадр
Давайте взглянем на последовательность вызовов методов, которые ведут к отрисовке первого кадра:
- ActivityThread.handleResumeActivity()
- WindowManagerImpl.addView()
- WindowManagerGlobal.addView()
- ViewRootImpl.setView()
- ViewRootImpl.requestLayout()
- ViewRootImpl.scheduleTraversals()
- Choreographer.postCallback()
- Choreographer.scheduleFrameLocked()
Метод Choreographer.scheduleFrameLocked() ставит в очередь сообщение MSG_DO_FRAME:
При обработке сообщения MSG_DO_FRAME происходит вызов метода Choreographer.doFrame(), который в свою очередь вызывает ViewRootImpl.doTraversal(), который осуществляет проходы measure pass и layout pass, и наконец проход the first draw pass по иерархии вьюшек:
Заключение
Мы начали с высокого уровня понимания того, что происходит, когда система создает процесс приложения:
Теперь мы знаем что именно происходит «под капотом»:
А теперь давайте соединим диаграммы из предыдущего поста, с того момента когда пользователь тапает на иконку приложения до момента отрисовки первого кадра:
Теперь, когда у нас есть полная картина, мы можем начать разбираться в том, как правильно контролировать холодный запуск. Следующий пост будет именно об этом! До встречи.