主頁 > 移動端開發 > 女兒拿著小天才電話手表問我App啟動流程

女兒拿著小天才電話手表問我App啟動流程

2020-10-15 17:14:30 移動端開發

前言

首先,new一個女兒,

var mDdaughter = new 女兒("6歲",“漂亮可愛”,“健康乖巧”,“最喜歡玩小天才電話手表和她的爸爸”)

好了,女兒有了,有一天,女兒問我:

“爸爸爸爸,你說我玩的這個小天才電話手表怎么這么厲害,隨便點一下這個小圖片,這個應用就冒出來了,就可以聽兒歌了,好神奇啊,”

我心里一驚:
image

小天才電話手表的系統就是Android,所以這不就是,,面試官常考的應用啟動流程嘛!
女兒也要來面試我了嗎!??
好了,既然女兒問了,那就答吧,
但是,對付這個小小的0經驗面試官,我該咋說呢?

解答小小面試官

女兒,你可以把手表里面想象成一個幼兒園,里面有一個老師,一個班長,一個班干部,以及一大堆小朋友,

  • 一個老師:Z老師(Zygote行程)

  • 一個班長:小A(ActivityManagerService)

  • 一個班干部:小L(Launcher桌面應用)

  • 一大堆小朋友:所有應用,包括音樂小朋友,聊天小朋友,日歷小朋友等等,

image

應用啟動程序就像一個小朋友被叫醒一樣,開機之后呢,Z老師會依次叫醒班長和班干部(SystemServer#ActivityManagerService,Launcher),小L醒了之后就會去了解手表里有哪些小朋友,長什么樣(icon,name),家庭資訊(包名,androidmanifest)等等,然后一個個把小朋友的照片(icon)貼到自己的身上,比如有音樂小朋友,聊天小朋友,日歷小朋友,其實也就是你手表上這個桌面啦,

這時候你要點開一個音樂小朋友呢(startActivity),小L就會通知班長小A(Binder),小A知道了之后,讓小L自己休息下(Paused),然后就去找Z老師了,Z老師就負責叫音樂小朋友起床了(fork行程,啟動ActivityThread),音樂小朋友起來后就又找小A帶她去洗臉刷牙(啟動ApplicationThread,Activity),都弄完了就可以進行各種表演了,唱歌啊,跳舞啊,

不是很明白啊?我們一起聊個天你就懂了,假如我是Launcher

image

image

女兒似懂非懂的給我點了一個贊??,爸爸你真棒,

十五年后

mDdaughter.grow(15)
mDdaughter.study("Android")

過了十五年,女兒已經21歲了,正在學習Android,考慮要不要女從父業,

這天,她一臉疑惑的來找我:
“爸,這個app啟動到底是怎么個流程啊,我看了好久還是不大明白,要不你再跟我詳細講一遍吧?”
“好嘞,別擔心,我這次詳細跟你說說”

解答Android程式媛

還記得我小時候跟你說過的故事嗎,Android系統就像一個幼兒園,有一個大朋友叫Launcher,身上會貼很多其他小朋友的名片,這個Launcher就是我們的桌面了,它通過PackageManagerService獲知了系統里所有應用的資訊,并展示了出來,當然它本身也是一個應用,

通過點擊一個應用圖示,也就是觸發了點擊事件,最后會執行到startActivity方法,這里也就和啟動Activity步驟重合上了,

那么這個startActivity干了啥?是怎么通過重重關卡喚醒這個應用的?

首先,介紹下系統中那些重要的成員,他們在app啟動流程中都擔任了重要的角色.

系統成員介紹

  • init行程,Android系統啟動后,Zygote并不是第一個行程,而是linux的根行程init行程,然后init行程才會啟動Zygote行程,
  • Zygote行程,所有android行程的父行程,當然也包括SystemServer行程
  • SystemServer行程,正如名字一樣,系統服務行程,負責系統中大大小小的事物,為此也是啟動了三員大將(ActivityManagerService,PackageManagerService,WindowManagerService)以及binder執行緒池,
  • ActivityManagerService,主要負責系統中四大組件的啟動、切換、調度及應用行程的管理和調度等作業,對于一些行程的啟動,都會通過Binder通信機制傳遞給AMS,再處理給Zygote,
  • PackageManagerService,主要負責應用包的一些操作,比如安裝,卸載,決議AndroidManifest.xml,掃描檔案資訊等等,
  • WindowManagerService,主要負責視窗相關的一些服務,比如視窗的啟動,添加,洗掉等,
  • Launcher,桌面應用,也是屬于應用,也有自己的Activity,一開機就會默認啟動,通過設定Intent.CATEGORY_HOME的Category隱式啟動,

搞清楚這些成員,就跟隨我一起看看怎么過五關斬六將,最終啟動了一個App,

第一關:跨行程通信,告訴系統我的需求

首先,要告訴系統,我Launcher要啟動一個應用了,呼叫Activity.startActivityForResult方法,最侄訓轉到mInstrumentation.execStartActivity方法,
由于Launcher自己處在一個單獨的行程,所以它需要跨行程告訴系統服務我要啟動App的需求,
找到要通知的Service,名叫ActivityTaskManagerService,然后使用AIDL,通過Binder與他進行通信,

這里的簡單說下ActivityTaskManagerService(簡稱ATMS),原來這些通信作業都是屬于ActivityManagerService,現在分了一部分作業給到ATMS,主要包括四大組件的調度作業,也是由SystemServer行程直接啟動的,相關原始碼可見ActivityManagerService.Lifecycle.startService方法,感興趣朋友可以自己看看,

接著說跨行程通信,相關代碼如下:

    //Instrumentation.java
    int result = ActivityTaskManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);


    //ActivityTaskManager.java            
    public static IActivityTaskManager getService() {
        return IActivityTaskManagerSingleton.get();
    }
    private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
            new Singleton<IActivityTaskManager>() {
                @Override
                protected IActivityTaskManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
                    return IActivityTaskManager.Stub.asInterface(b);
                }
            };



    //ActivityTaskManagerService.java
    public class ActivityTaskManagerService extends IActivityTaskManager.Stub
    
    public static final class Lifecycle extends SystemService {
        private final ActivityTaskManagerService mService;

        public Lifecycle(Context context) {
            super(context);
            mService = new ActivityTaskManagerService(context);
        }

        @Override
        public void onStart() {
            publishBinderService(Context.ACTIVITY_TASK_SERVICE, mService);
            mService.start();
        }
    }        

startActivity我們都很熟悉,平時啟動Activity都會使用,啟動應用也是從這個方法開始的,也會同樣帶上intent資訊,表示要啟動的是哪個Activity,

另外要注意的一點是,startActivity之后有個checkStartActivityResult方法,這個方法是用作檢查啟動Activity的結果,當啟動Activity失敗的時候,就會通過這個方法拋出例外,比如有我們常見的問題:未在AndroidManifest.xml注冊,

   public static void checkStartActivityResult(int res, Object intent) {
        switch (res) {
            case ActivityManager.START_INTENT_NOT_RESOLVED:
            case ActivityManager.START_CLASS_NOT_FOUND:
                if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
                    throw new ActivityNotFoundException(
                            "Unable to find explicit activity class "
                            + ((Intent)intent).getComponent().toShortString()
                            + "; have you declared this activity in your AndroidManifest.xml?");
                throw new ActivityNotFoundException(
                        "No Activity found to handle " + intent);
            case ActivityManager.START_PERMISSION_DENIED:
                throw new SecurityException("Not allowed to start activity "
                        + intent);
            case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
                throw new AndroidRuntimeException(
                        "FORWARD_RESULT_FLAG used while also requesting a result");
            case ActivityManager.START_NOT_ACTIVITY:
                throw new IllegalArgumentException(
                        "PendingIntent is not an activity");
            //...
        }
    }

第二關:通知Launcher可以休息了

ATMS收到要啟動的訊息后,就會通知上一個應用,也就是Launcher可以休息會了,進入Paused狀態,

    //ActivityStack.java

    private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
        //...
        ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
        //...
        boolean pausing = getDisplay().pauseBackStacks(userLeaving, next, false);
        if (mResumedActivity != null) {
            if (DEBUG_STATES) Slog.d(TAG_STATES,
                    "resumeTopActivityLocked: Pausing " + mResumedActivity);
            pausing |= startPausingLocked(userLeaving, false, next, false);
        }
        //...

        if (next.attachedToProcess()) {
            //應用已經啟動
            try {
                //...
                transaction.setLifecycleStateRequest(
                        ResumeActivityItem.obtain(next.app.getReportedProcState(),
                                getDisplay().mDisplayContent.isNextTransitionForward()));
                mService.getLifecycleManager().scheduleTransaction(transaction);
                //...
            } catch (Exception e) {
                //...
                mStackSupervisor.startSpecificActivityLocked(next, true, false);
                return true;
            }
            //...
            // From this point on, if something goes wrong there is no way
            // to recover the activity.
            try {
                next.completeResumeLocked();
            } catch (Exception e) {
                // If any exception gets thrown, toss away this
                // activity and try the next one.
                Slog.w(TAG, "Exception thrown during resume of " + next, e);
                requestFinishActivityLocked(next.appToken, Activity.RESULT_CANCELED, null,
                        "resume-exception", true);
                return true;
            }
        } else {
            //冷啟動流程
            mStackSupervisor.startSpecificActivityLocked(next, true, true);
        }        
    }

