美文网首页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