Activity启动过程分析

通常我们在Activity中启动一个另一个Activity,就是调用Activity的startActivity方法,这个最终会调用到Activity的startActivityForResult方法。那你有没有想过Activity的启动到底经历了哪些过程,我们今天就来分析一下。

在具体分析之前,要先说明一下,Activity的启动流程在细节挺繁琐的,比如启动另一个App的Activity或者启动不同的launchMode的Activity,在细节上都会有不同。我们这次的源码分析着重分析一下流程,为了简单起见,就以分析一个Activity启动app内部另一个standard模式的Activity为主。

Activity启动另一个Activity之前,当前的Activity先要执行onPause,被启动的Activity才会执行到onResume方法。这中间实际上是会执行4次IPC过程的:

当前Activity发起启动另一个Activity的请求——>ActivityManagerService

ActivityManagerService——> 通知App暂停当前Activity

当前App告知已经暂停了当前的Activity——> ActivityManagerService

ActivityManagerService ——> 通知App启动新的Activity

注:本次源码分析采用Android7.0,不同版本的源码在细节上会有不同,比如,在Android8.0上ActivityManagerService就改成了以AIDL的方式来写,请不要太纠结API的不同。

注:本文Activity启动过程源码分析过程比较长,代码较繁琐,请做好心理准备。

Activity启动涉及到的类

首先要简单介绍一下Activity启动过程涉及到的类,以便于更好的理解这个启动过程。

ActivityThread:App启动的入口

ApplicationThread:ActivityThread的内部类,继承Binder,可以进程跨进程通信。

ApplicationThreadProxy:ApplicationThread的一个本地代理,其它的client端通过这个对象调用server端ApplicationThread中方法。

Instrumentation:负责发起Activity的启动、并具体负责Activity的创建以及Activity生命周期的回调。一个应用进程只会有一个Instrumentation对象,App内的所有Activity都持有该对象的引用。

ActivityManagerService:简称AMS,是service端对象,负责管理系统中所有的Activity

ActivityManagerProxy:是ActivityManagerService的本地代理

ActivityStack:Activity在AMS的栈管理,用来记录已经启动的Activity的先后关系,状态信息等。通过ActivityStack决定是否需要启动新的进程。

ActivityRecord:ActivityStack的管理对象,每个Activity在AMS对应一个ActivityRecord,来记录Activity的状态以及其他的管理信息。其实就是服务器端的Activity对象的映像。

TaskRecord:AMS抽象出来的一个“任务”的概念,是记录ActivityRecord的栈,一个“Task”包含若干个ActivityRecord。AMS用TaskRecord确保Activity启动和退出的顺序。

介绍完这些,我们开始进入正题

Activity的启动过程

Activity启动最终会调用到startActivityForResult方法,我们只需要关注mParent == null中的逻辑即可。mParent代表的是ActivityGroup,其最开始是为了在一个界面中嵌入多个子Activity,在API13的时候就已经废弃了,可以使用Fragment表示一个界面的多个区域。

# android.app.ActivitypublicvoidstartActivityForResult(@RequiresPermission Intent intent,intrequestCode,                                  @Nullable Bundle options){if(mParent == null) {        Instrumentation.ActivityResult ar =                mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken,this,                        intent, requestCode, options);if(ar != null) {            mMainThread.sendActivityResult(                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),                    ar.getResultData());        }        ...    }else{        ...    }}

调用了Instrumentation的execStartActivity方法,在这里通过ActivityManagerNative.getDefault()方法获取ActivityManagerService的一个本地代理对象ActivityManagerProxy,然后调用了其startActivity方法:

# android.app.InstrumentationpublicActivityResult execStartActivity(        Context who, IBinder contextThread, IBinder token, Activity target,        Intent intent, int requestCode, Bundle options) {    IApplicationThread whoThread = (IApplicationThread) contextThread;    ...try{      ...        int result = ActivityManagerNative.getDefault()                .startActivity(whoThread, who.getBasePackageName(), intent,                        intent.resolveTypeIfNeeded(who.getContentResolver()),                        token, target !=null? target.mEmbeddedID :null,                        requestCode,0,null, options);        checkStartActivityResult(result, intent);    }catch(RemoteException e) {thrownewRuntimeException("Failure from system", e);    }returnnull;}

我们看一下ActivityManagerNative,继承了Binder并实现了IActivityManager接口,ActivityManagerService就是继承了ActivityManagerNative。

publicabstractclassActivityManagerNativeextendsBinderimplementsIActivityManager

publicfinalclassActivityManagerServiceextendsActivityManagerNativeimplementsWatchdog.Monitor,BatteryStatsImpl.BatteryCallback

classActivityManagerProxyimplementsIActivityManager

ActivityManagerNative实际上获取的就是其内部类ActivityManagerProxy对象。ActivityManagerProxy只是ActivityManagerService的本地代理对象,其startActivity方法会调用到AMS的startActivity方法。而且要注意,这个startActivity方法会把ApplicationThread对象传递到AMS所在进程,当然了AMS拿到的实际上是ApplicationThread的代理对象ApplicationThreadProxy,AMS就要通过这个代理对象与我们的App进程进行通信。

# android.app.ActivityManagerNativeprivatestaticfinalSingleton gDefault =newSingleton() {protectedIActivityManager create() {        IBinder b = ServiceManager.getService("activity");if(false) {            Log.v("ActivityManager","default service binder = "+ b);        }        IActivityManager am = asInterface(b);if(false) {            Log.v("ActivityManager","default service = "+ am);        }returnam;    }};staticpublicIActivityManager asInterface(IBinder obj) {if(obj ==null) {returnnull;    }    IActivityManager in =            (IActivityManager)obj.queryLocalInterface(descriptor);if(in !=null) {returnin;    }returnnewActivityManagerProxy(obj);}staticpublicIActivityManager getDefault() {returngDefault.get();}

注:在Android8.0,由于使用AIDL的方式来写ActivityManagerService,ActivityManagerNative已经过期。

我们接着看一下AMS的startActivity方法:

# com.android.server.am.ActivityManagerServicepublicfinalintstartActivity(IApplicationThread caller, String callingPackage,                              Intent intent, String resolvedType, IBinder resultTo, String resultWho,intrequestCode,intstartFlags, ProfilerInfo profilerInfo, Bundle bOptions){returnstartActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,            resultWho, requestCode, startFlags, profilerInfo, bOptions,            UserHandle.getCallingUserId());}

startActivity方法紧接着调用了其startActivityAsUser方法。

@OverridepublicfinalintstartActivityAsUser(IApplicationThread caller, String callingPackage,                                    Intent intent, String resolvedType, IBinder resultTo, String resultWho,intrequestCode,intstartFlags, ProfilerInfo profilerInfo, Bundle bOptions,intuserId){    enforceNotIsolatedCaller("startActivity");    userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),            userId,false, ALLOW_FULL_ONLY,"startActivity",null);//TODO:Switch to user app stacks here.returnmActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,            resolvedType,null,null, resultTo, resultWho, requestCode, startFlags,            profilerInfo,null,null, bOptions,false, userId,null,null);}

接着调用了ActivityStarter的startActivityMayWait方法,由于方法很长,我们只保留关键的流程部分:

# com.android.server.am.ActivityStarterfinalintstartActivityMayWait(IApplicationThread caller,intcallingUid,        String callingPackage, Intent intent, String resolvedType,        IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,        IBinder resultTo, String resultWho,intrequestCode,intstartFlags,        ProfilerInfo profilerInfo, IActivityManager.WaitResult outResult, Configuration config,        Bundle bOptions, boolean ignoreTargetSecurity,intuserId,        IActivityContainer iContainer, TaskRecord inTask){          ...intres = startActivityLocked(caller, intent, ephemeralIntent, resolvedType,                aInfo, rInfo, voiceSession, voiceInteractor,                resultTo, resultWho, requestCode, callingPid,                callingUid, callingPackage, realCallingPid, realCallingUid, startFlags,                options, ignoreTargetSecurity, componentSpecified, outRecord, container,                inTask);        ...    }}

ActivityStarter调用了自身的startActivityLocked方法,这又是一个很长的方法,保留关键的流程如下。

# com.android.server.am.ActivityStarterfinalintstartActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,        String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,        IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,        IBinder resultTo, String resultWho,intrequestCode,intcallingPid,intcallingUid,        String callingPackage,intrealCallingPid,intrealCallingUid,intstartFlags,        ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,        ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container,        TaskRecord inTask){interr = ActivityManager.START_SUCCESS;    ...try{        mService.mWindowManager.deferSurfaceLayout();        err = startActivityUnchecked(r, sourceRecord, voiceSession, voiceInteractor, startFlags,true, options, inTask);    } finally {        mService.mWindowManager.continueSurfaceLayout();    }    postStartActivityUncheckedProcessing(r, err,stack.mStackId, mSourceRecord, mTargetStack);returnerr;}

ActivityStarter又调用了自身的startActivityUnchecked方法,

# com.android.server.am.ActivityStarterprivateintstartActivityUnchecked(final ActivityRecord r, ActivityRecord sourceRecord,        IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,intstartFlags, boolean doResume, ActivityOptions options, TaskRecord inTask){    ...if(mDoResume) {        ...if(!mTargetStack.isFocusable()            ...        }else{            mSupervisor.resumeFocusedStackTopActivityLocked(mTargetStack, mStartActivity,                    mOptions);        }    }else{      ...    }    ...returnSTART_SUCCESS;}

在ActivityStarter中调用了ActivityStackSupervisor的resumeFocusedStackTopActivityLocked方法。

# com.android.server.am.ActivityStackSupervisorbooleanresumeFocusedStackTopActivityLocked(

        ActivityStack targetStack, ActivityRecord target, ActivityOptions targetOptions){if(targetStack != null && isFocusedStack(targetStack)) {returntargetStack.resumeTopActivityUncheckedLocked(target, targetOptions);    }    ... }

在ActivityStackSupervisor的resumeFocusedStackTopActivityLocked中又调用了ActivityStack的resumeTopActivityUncheckedLocked方法。

# com.android.server.am.ActivityStackbooleanresumeTopActivityUncheckedLocked(ActivityRecord prev, ActivityOptions options){  ...    boolean result =false;try{// Protect against recursion.mStackSupervisor.inResumeTopActivity =true;        ...        result = resumeTopActivityInnerLocked(prev, options);    } finally {        mStackSupervisor.inResumeTopActivity =false;    }returnresult;}

ActivityStack在resumeTopActivityUncheckedLocked又调用了其自身的resumeTopActivityInnerLocked方法。

# com.android.server.am.ActivityStackprivatebooleanresumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options){    ..// We need to start pausing the current activity so the top one can be resumed......if(mResumedActivity != null) {if(DEBUG_STATES) Slog.d(TAG_STATES,"resumeTopActivityLocked: Pausing "+ mResumedActivity);        pausing |= startPausingLocked(userLeaving,false, next, dontWaitForPause);    }    ...}

由于当前的Activity执行了onResume,所以mResumedActivity != null 条件满足,就会调用startPausingLocked方法先暂停当前的Activity。注意:这个过程必然是一个IPC过程。我们看一下startPausingLocked方法。

# com.android.server.am.ActivityStackfinalboolean startPausingLocked(boolean userLeaving, boolean uiSleeping,        ActivityRecord resuming, boolean dontWait) {    ActivityRecord prev = mResumedActivity;    ...if(prev.app !=null&& prev.app.thread !=null) {if(DEBUG_PAUSE) Slog.v(TAG_PAUSE,"Enqueueing pending pause: "+ prev);try{            ...            prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,                    userLeaving, prev.configChangeFlags, dontWait);        }catch(Exceptione) {          ...        }    }else{      ...    }    ...}

prev.app.thread表示的是IApplicationThread对象,在这里就是指的发起启动的Activity所在进程的ApplicationThread的本地代理ApplicationThreadProxy。调用它的schedulePauseActivity方法,很明显是一次IPC过程,最终调用到server端,也就是发起启动的Activity所在进程ApplicationThread的schedulePauseActivity方法。

# android.app.ActivityThread$$ApplicationThreadpublicfinalvoidschedulePauseActivity(IBinder token, boolean finished,        boolean userLeaving,intconfigChanges, boolean dontReport){intseq = getLifecycleSeq();if(DEBUG_ORDER) Slog.d(TAG,"pauseActivity "+ ActivityThread.this+" operation received seq: "+ seq);    sendMessage(            finished ? H.PAUSE_ACTIVITY_FINISHING : H.PAUSE_ACTIVITY,            token,            (userLeaving ? USER_LEAVING :0) | (dontReport ? DONT_REPORT :0),            configChanges,            seq);}

通过Hander的转发,接着会调用到ActivityThread的handlePauseActivity方法。

# android.app.ActivityThreadprivatevoidhandlePauseActivity(IBinder token, boolean finished,        boolean userLeaving,intconfigChanges, boolean dontReport,intseq){    ActivityClientRecord r = mActivities.get(token);    ...if(r != null) {    ...        performPauseActivity(token, finished, r.isPreHoneycomb(),"handlePauseActivity");    ...// Tell the activity manager we have paused.if(!dontReport) {try{                ActivityManagerNative.getDefault().activityPaused(token);            }catch(RemoteException ex) {throwex.rethrowFromSystemServer();            }        }        mSomeActivitiesChanged =true;    }}

在ActivityThread的handlePauseActivity中,首先会调用performPauseActivity来暂停当前的Activity,经过层层调用,会调用到Intrumentation的callActivityOnPause方法,最终调用Activity的onPause方法,这一块的流程比较简单,在这里就不再详细分析了,感兴趣的可以自己研究下。

暂停之后,会调动ActivityManagerNative.getDefault().activityPaused(token),这个很明显又是一次IPC过程,就是告诉AMS,已经暂停当前的Activity,可以启动新的Activity 了。

我们来看一下AMS的的activityPaused方法:

# com.android.server.am.ActivityManagerService@OverridepublicfinalvoidactivityPaused(IBinder token){    finallongorigId = Binder.clearCallingIdentity();    synchronized(this) {        ActivityStackstack= ActivityRecord.getStackLocked(token);if(stack!= null) {stack.activityPausedLocked(token,false);        }    }    Binder.restoreCallingIdentity(origId);}

AMS中的activityPaused又调用了ActivityStack的activityPausedLocked方法。

# com.android.server.am.ActivityStackfinalvoid activityPausedLocked(IBinder token, boolean timeout) {    ...finalActivityRecord r = isInStackLocked(token);if(r !=null) {        mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);if(mPausingActivity == r) {if(DEBUG_STATES) Slog.v(TAG_STATES,"Moving to PAUSED: "+ r                    + (timeout ?" (due to timeout)":" (pause complete)"));            completePauseLocked(true,null);return;        }else{          ...        }    }  ...}

在这个方法中又调用了ActivityStack自身的completePauseLocked方法,

# com.android.server.am.ActivityStackprivatevoidcompletePauseLocked(boolean resumeNext, ActivityRecord resuming){    ActivityRecord prev = mPausingActivity;if(resumeNext) {        final ActivityStack topStack = mStackSupervisor.getFocusedStack();if(!mService.isSleepingOrShuttingDownLocked()) {            mStackSupervisor.resumeFocusedStackTopActivityLocked(topStack, prev, null);        }else{          ...        }    }    ...}

然后又调用了ActivityStackSupervisor的resumeFocusedStackTopActivityLocked方法,这视乎又重新调用了一遍,真复杂啊。

这个流程我们上面讲过了,ActivityStackSupervisor会继续调用ActivityStack的resumeTopActivityUncheckedLocked方法,然后ActivityStack又调用其resumeTopActivityInnerLocked方法,调来调去,又到这个方法里面了,上次在这里是执行了前一个Activity的onPause方法。这次会调用到ActivityStackSupersivor的startSpecificActivityLocked方法。

# com.android.server.am.ActivityStackprivateboolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {// We need to start pausing the current activity so the top one can be resumed...finalboolean dontWaitForPause = (next.info.flags & FLAG_RESUME_WHILE_PAUSING) !=0;    boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving, next, dontWaitForPause);if(mResumedActivity !=null) {if(DEBUG_STATES) Slog.d(TAG_STATES,"resumeTopActivityLocked: Pausing "+ mResumedActivity);        pausing |= startPausingLocked(userLeaving,false, next, dontWaitForPause);    }    ...    ActivityStack lastStack = mStackSupervisor.getLastStack();if(next.app !=null&& next.app.thread !=null) {        ...    }else{        ...        mStackSupervisor.startSpecificActivityLocked(next,true,true);    }if(DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();returntrue;}

分析到这里,好像我们的就要开始启动我们的目标Activity了,好激动有木有!

在ActivityStackSupersivor的startSpecificActivityLocked方法中会判断Activity所在进程是否存在,如果不存在的话就要创建一个新的进程。在这里,我们是Activity启动其App内部的另一个Activity,所以进程肯定是存在的,会走到realStartActivityLocked方法中。

# com.android.server.am.ActivityStackSupervisorvoidstartSpecificActivityLocked(ActivityRecord r,

        boolean andResume, boolean checkConfig){    ...if(app != null && app.thread != null) {try{    ...            realStartActivityLocked(r, app, andResume, checkConfig);return;        }catch(RemoteException e) {          ...        }    }    mService.startProcessLocked(r.processName, r.info.applicationInfo,true,0,"activity", r.intent.getComponent(),false,false,true);}

再来看一下ActivityStackSupersivor的realStartActivityLocked方法,这次似乎真的要启动一个Activity了。

# com.android.server.am.ActivityStackSupervisorfinal booleanrealStartActivityLocked(ActivityRecord r, ProcessRecord app,

        boolean andResume, boolean checkConfig)throws RemoteException{    ...try{        app.thread.scheduleLaunchActivity(newIntent(r.intent), r.appToken,                System.identityHashCode(r), r.info,newConfiguration(mService.mConfiguration),newConfiguration(task.mOverrideConfig), r.compat, r.launchedFromPackage,                task.voiceInteractor, app.repProcState, r.icicle, r.persistentState, results,                newIntents, !andResume, mService.isNextTransitionForward(), profilerInfo);      ...    }catch(RemoteException e) {      ...    }    ...returntrue;}

看了代码,果然,调用了app.thread.scheduleLaunchActivity方法,app.thread我们前面讲过,就是IApplicationThread对象,实际上就是ApplicationThreadProxy对象,经过IPC过程会调用到ApplicationThread的scheduleLaunchActivity方法,我们来看一下:

# android.app.ActivityThread$$ApplicationThread@OverridepublicfinalvoidscheduleLaunchActivity(Intent intent, IBinder token,intident,        ActivityInfo info, Configuration curConfig, Configuration overrideConfig,        CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,intprocState, Bundle state, PersistableBundle persistentState,        List pendingResults, List pendingNewIntents,        boolean notResumed, boolean isForward, ProfilerInfo profilerInfo){    ...    sendMessage(H.LAUNCH_ACTIVITY, r);}

通过Hander的转发,接着会调用到ActivityThread的handlePauseActivity方法。

# android.app.ActivityThreadprivatevoidhandleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason){    ...    Activity a = performLaunchActivity(r, customIntent);if(a != null) {    ...        handleResumeActivity(r.token,false, r.isForward,                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);      ...    }else{      ...    }}

handlePauseActivity内部调用performLaunchActivity方法:

# android.app.ActivityThreadprivateActivity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {    ...    Activity activity =null;try{        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();        activity = mInstrumentation.newActivity(                cl, component.getClassName(), r.intent);        ...    }catch(Exceptione) {      ...    }try{        Application app = r.packageInfo.makeApplication(false, mInstrumentation);        ...if(activity !=null) {            Context appContext = createBaseContextForActivity(r, 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);            ...            activity.mCalled =false;if(r.isPersistable()) {                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);            }else{                mInstrumentation.callActivityOnCreate(activity, r.state);            }          ...            r.activity = activity;            r.stopped =true;if(!r.activity.mFinished) {                activity.performStart();                r.stopped =false;            }if(!r.activity.mFinished) {if(r.isPersistable()) {if(r.state !=null|| r.persistentState !=null) {                        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,                                r.persistentState);                    }                }elseif(r.state !=null) {                    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);                }            }if(!r.activity.mFinished) {                activity.mCalled =false;if(r.isPersistable()) {                    mInstrumentation.callActivityOnPostCreate(activity, r.state,                            r.persistentState);                }else{                    mInstrumentation.callActivityOnPostCreate(activity, r.state);                }if(!activity.mCalled) {thrownewSuperNotCalledException("Activity "+ r.intent.getComponent().toShortString() +" did not call through to super.onPostCreate()");                }            }        }        r.paused =true;        mActivities.put(r.token, r);    }catch(SuperNotCalledException e) {throwe;    }catch(Exceptione) {      ...    }returnactivity;}

在performLaunchActivity内部,创建了Activity对象,并调用了activity的attach方法,在这个方法中绑定一些属性,并且创建了Activity所属的Window对象。接着Instrumentation会调用callActivityOnCreate、callActivityOnRestoreInstanceState、callActivityOnPostCreate等来完成Activity生命周期的回调。不过有一点很有意思,在这个方法中Activity自己调用了其performStart方法,但这个方法内部又调用了Instrumentation的callActivityOnStart方法,进而又调用了Activity的onStart方法。绕过来绕过去,总之,Activity生命周期方法的调用均是通过Instrumentation来控制的。

至此,Activity的启动过程算是分析完了,太费脑筋了,需要来一瓶营养快线补补身体。流程太多,不好记,但是有句话说的好 "一图胜千言",下面我们来看一下Activity启动的一个时序图。

Activity启动过程.png

总结

源码分析过程是比较绕,比较烧脑的过程,不要太纠结去API的调用,尽量侧重流程。分析源码,最好带着问题去分析,不要为了分析而分析,尽量在分析过程中寻找自己想要的答案。比如:

Activity对象是怎么创建的?

Window对象是什么时候创建的?

LayoutInflater什么时候创建的?

为什么在Activity中的布局中或者Fragment的中View获取的Context都是其所在的Activity对象?

为什么自定义View一定要有两个参数的构造函数?

Activity的生命周期方法是被谁回调的?

Application是什么时候创建的?

ClassLoader对象是什么时候创建的?

子线程可以启动Activity、Service吗?

下拉通知栏,会影响Activity生命周期吗?

笔者初入Android开发没多久的时候,有次面试,面试官问我在子线程中启动Activity可以吗?我回答可以,因为我试过,但是问我为什么我却不知道。如果那时候看了Activity启动过程的源码,看了Binder机制的话,应该很容易的就回答出来了。

参考

Android Launcher 启动 Activity 的工作过程

【凯子哥带你学Framework】Activity启动过程全解析

Android进阶——Android四大组件启动机制之Activity启动过程

作者:sososeen09

链接:https://www.jianshu.com/p/13b07beacb1f

来源:

著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

你可能感兴趣的:(Activity启动过程分析)