Русский
Русский
English
Статистика
Реклама

Перевод Холодный запуск приложения

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


Общая схема


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-приложения. Оставайтесь с нами!
Источник: habr.com
К списку статей
Опубликовано: 01.10.2020 10:13:11
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Разработка под android

Android development

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru