Данный пост является кастомизированным переводом статьи с моими вставками =)
Это будет серия постов о процессе холодного запуска Android приложения, с момента нажатия на иконку и до создания процесса приложения.
Общая схема
Открывая окно
Перед тем как запустить новый процесс приложения, 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 и не будет отрисован первый кадр. То есть пока не будет завершен холодный запуск. Пользователь может видеть данное окно длительное время, поэтому постарайтесь сделать его приятным =).
Содержимое стартового окна берется из drawable-ресурсов windowSplashscreenContent и windowBackground запускаемого Activity. Банальный пример такого окна:
Если пользователь восстанавливает 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; }}
На заметку: Начиная с 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 нашего приложения запущен и готов к работе.
Контроль над приложением
В процессе 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(), так что именно здесь должен начаться мониторинг холодного запуска приложения.
Давайте немного подробно разберем 3-ий пункт и узнаем что и как происходит при загрузке компонентов и ресурсов приложения. Порядок шагов такой:
- Загрузка и создание инстанса класса AppComponentFactory.
- Вызов метода AppComponentFactory.instantiateClassLoader().
- Вызов метода AppComponentFactory.instantiateApplication() для загрузки и создания инстанса класса Application.
- Для каждого объявленного ContentProvider-а, в порядке приоритета, вызов метода AppComponentFactory.instantiateProvider() для загрузки его класса и создания инстанса, после вызов метода ContentProvider.onCreate().
- И наконец, вызов метода Application.onCreate().
Эпилог
Мы начали изучать холодную загрузку с очень обще-абстрагированного уровня:
Теперь мы знаем, что происходит под капотом:
Ну что же, это был длинный пост =) Но это не все! В следующих постах мы продолжим глубокое погружение в процесс запуска Android-приложения. Оставайтесь с нами!