Activity底层知识之Activity启动透彻分析

前言

对于我们做电商APP的开发人员来说,Activity是四大组件中使用最多的,当然也是最复杂的。那Activity是如何启动的?点击手机上APP图标又发生了什么呢?今天我们就一一解析。

APP是怎么启动的?

当我们点击某个APP图标的时候,这里假设是电商APP-永辉生活。这个APP的首页(或启动页)就展示在我们面前了。看似简单的操作,其实里面是Activity和AMS反反复复的通信过程。

这里补充一下,我们点击的应用图标的界面其实是一个Activity,官方用语Launcher。launcher其实就是一个app,它的作用用来显示和管理手机上其他App。目前市场上有很多第三方的launcher应用,比如“小米桌面”、“91桌面”等等(因此我们可以对桌面图标进行分组,调用网络请求等)。

首先回顾一下我们是怎么定义默认启动的Activity的呢?


            
                
                
            
        

这些配置信息在APP安装的时候(或者Android系统启动的时候),通过PMS(PackageManagerService)从永辉生活APK的配置文件(AndroidManifest)文件中读取到的。所以点击永辉生活应用图标就启动了永辉生活。

启动APP就这么简单吗?

并非如此。上述只是一个最简单描述。我们通过Launcher启动一个新的Activity是启动一个新的进程,Launcher和永辉生活 APP属于不同的进程。它们之间的通信是通过Binder完成通信的。我们的AMS登场了。
我们这里就以永辉生活APP为例,整体流程分为7个阶段。

  1. Launcher通知AMS,要启动永辉生活APP,并告诉要启动哪个页面(这里我们是引导页)
  2. AMS通知Launcher,“好了,我知道了,谢谢你了,你可以休息了”。同时把要启动的引导页信息记录下来。
  3. Launcher进入paused状态,同时通知AMS,“那我休息了,接下来我就不管了”。

1-3阶段上面是Launcher和AMS之间的通信。

  1. AMS检查永辉生活APP是否已经启动了。是的话,就唤起永辉生活APP,如果不是的话,就开启一个新的进程。AMS在新进程创建一个ActivityThread对象,启动其中的main函数。
  2. 永辉生活APP启动后,通知AMS,“我启动好了”。
  3. AMS翻出第二阶段的保存的值,告诉永辉生活APP,启动哪个页面。
  4. 永辉生活APP启动引导页,创建Context并与引导页Activity相关联。然后调用引导页onCreate相关联。

4-7阶段,永辉生活APP与AMS相互通信。

这边涉及到一堆类。

  • ActivityThread: 应用的启动入口类,当应用启动,会首先执行其main方法,开启主线程消息循环机制。
  • ApplicationThread: ActivityThread的内部类,主要与系统进程AMS通信,从而对应用进程的具体Activity操作进行管理。
  • Instrumentation: ActivityThread的属性变量,主要辅助ActivityThread类调用Activity的生命周期相关方法。
  • ActivityManagerService(AMS): Activity管理系统服务类,主要是对所有的Activity进行管理。
  • ActivityStack,Activity在AMS的栈管理,用来记录已经启动的Activity的先后关系,状态信息等。通过ActivityStack决定是否需要启动新的进程。
  • ActivityRecord,ActivityStack的管理对象,每个Activity在AMS对应一个ActivityRecord,来记录Activity的状态以及其他的管理信息。其实就是服务器端的Activity对象的映像。
  • TaskRecord,AMS抽象出来的一个“任务”的概念,是记录ActivityRecord的栈,一个“Task”包含若干个ActivityRecord。AMS用TaskRecord确保Activity启动和退出的顺序。如果你清楚Activity的4种launchMode,那么对这个概念应该不陌生。

第1阶段:Launcher通知AMS

Launcher通知AMS的流程图:

Activity底层知识之Activity启动透彻分析_第1张图片
image
/**
     * Launches the intent referred by the clicked shortcut.
     *
     * @param v The view representing the clicked shortcut.
     */
    public void onClick(View v) {
        Object tag = v.getTag();
        if (tag instanceof ApplicationInfo) {
            // Open shortcut
            final Intent intent = ((ApplicationInfo) tag).intent;
            startActivitySafely(intent);
        } else if (tag instanceof FolderInfo) {
            handleFolderClick((FolderInfo) tag);
        }
    }
    void startActivitySafely(Intent intent) {
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        try {
            startActivity(intent);
        } catch (ActivityNotFoundException e) {
            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
        } catch (SecurityException e) {
            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
            e(LOG_TAG, "Launcher does not have the permission to launch " + intent +
                    ". Make sure to create a MAIN intent-filter for the corresponding activity " +
                    "or use the exported attribute for this activity.", e);
        }
    }

