«Холодный» запуск Android-приложения

от автора

Всем приветъ! Давно ничего не писал=)
Данный пост является кастомизированным переводом статьи с моими вставками =)
Это будет серия постов о процессе «холодного» запуска Android приложения, с момента нажатия на иконку и до создания процесса приложения.

image

Общая схема

image

Открывая «окно»…

Перед тем как запустить новый процесс приложения, system_server создает стартовое окно используя метод PhoneWindowManager.addSplashScreen():

public class PhoneWindowManager implements WindowManagerPolicy {    public StartingSurface addSplashScreen(...) {     ...     PhoneWindow win = new PhoneWindow(context);     win.setIsStartingWindow(true);     win.setType(TYPE_APPLICATION_STARTING);     win.setTitle(label);     win.setDefaultIcon(icon);     win.setDefaultLogo(logo);     win.setLayout(MATCH_PARENT, MATCH_PARENT);      addSplashscreenContent(win, context);      WindowManager wm = (WindowManager) context.getSystemService(       WINDOW_SERVICE     );     View view = win.getDecorView();     wm.addView(view, params);     ...   }    private void addSplashscreenContent(PhoneWindow win,       Context ctx) {     TypedArray a = ctx.obtainStyledAttributes(R.styleable.Window);     int resId = a.getResourceId(       R.styleable.Window_windowSplashscreenContent,       0     );     a.recycle();     Drawable drawable = ctx.getDrawable(resId);     View v = new View(ctx);     v.setBackground(drawable);     win.setContentView(v);   } }

Стартовое окно это то, что пользователь будет видеть пока запускается само приложение. Окно будет отображаться до тех пор пока не будет запущена Activity и не будет отрисован первый кадр. То есть пока не будет завершен «холодный» запуск. Пользователь может видеть данное окно длительное время, поэтому постарайтесь сделать его приятным =).
image

Содержимое стартового окна берется из drawable-ресурсов windowSplashscreenContent и windowBackground запускаемого Activity. Банальный пример такого окна:

image

Если пользователь восстанавливает Activity из режима последнего экрана(Recent screen), при этом на нажимая на иконку приложения, то system_server вызывает метод TaskSnapshotSurface.create(), чтобы создать стартовое окно из уже сделанного скриншота.

Как только стартовое окно показано пользователю, system_server готов запустить процесс приложения и вызывает метод ZygoteProcess.startViaZygote():

public class ZygoteProcess {   private Process.ProcessStartResult startViaZygote(...) {     ArrayList<String> argsForZygote = new ArrayList<>();     argsForZygote.add("--runtime-args");     argsForZygote.add("--setuid=" + uid);     argsForZygote.add("--setgid=" + gid);     argsForZygote.add("--runtime-flags=" + runtimeFlags);     ...     return zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi),                                           zygotePolicyFlags,                                           argsForZygote);   } }

В коде видно, что метод ZygoteProcess.zygoteSendArgsAndGetResult() отправляет аргументы запуска через сокет Zygote-процессу.

«Разделение» Zygote-ы

Согласно документации Android-а об управлении памятью следует:

Каждый процесс приложения запускается с помощью форкания(разделения) от существующего Zygote-процесса…

Вкратце об этом я писал в предыдущей статье про запуск Android-а. А теперь давайте посмотрим поглубже на происходящие процессы.

Когда система загружается процесс Zygote стартует и выполняет метод ZygoteInit.main():

public class ZygoteInit {    public static void main(String argv[]) {     ...     if (!enableLazyPreload) {         preload(bootTimingsTraceLog);     }     // The select loop returns early in the child process after     // a fork and loops forever in the zygote.     caller = zygoteServer.runSelectLoop(abiList);     // We're in the child process and have exited the     // select loop. Proceed to execute the command.     if (caller != null) {       caller.run();     }   }    static void preload(TimingsTraceLog bootTimingsTraceLog) {     preloadClasses();     cacheNonBootClasspathClassLoaders();     preloadResources();     nativePreloadAppProcessHALs();     maybePreloadGraphicsDriver();     preloadSharedLibraries();     preloadTextResources();     WebViewFactory.prepareWebViewInZygote();     warmUpJcaProviders();   } }

Как вы видите метод ZygoteInit.main() делает 2 важные вещи:

