[Разработка под Android] «Холодный» запуск приложения (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Всем приветъ! Давно ничего не писал=)
Данный пост является кастомизированным переводом статьи с моими вставками =)
Это будет серия постов о процессе «холодного» запуска 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-приложения. Оставайтесь с нами!
===========
Источник:
habr.com
===========
===========
Автор оригинала: Roman Guy
===========Похожие новости:
- [Разработка мобильных приложений, Разработка под Android, Kotlin, Голосовые интерфейсы] Голос в мобильном приложении: учимся вызывать экраны и заполнять формы без рук
- [Разработка мобильных приложений, Разработка под Android] Еще раз про многомодульность Android-приложений
- [Разработка мобильных приложений, Разработка под Android, Смартфоны, Софт, Социальные сети и сообщества] В бете Telegram появились комментарии к постам
- [IPTV, Видеоконференцсвязь, Разработка под Linux, Разработка под Android, Производство и разработка электроники] Как разработать аналог Zoom для ТВ-приставок на RDK и Linux. Разбираемся с фреймворком GStreamer
- [Разработка под Android] Бесшовные A/B-обновления в Android: как они устроены
- [IT-компании, Копирайт, Разработка мобильных приложений, Разработка под Android] Со следующего года все приложения Google Play будут платить комиссию 30 %, включая Netflix и Spotify
- [Flutter, Конференции, Разработка мобильных приложений, Разработка под Android, Разработка под iOS] Нативная разработка vs кроссплатформенная – обсуждаем 30 сентября с владельцами приложений
- [Разработка под Android] Google Play In-App Review API: пошаговое руководство по внедрению
- [Разработка мобильных приложений, Разработка под Android, Разработка под e-commerce, Аналитика мобильных приложений] Как устроен Push Kit от Huawei
- [Разработка мобильных приложений, Разработка под Android, Kotlin] Retrofit2 на Android используя Kotlin
Теги для поиска: #_razrabotka_pod_android (Разработка под Android), #_android_development, #_razrabotka_pod_android (
Разработка под Android
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:56
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Всем приветъ! Давно ничего не писал=) Данный пост является кастомизированным переводом статьи с моими вставками =) Это будет серия постов о процессе «холодного» запуска 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 важные вещи:
Когда же на сокет приходит команда на форкинг процесса, метод 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); } } } Тут происходят две интересные вещи:
Контроль над приложением В процессе 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; } } Парочка ключевых выводов:
Давайте немного подробно разберем 3-ий пункт и узнаем что и как происходит при загрузке компонентов и ресурсов приложения. Порядок шагов такой:
Эпилог Мы начали изучать «холодную» загрузку с очень обще-абстрагированного уровня: Теперь мы знаем, что происходит «под капотом»: Ну что же, это был длинный пост =) Но это не все! В следующих постах мы продолжим глубокое погружение в процесс запуска Android-приложения. Оставайтесь с нами! =========== Источник: habr.com =========== =========== Автор оригинала: Roman Guy ===========Похожие новости:
Разработка под Android ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 19:56
Часовой пояс: UTC + 5