這里有兩個類沒有見過:

  • ActivityStack,是Activity的堆疊管理,相當于我們平時專案里面自己寫的Activity管理類,用于管理Activity的狀態啊,如堆疊出堆疊順序等等,
  • ActivityRecord,代表具體的某一個Activity,存放了該Activity的各種資訊,

startPausingLocked方法就是讓上一個應用,這里也就是Launcher進入Paused狀態,
然后就會判斷應用是否啟動,如果已經啟動了,就會走ResumeActivityItem的方法,看這個名字,結合應用已經啟動的前提,是不是已經猜到了它是干嗎的?沒錯,這個就是用來控制Activity的onResume生命周期方法的,不僅是onResume還有onStart方法,具體可見ActivityThread的handleResumeActivity方法原始碼,

如果應用沒啟動就會接著走到startSpecificActivityLocked方法,接著看,

第三關:是否已啟動行程,否則創建行程

Launcher進入Paused之后,ActivityTaskManagerService就會判斷要打開的這個應用行程是否已經啟動,如果已經啟動,則直接啟動Activity即可,這也就是應用內的啟動Activity流程,如果行程沒有啟動,則需要創建行程,

這里有兩個問題:

  • 怎么判斷應用行程是否存在呢?如果一個應用已經啟動了,會在ATMS里面保存一個WindowProcessController資訊,這個資訊包括processName和uid,uid則是應用程式的id,可以通過applicationInfo.uid獲取,processName則是行程名,一般為程式包名,所以判斷是否存在應用行程,則是根據processName和uid去判斷是否有對應的WindowProcessController,并且WindowProcessController里面的執行緒不為空,代碼如下:
    //ActivityStackSupervisor.java
    void startSpecificActivityLocked(ActivityRecord r, boolean andResume, boolean checkConfig) {
        // Is this activity's application already running?
        final WindowProcessController wpc =
                mService.getProcessController(r.processName, r.info.applicationInfo.uid);

        boolean knownToBeDead = false;
        if (wpc != null && wpc.hasThread()) {
            //應用行程存在
            try {
                realStartActivityLocked(r, wpc, andResume, checkConfig);
                return;
            } 
        }
    }

    //WindowProcessController.java
    IApplicationThread getThread() {
        return mThread;
    }

    boolean hasThread() {
        return mThread != null;
    }
  • 還有個問題就是怎么創建行程?還記得Z老師嗎?對,就是Zygote行程,之前說了他是所有行程的父行程,所以就要通知Zygote去fork一個新的行程,服務于這個應用,
    //ZygoteProcess.java
    private Process.ProcessStartResult attemptUsapSendArgsAndGetResult(
            ZygoteState zygoteState, String msgStr)
            throws ZygoteStartFailedEx, IOException {
        try (LocalSocket usapSessionSocket = zygoteState.getUsapSessionSocket()) {
            final BufferedWriter usapWriter =
                    new BufferedWriter(
                            new OutputStreamWriter(usapSessionSocket.getOutputStream()),
                            Zygote.SOCKET_BUFFER_SIZE);
            final DataInputStream usapReader =
                    new DataInputStream(usapSessionSocket.getInputStream());

            usapWriter.write(msgStr);
            usapWriter.flush();

            Process.ProcessStartResult result = new Process.ProcessStartResult();
            result.pid = usapReader.readInt();
            // USAPs can't be used to spawn processes that need wrappers.
            result.usingWrapper = false;

            if (result.pid >= 0) {
                return result;
            } else {
                throw new ZygoteStartFailedEx("USAP specialization failed");
            }
        }
    }

可以看到,這里其實是通過socket和Zygote進行通信,BufferedWriter用于讀取和接收訊息,這里將要新建行程的訊息傳遞給Zygote,由Zygote進行fork行程,并回傳新行程的pid,

可能又會有人問了?fork是啥?為啥這里又變成socket進行IPC通信,而不是Bindler了?

  • 首先,fork()是一個方法,是類Unix作業系統上創建行程的主要方法,用于創建子行程(等同于當前行程的副本),
  • 那為什么fork的時候不用Binder而用socket了呢?主要是因為fork不允許存在多執行緒,Binder通訊偏偏就是多執行緒,

問題總是在不斷產生,總有好奇的朋友會接著問,為什么fork不允許存在多執行緒?

  • 防止死鎖,其實你想想,多執行緒+多行程,聽起就不咋靠譜是不,假設多執行緒里面執行緒A對某個鎖lock,另外一個執行緒B呼叫fork創建了子行程,但是子行程卻沒有了執行緒A,但是鎖本身卻被fork了出來,那么這個鎖沒人可以打開了,一旦子行程中另外的執行緒又對這個鎖進行lock,就死鎖了,

第四關:ActivityThread閃亮登場

剛才說到由Zygote進行fork行程,并回傳新行程的pid,其實這程序中也實體化ActivityThread物件,一起看看是怎么實作的:

   //RuntimeInit.java
   protected static Runnable findStaticMain(String className, String[] argv,
            ClassLoader classLoader) {
        Class<?> cl;

        try {
            cl = Class.forName(className, true, classLoader);
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(
                    "Missing class when invoking static main " + className,
                    ex);
        }

        Method m;
        try {
            m = cl.getMethod("main", new Class[] { String[].class });
        } catch (NoSuchMethodException ex) {
            throw new RuntimeException(
                    "Missing static main on " + className, ex);
        } catch (SecurityException ex) {
            throw new RuntimeException(
                    "Problem getting static main on " + className, ex);
        }
        //...
        return new MethodAndArgsCaller(m, argv);
    }

原來是反射!通過反射呼叫了ActivityThread 的 main 方法,ActivityThread大家應該都很熟悉了,代表了Android的主執行緒,而main方法也是app的主入口,這不對上了!新建行程的時候就呼叫了,可不是主入口嘛,來看看這個主入口,

    public static void main(String[] args) {
        //...
        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        //...

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }
        //...
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

main方法主要創建了ActivityThread,創建了主執行緒的Looper物件,并開始loop回圈,除了這些,還要告訴AMS,我醒啦,行程創建好了!也就是上述代碼中的attach方法,最后會轉到AMSattachApplicationLocked方法,一起看看這個方法干了啥:

    //ActivitymanagerService.java
    private final boolean attachApplicationLocked(IApplicationThread thread,
            int pid, int callingUid, long startSeq) {
        //...
        ProcessRecord app;
        //...
        thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
                        null, null, null, testMode,
                        mBinderTransactionTrackingEnabled, enableTrackAllocation,
                        isRestrictedBackupMode || !normalMode, app.isPersistent(),
                        new Configuration(app.getWindowProcessController().getConfiguration()),
                        app.compat, getCommonServicesLocked(app.isolated),
                        mCoreSettingsObserver.getCoreSettingsLocked(),
                        buildSerial, autofillOptions, contentCaptureOptions);
        //...
        app.makeActive(thread, mProcessStats);

        //...
        // See if the top visible activity is waiting to run in this process...
        if (normalMode) {
            try {
                didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
            } catch (Exception e) {
                Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
                badApp = true;
            }
        }
        //...        
    }

    //ProcessRecord.java
    public void makeActive(IApplicationThread _thread, ProcessStatsService tracker) {
        //...
        thread = _thread;
        mWindowProcessController.setThread(thread);
    }

這里主要做了三件事:

  • bindApplication方法,主要用來啟動Application,
  • makeActive方法,設定WindowProcessController里面的執行緒,也就是上文中說過判斷行程是否存在所用到的,
  • attachApplication方法,啟動根Activity,

第五關:創建Application

接著上面看,按照我們所熟知的,應用啟動后,應該就是啟動Applicaiton,啟動Activity,看看是不是怎么回事:

    //ActivityThread#ApplicationThread
    public final void bindApplication(String processName, ApplicationInfo appInfo,
                List<ProviderInfo> providers, ComponentName instrumentationName,
                ProfilerInfo profilerInfo, Bundle instrumentationArgs,
                IInstrumentationWatcher instrumentationWatcher,
                IUiAutomationConnection instrumentationUiConnection, int debugMode,
                boolean enableBinderTracking, boolean trackAllocation,
                boolean isRestrictedBackupMode, boolean persistent, Configuration config,
                CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
                String buildSerial, AutofillOptions autofillOptions,
                ContentCaptureOptions contentCaptureOptions) {
            AppBindData data = https://www.cnblogs.com/jimuzz/p/new AppBindData();
            data.processName = processName;
            data.appInfo = appInfo;
            data.providers = providers;
            data.instrumentationName = instrumentationName;
            data.instrumentationArgs = instrumentationArgs;
            data.instrumentationWatcher = instrumentationWatcher;
            data.instrumentationUiAutomationConnection = instrumentationUiConnection;
            data.debugMode = debugMode;
            data.enableBinderTracking = enableBinderTracking;
            data.trackAllocation = trackAllocation;
            data.restrictedBackupMode = isRestrictedBackupMode;
            data.persistent = persistent;
            data.config = config;
            data.compatInfo = compatInfo;
            data.initProfilerInfo = profilerInfo;
            data.buildSerial = buildSerial;
            data.autofillOptions = autofillOptions;
            data.contentCaptureOptions = contentCaptureOptions;
            sendMessage(H.BIND_APPLICATION, data);
        }

        public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG,">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = https://www.cnblogs.com/jimuzz/p/(AppBindData)msg.obj;
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;
                }
            }
    

可以看到這里有個H,H是主執行緒的一個Handler類,用于處理需要主執行緒處理的各類訊息,包括BIND_SERVICE,LOW_MEMORY,DUMP_HEAP等等,接著看handleBindApplication:

    private void handleBindApplication(AppBindData data) {
        //...
        try {
            final ClassLoader cl = instrContext.getClassLoader();
            mInstrumentation = (Instrumentation)
                    cl.loadClass(data.instrumentationName.getClassName()).newInstance();
        }
        //...
        Application app;
        final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
        final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
        try {
            // If the app is being launched for full backup or restore, bring it up in
            // a restricted environment with the base application class.
            app = data.info.makeApplication(data.restrictedBackupMode, null);
            mInitialApplication = app;
            // don't bring up providers in restricted mode; they may depend on the
            // app's custom Application class
            if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                    installContentProviders(app, data.providers);
                }
            }

            // Do this after providers, since instrumentation tests generally start their
            // test thread at this point, and we don't want that racing.
            try {
                mInstrumentation.onCreate(data.instrumentationArgs);
            }
            //...
            try {
                mInstrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
                if (!mInstrumentation.onException(app, e)) {
                    throw new RuntimeException(
                            "Unable to create application " + app.getClass().getName()
                                    + ": " + e.toString(), e);
                }
            }
        }
            //...
    }

這里資訊量就多了,一點點的看:

  • 首先,創建了Instrumentation,也就是上文一開始startActivity的第一步,每個應用程式都有一個Instrumentation,用于管理這個行程,比如要創建Activity的時候,首先就會執行到這個類里面,
  • makeApplication方法,創建了Application,終于到這一步了,最侄訓走到newApplication方法,執行Application的attach方法,
    public Application newApplication(ClassLoader cl, String className, Context context)
            throws InstantiationException, IllegalAccessException, 
            ClassNotFoundException {
        Application app = getFactory(context.getPackageName())
                .instantiateApplication(cl, className);
        app.attach(context);
        return app;
    }

attach方法有了,onCreate方法又是何時呼叫的呢?馬上來了:

    instrumentation.callApplicationOnCreate(app);

    public void callApplicationOnCreate(Application app) {
        app.onCreate();
    }

也就是創建Application->attach->onCreate呼叫順序,

等等,在onCreate之前還有一句重要的代碼:

installContentProviders

這里就是啟動Provider的相關代碼了,具體邏輯就不分析了,

第六關:啟動Activity

說完bindApplication,該說說后續了,上文第五關說到,bindApplication方法之后執行的是attachApplication方法,最侄訓執行到ActivityThread的handleLaunchActivity方法:

    public Activity handleLaunchActivity(ActivityClientRecord r,
                                         PendingTransactionActions pendingActions, Intent customIntent) {
        //...
        WindowManagerGlobal.initialize();
        //...
        final Activity a = performLaunchActivity(r, customIntent);
        //...
        return a;
    }

首先,初始化了WindowManagerGlobal,這是個啥呢?
沒錯,就是WindowManagerService了,也為后續視窗顯示等作了準備,

繼續看performLaunchActivity:

    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        //創建ContextImpl
        ContextImpl appContext = createBaseContextForActivity(r);
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            //創建Activity
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
        }

        try {
            if (activity != null) {
                //完成activity的一些重要資料的初始化
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);

                if (customIntent != null) {
                    activity.mIntent = customIntent;
                }

                //設定activity的主題
                int theme = r.activityInfo.getThemeResource();
                if (theme != 0) {
                    activity.setTheme(theme);
                }

                //呼叫activity的onCreate方法
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
            }
        }

        return activity;
    }

哇,終于看到onCreate方法了,穩住,還是一步步看看這段代碼,

首先,創建了ContextImpl物件,ContextImpl可能有的朋友不知道是啥,ContextImpl繼承自Context,其實就是我們平時用的背景關系,有的同學可能表示,這不對啊,獲取背景關系明明獲取的是Context物件,來一起跟隨原始碼看看,

    //Activity.java
    Context mBase;

    @Override
    public Executor getMainExecutor() {
        return mBase.getMainExecutor();
    }

    @Override
    public Context getApplicationContext() {
        return mBase.getApplicationContext();
    }

這里可以看到,我們平時用的背景關系就是這個mBase,那么找到這個mBase是啥就行了:

    protected void attachBaseContext(Context base) {
        if (mBase != null) {
            throw new IllegalStateException("Base context already set");
        }
        mBase = base;
    }

    //一層層往上找

    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
        
        attachBaseContext(context);

        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }

       
    }

這不就是,,,剛才一開始performLaunchActivity方法里面的attach嗎?太巧了,所以這個ContextImpl就是我們平時所用的背景關系,

順便看看attach還干了啥?新建了PhoneWindow,建立自己和Window的關聯,并設定了setSoftInputMode等等,

ContextImpl創建完之后,會通過類加載器創建Activity的物件,然后設定好activity的主題,最后呼叫了activity的onCreate方法,

總結

再一起捋一遍App的啟動流程:

  • Launcher被呼叫點擊事件,轉到Instrumentation類的startActivity方法,
  • Instrumentation通過跨行程通信告訴AMS要啟動應用的需求,
  • AMS反饋Launcher,讓Launcher進入Paused狀態
  • Launcher進入Paused狀態,AMS轉到ZygoteProcess類,并通過socket與Zygote通信,告知Zygote需要新建行程,
  • Zygote fork行程,并呼叫ActivityThread的main方法,也就是app的入口,
  • ActivityThread的main方法新建了ActivityThread實體,并新建了Looper實體,開始loop回圈,
  • 同時ActivityThread也告知AMS,行程創建完畢,開始創建Application,Provider,并呼叫Applicaiton的attach,onCreate方法,
  • 最后就是創建背景關系,通過類加載器加載Activity,呼叫Activity的onCreate方法,

至此,應用啟動完畢,

當然,分析原始碼的目的一直都不是為了學知識而學,而是理解了這些基礎,我們才能更好的解決問題,
學習了App的啟動流程,我們可以再思考下一些之前沒理解透的問題,比如啟動優化

分析啟動程序,其實可以優化啟動速度的地方有三個地方:

  • Application的attach方法,MultiDexApplication會在方法里面會去執行MultiDex邏輯,所以這里可以進行MultiDex優化,比如今日頭條方案就是單獨啟動一個行程的activity去加載MultiDex,
  • Application的onCreate方法,大量三方庫的初始化都在這里進行,所以我們可以開啟執行緒池,懶加載等等,把每個啟動任務進行區分,哪些可以子執行緒運行,哪些有先后順序,
  • Activity的onCreate方法,同樣進行執行緒處理,懶加載,或者預創建Activity,提前類加載等等,

最后希望各位老鐵都能有一個乖巧可愛漂亮的女兒/兒子,??

附件

fork使用多執行緒
今日頭條啟動優化
app啟動流程分析


我的公眾號:碼上積木,每天三問面試題,詳細剖析,助你成為offer收割機,

謝謝你的閱讀,如果你覺得寫的還行,就點個贊支持下吧!感謝!
你的一個??,就是我分享的動力??,

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/172687.html

標籤:Android

上一篇:Android連載38-訪問聯系人

下一篇:是時候更新手里的武器了—Jetpack最全簡析

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more