美文网首页Android开发Android开发经验谈Android技术知识
Activity底层知识之Activity启动透彻分析

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

作者: 遛狗的程序员 | 来源:发表于2018-08-15 21:49 被阅读17次

前言

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

APP是怎么启动的?

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

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

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

<activity
            android:name=".guide.NullDefaultActivity"
            android:configChanges="keyboardHidden|orientation"
            android:screenOrientation="landscape"
            android:theme="@style/SplashTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </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的流程图:

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的接口,里面定义了四大组件所有的生命周期。
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的角度,来画这张图,是这样的:

image

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

先来感受一下:

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<ResultInfo> pendingResults, List<ReferrerIntent> 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。

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阶段 启动斗鱼首页Activity

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

image

handleLaunchActivity方法都做哪些事呢?

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

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

参考:

  • Android系统源代码情景分析(第三版) 罗升阳著
  • Android插件化开发指能 包建强著
  • 项目源码代码分析
  • launcher 启动流程解析
  • 部分图片和内容直接借用网上搜集过来的,如若侵权,请联系作者

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


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

qrcode_for_gh_1ba0785324d6_430.jpg

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

WXCD.jpeg

相关文章

网友评论

    本文标题:Activity底层知识之Activity启动透彻分析

    本文链接:https://www.haomeiwen.com/subject/evjgbftx.html