  • Подгружает все необходимые системные библиотеки и ресурсы Android-фреймворка. Подобная предзагрузка не только экономит память но еще и экономит время запуска приложений.
  • Далее он запускает метод ZygoteServer.runSelectLoop(), который в свою очередь запускает сокет и начинает слушать вызовы данного сокета.

Когда же на сокет приходит команда на форкинг процесса, метод ZygoteConnection.processOneCommand() обрабатывает аргументы используя метод ZygoteArguments.parseArgs() и запускает метод Zygote.forkAndSpecialize():

public final class Zygote {    public static int forkAndSpecialize(...) {     ZygoteHooks.preFork();      int pid = nativeForkAndSpecialize(...);      // Set the Java Language thread priority to the default value.     Thread.currentThread().setPriority(Thread.NORM_PRIORITY);      ZygoteHooks.postForkCommon();     return pid;   } } 

image

На заметку: Начиная с Android 10 есть оптимизационная фича под названием Unspecialized App Process, которая имеет пул не специализированных Zygote-процессов, для еще более быстрого запуска приложений.

Приложение запустилось!

После форка дочерний процесс запускает метод RuntimeInit.commonInit(), который устанавливает дефолтный UncaughtExceptionHandler. Далее, процесс запускает метод ActivityThread.main():

public final class ActivityThread {    public static void main(String[] args) {     Looper.prepareMainLooper();      ActivityThread thread = new ActivityThread();     thread.attach(false, startSeq);      Looper.loop();   }    final ApplicationThread mAppThread = new ApplicationThread();    private void attach(boolean system, long startSeq) {     if (!system) {       IActivityManager mgr = ActivityManager.getService();       mgr.attachApplication(mAppThread, startSeq);     }   } }

Тут происходят две интересные вещи:

  • Метод ActivityThread.main() создает новый поток(Thread) и вызывает метод Looper.loop(), в котором будет запущен новый инстанс Looper-а. Он будет привязан к новому потоку(который становится MainThread-ом aka UiThread) и будет работать(теоретически) бесконечно. Looper привязавшись, будет ожидать сообщений для того чтобы поместить их к своему MessageQueue.
  • Далее, метод ActivityThread.attach() делает IPC-запрос к методу ActivityManagerService.attachApplication() system_server-а, тем самым давая понять, что MainThread нашего приложения запущен и готов к работе.

image

Контроль над приложением

В процессе system_server метод ActivityManagerService.attachApplication() вызывает метод ActivityManagerService.attachApplicationLocked(), который завершает настройку запускаемого приложения:

public class ActivityManagerService extends IActivityManager.Stub {    private boolean attachApplicationLocked(       IApplicationThread thread, int pid, int callingUid,       long startSeq) {     thread.bindApplication(...);      // See if the top visible activity is waiting to run     //  in this process...     mAtmInternal.attachApplication(...);      // Find any services that should be running in this process...     mServices.attachApplicationLocked(app, processName);      // Check if a next-broadcast receiver is in this process...     if (isPendingBroadcastProcessLocked(pid)) {         sendPendingBroadcastsLocked(app);     }     return true;   } }

Парочка ключевых выводов:

  • Процесс system_server делает IPC-запрос к методу ActivityThread.bindApplication() в процессе нашего приложения, который направляет запрос к методу ActivityThread.handleBindApplication() в MainThread-е приложения.
  • Сразу после этого, system_server планирует запуск Pending Activity, Service и BroadcastReciever-ов нашего приложения.
  • Метод ActivityThread.handleBindApplication() загружает APK-файл и компоненты приложения.
  • Разработчики имеют возможность немного повлиять на процессы перед запуском метода ActivityThread.handleBindApplication(), так что именно здесь должен начаться мониторинг холодного запуска приложения.

image

Давайте немного подробно разберем 3-ий пункт и узнаем что и как происходит при загрузке компонентов и ресурсов приложения. Порядок шагов такой:

  • Загрузка и создание инстанса класса AppComponentFactory.
  • Вызов метода AppComponentFactory.instantiateClassLoader().
  • Вызов метода AppComponentFactory.instantiateApplication() для загрузки и создания инстанса класса Application.
  • Для каждого объявленного ContentProvider-а, в порядке приоритета, вызов метода AppComponentFactory.instantiateProvider() для загрузки его класса и создания инстанса, после вызов метода ContentProvider.onCreate().
  • И наконец, вызов метода Application.onCreate().

Эпилог

Мы начали изучать «холодную» загрузку с очень обще-абстрагированного уровня:
image

Теперь мы знаем, что происходит «под капотом»:
image

Ну что же, это был длинный пост =) Но это не все! В следующих постах мы продолжим глубокое погружение в процесс запуска Android-приложения. Оставайтесь с нами!

ссылка на оригинал статьи https://habr.com/ru/post/521522/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *