ستكون هذه سلسلة من المنشورات حول عملية التشغيل البارد لتطبيق 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);
}
}
نافذة البداية هي ما سيراه المستخدم أثناء تشغيل التطبيق. سيتم عرض النافذة حتى يتم بدء النشاط ويتم رسم الإطار الأول. أي حتى اكتمال البداية الباردة . يمكن للمستخدم رؤية هذه النافذة لفترة طويلة ، لذا حاول أن تجعلها ممتعة.
يتم أخذ محتوى نافذة البداية من نافذة الموارد القابلة للرسم ، ومحتوى الشاشة ، والنافذة ، وخلفية النشاط الذي تم إطلاقه . مثال تافه لمثل هذه النافذة:
إذا استعاد المستخدم النشاط من وضع الشاشة الأخير ، أثناء النقر على أيقونة التطبيق ، ثم 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 .
"انفصال" الزيجوت
وفقًا لوثائق 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 () تفعل شيئين مهمين:
- يقوم بتحميل جميع مكتبات النظام والموارد اللازمة لإطار عمل 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. رئيسي () :
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 بجدولة إطلاق النشاط المعلق والخدمة والإذاعة لتطبيقنا .
- طريقة ActivityThread. يُحمِّل handleBindApplication () ملف APK ومكونات التطبيق.
- للمطورين القدرة على التأثير بشكل طفيف على العمليات قبل تشغيل طريقة ActivityThread. handleBindApplication () ، لذلك هذا هو المكان الذي يجب أن تبدأ فيه مراقبة البدء البارد للتطبيق.
دعنا نلقي نظرة فاحصة على النقطة الثالثة ونكتشف ماذا وكيف يحدث عند تحميل مكونات وموارد التطبيق. ترتيب الخطوات كما يلي:
- تحميل فئة AppComponentFactory وإنشائها .
- استدعاء AppComponentFactory. InstantiateClassLoader () .
- استدعاء AppComponentFactory. InstantiateApplication () لتحميل فئة التطبيق وإنشائها .
- لكل ContentProvider المُعلن ، بترتيب الأسبقية ، استدعاء AppComponentFactory. InstantiateProvider () لتحميل فئته وإنشاء مثيل ، بعد استدعاء طريقة ContentProvider. عند الإنشاء () .
- أخيرًا ، استدعاء التطبيق. عند الإنشاء () .
الخاتمة
بدأنا في استكشاف التمهيد البارد من مستوى تجريدي عام للغاية:
الآن نحن نعرف ما يحدث تحت غطاء المحرك:
حسنًا ، كان هذا منشورًا طويلاً. لكن هذا ليس كل شيء! في المنشورات التالية ، سنواصل الغوص العميق في عملية إطلاق تطبيق Android. ابقى معنا!