继续跟进:

@Override
    public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {
            startActivityForResult(intent, -1, options);
        } else {
            startActivityForResult(intent, -1);
        }
    }

后调用的还是这个重载方法:

public void startActivityForResult(Intent intent, int requestCode, @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());
            }
            if (requestCode >= 0) {
                mStartedActivity = true;
            }

            cancelInputsAndStartExitTransition(options);
            
        } else {
            if (options != null) {
                mParent.startActivityFromChild(this, intent, requestCode, options);
            } else {
          mParent.startActivityFromChild(this, intent, requestCode);
            }
        }
    }

可以发现这里调用了mInstrumentation.execStartActivity方法,这里先简单介绍一下Instrumentation对象,他是Android系统中应用程序端操作Activity的具体操作类,这里的操作段是相对于ActivityManagerService服务端来说的。也就是说当我们在执行对Activity的具体操作时,比如回调生命周期的各个方法都是借助于Instrumentation类来实现的。

我们发现有个mMainThread变量,这是一个ActivityThread变量,就是主线程,也就是UI线程,它是APP启动时候创建的,它代表了一个应用程序。

它里面有个main函数,这个是由Android系统底层提供的。源码如下:

public static void main(String[] args) {
       //省略部分代码

        Looper.prepareMainLooper();

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

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

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

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

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

上面代码传递了两个很重要的参数:

  • 通过ActivityThread的getApplicationThread方法取到一个Binder对象,它的类型为ApplicationThread,它代表着Launcher所在的App进程。
  • mToken,这也是个Binder对象,它代表了Launcher这个Activity,这里也通过Instrumentation传给AMS,AMS一查电话簿,就知道是谁向AMS发起请求了。

这两个参数是伏笔,传递给AMS,以后AMS想反过来通知Launcher,就能通过这两个参数,找到Launcher。

Instrumentation的execStartActivity方法:

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        ....................................................
        
        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) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }

AMN的getDefault方法

  • ServiceManager是一个容器类。
  • AMN的getDefault方法返回类型为IActivityManager,而不是AMP。IActivityManager是一个实现了IInterface的接口,里面定义了四大组件所有的生命周期。
Activity底层知识之Activity启动透彻分析_第2张图片
image

AMP的startActivity方法

看到这里,你会发现AMP的startActivity方法,和AIDL的Proxy方法,是一模一样的,写入数据到另一个进程,也就是AMS,然后等待AMS返回结果。

第2阶段 AMS处理Laucner传递过来的信息

  • 首先Binder,也就是AMN/AMP,和AMS通信,肯定每次是做不同的事情,就比如说这次Launcher要启动永辉生活App,那么会发送类型为START_ACTIVITY——TRANSACTION的请求给AMS,同时会告诉AMS要启动哪个Activity。
  • AMS说,好,我知道了,然后它会干一件很有趣的事情,就是检查永辉生活App中的Manifest文件,是否存在要启动的Activity。如果不存在,就抛出Activity not found的错误,各位做App的同学对这个异常应该再熟悉不过了,经常写了个Activity而忘记在Manifest中声明了,就报这个错,就是因为AMS在这里做检查。不管是新启动一个App的首页,还是在App内部跳转到另一个Activity,都会做这个检查。
  • 接下来AMS会通知Launcher

Binder的双方进行通信是平等的,谁发消息,谁就是Client,接收的一方就是Server。Client这边会调用Server的代理对象。

那么当AMS想给Launcher发消息,又该怎么办呢?前面不是把Launcher以及它所在的进程给传过来了吗?它在AMS这边保存为一个ActivityRecord对象,这个对象里面有一个ApplicationThreadProxy,单单从名字看就出卖了它,这就是一个Binder代理对象。它的Binder真身,也就是ApplicationThread。

站在AIDL的角度,来画这张图,是这样的:

Activity底层知识之Activity启动透彻分析_第3张图片
image

第3阶段:Launcher开始休息,然后通知AMS,“我休息了”

先来感受一下:

Activity底层知识之Activity启动透彻分析_第4张图片
image
  // we use token to identify this activity without having to send the
        // activity itself back to the activity manager. (matters more with ipc)
        @Override
        public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
                ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
                CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
                int procState, Bundle state, PersistableBundle persistentState,
                List pendingResults, List pendingNewIntents,
                boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

            updateProcessState(procState, false);

            ActivityClientRecord r = new ActivityClientRecord();

            r.token = token;
            r.ident = ident;
            r.intent = intent;
            r.referrer = referrer;
            r.voiceInteractor = voiceInteractor;
            r.activityInfo = info;
            r.compatInfo = compatInfo;
            r.state = state;
            r.persistentState = persistentState;

            r.pendingResults = pendingResults;
            r.pendingIntents = pendingNewIntents;

            r.startsNotResumed = notResumed;
            r.isForward = isForward;

            r.profilerInfo = profilerInfo;

            r.overrideConfig = overrideConfig;
            updatePendingConfiguration(curConfig);

            sendMessage(H.LAUNCH_ACTIVITY, r);
        }
 public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case LAUNCH_ACTIVITY: {
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
                    final ActivityClientRecord r = (ActivityClientRecord) msg.obj;

                    r.packageInfo = getPackageInfoNoCheck(
                            r.activityInfo.applicationInfo, r.compatInfo);
                    handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                } break;

AMS给Activity发送的所有消息,以及给其它三大组件发送的所有消息,都从H这里经过。插件化就可以在这里动手脚。
H对于PAUSE_ACTIVITY消息的处理,如上面的代码,是调用ActivityThread的handlePauseActivity方法。这个方法干两件事:

  • ActivityThread里面有一个mActivities集合,保存当前App也就是Launcher中所有打开的Activity,把它找出来,让它休眠。
  • 通过AMP通知AMS,我真的休眠了。
    你可能会找不到H和APT这两个类文件,那是因为它们都是ActivityThread的内嵌类。

第4阶段:AMS启动新的进程

AMS接下来要启动永辉生活App的首页,因为永辉生活App不在后台进程中,所以要启动一个新的进程。这里调用的是Process.start方法,并且指定了ActivityThread的main函数为入口函数。

  Process.ProcessStartResult startResult = Process.start(entryPoint,
                    app.processName, uid, uid, gids, debugFlags, mountExternal,
                    app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
                    app.info.dataDir, entryPointArgs);

第5阶段:新的进程启动,以ActivityThread的main函数为入口

启动新进程,其实就是启动一个新的APP。

Activity底层知识之Activity启动透彻分析_第5张图片
image

在启动新进程的时候,为这个进程创建ActivityThread对象,这就是我们耳熟能详的主线程(UI线程)。

创建好UI线程后,立刻进入ActivityThread的main函数,接下来要做2件具有重大意义的事情:

1)创建一个主线程Looper,也就是MainLooper。看见没,MainLooper就是在这里创建的。
2)创建Application。记住,Application是在这里生成的。

第6阶段 AMS告诉新App启动哪个Activity

还记得第1阶段,Launcher发送给AMS要启动永辉生活App的哪个Activity吗?这个信息被AMS存下来了。

那么在第6阶段,AMS从过去的记录中翻出来要启动哪个Activity,然后通过ActivityThreadProxy告诉App。

第7阶段 启动永辉生活APP首页Activity

万事俱备只欠东风,收场的来了

Activity底层知识之Activity启动透彻分析_第6张图片
image

handleLaunchActivity方法都做哪些事呢?

  • 通过Instrumentation的newActivity方法,创建出来要启动的Activity实例。
  • 为这个Activity创建一个上下文Context对象,并与Activity进行关联。
  • 通过Instrumentation的callActivityOnCreate方法,执行Activity的onCreate方法,从而启动Activity。看到这里是不是很熟悉很亲切?

至此,App启动完毕。这个流程是经过了很多次握手, App和ASM,频繁的向对方发送消息,而发送消息的机制,是建立在Binder的基础之上的。

参考:

  • Android系统源代码情景分析(第三版) 罗升阳著
  • Android插件化开发指能 包建强著
  • 项目源码代码分析
  • launcher 启动流程解析

声明:此为原创,转载请联系作者


作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。

Activity底层知识之Activity启动透彻分析_第7张图片
qrcode_for_gh_1ba0785324d6_430.jpg

当然喜爱技术,乐于分享的你也可以可以添加作者微信号:

Activity底层知识之Activity启动透彻分析_第8张图片
WXCD.jpeg

你可能感兴趣的:(Activity底层知识之Activity启动透彻分析)