专栏首页Android技术分享原来一个App是这样启动起来的,一看就懂

原来一个App是这样启动起来的,一看就懂

前言

当我们点击手机屏幕上的软件图标时,就可以打开这个软件,看似很简单的过程其实包含了许多的底层交互,看了还不明白,欢迎来打我。

启动流程简介

首先要知道的是,手机屏幕其实就是一个Activity,我们专业点将其称为Launcher,相信做过车载设备开发的朋友肯定不会陌生,Launcher是手机厂商提供的,不同的手机厂商比拼的就是Launcher的设计。当然我们自己也可以去编写Launcher,运行在手机上使用自己的桌面风格,当然这里我们不去讲如何去编写一个Launcher,如果你感兴趣欢迎关注我。

写过AndroidDemo的朋友肯定都知道,我们必须在AndroidManifest配置文件中配置默认启动的Activity。

<intent-filter>
    <action android:name="android.intent.action.MAIN" />

    <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> 

其实就是告诉Launcher该启动这个App的哪个页面。这样当系统启动的时候,PackageManger-Service就可以从配置文件中读取到该启动哪个Activity。

其次要知道的是,Launch和其他APP,运行在不同的进程中,所以他们之间的通信是通过Binder去完成的,所以AMS是必不可少的。下面我们以启动微信为例,看看启动流程是怎样的。

简单概括启动微信的流程就是:

1.Launcher通知AMS 要启动微信了,并且告诉AMS要启动的是哪个页面也就是首页是哪个页面 2.AMS收到消息告诉Launcher知道了,并且把要启动的页面记下来 3.Launcher进入Paused状态,告诉AMS,你去找微信吧

上述就是Launcher和AMS的交互过程

4.AMS检查微信是否已经启动了也就是是否在后台运行,如果是在后台运行就直接启动,如果不是,AMS会在新的进程中创建一个ActivityThread对象,并启动其中的main函数。 5.微信启动后告诉AMS,启动好了 6.AMS通过之前的记录找出微信的首页,告诉微信应该启动哪个页面 7.微信按照AMS通知的页面去启动就启动成功了。

上述阶段是微信和AMS的交互过程。那么接下来我们分析下具体过程

启动流程分析

点击Launcher上的微信图标时,会调用startActivitySafely方法,intent中携带微信的关键信息也就是我们在配置文件中配置的默认启动页信息,其实在微信安装的时候,Launcher已经将启动信息记录下来了,图标只是一个快捷方式的入口。

startActivitySafely的方法最终还是会调用Activity的startActivity方法

@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
    if (options != null) {
        startActivityForResult(intent, -1, options);
    } else {
        // Note we want to go through this call for compatibility with
        // applications that may have overridden the method.
        startActivityForResult(intent, -1);
    }
} 

而startActivity方法最终又会回到startActivityForResult方法,这里startActivityForResult的方法中code为-1,表示startActivity并不关心是否启动成功。startActivityForResult部分方法如下所示:

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
        @Nullable Bundle options) {
    if (mParent == null) {
        options = transferSpringboardActivityOptions(options);
        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) { 

startActivityForResult方法中又会调用mInstrumentation.execStartActivity方法,我们看到其中有个参数是

mMainThread.getApplicationThread() 

关于ActivityThread曾在 深入理解Android消息机制文章中提到过,ActivityThread是在启动APP的时候创建的,ActivityThread代表应用程序,而我们开发中常用的Application其实是ActivityThread的上下文,在开发中我们经常使用,但在Android系统中其实地位很低的。

Android的main函数就在ActivityThread中

public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
    SamplingProfilerIntegration.start();

    // CloseGuard defaults to true and can be quite spammy.  We
    // disable it here, but selectively enable it later (via
    // StrictMode) on debug builds, but using DropBox, not logs.
    CloseGuard.setEnabled(false);

    Environment.initForCurrentUser(); 

再回到上面方法

mMainThread.getApplicationThread() 

得到的是一个Binder对象,代表Launcher所在的App的进程,mToken实际也是一个Binder对象,代表Launcher所在的Activity通过Instrumentation传给AMS,这样AMS就知道是谁发起的请求。

mInstrumentation.execStartActivity

instrumentation在测试的时候经常用到,instrumentation的官方文档:

http://developer.android.com/intl/zh-cn/reference/android/app/Instrumentation.html

这里不对instrumentation进行详细介绍了,我们主要接着看mInstrumentation.execStartActivity方法

public Instrumentation.ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {
    IApplicationThread whoThread = (IApplicationThread)contextThread;
    if(this.mActivityMonitors != null) {
        Object e = this.mSync;
        synchronized(this.mSync) {
            int N = this.mActivityMonitors.size();

            for(int i = 0; i < N; ++i) {
                Instrumentation.ActivityMonitor am = (Instrumentation.ActivityMonitor)this.mActivityMonitors.get(i);
                if(am.match(who, (Activity)null, intent)) {
                    ++am.mHits;
                    if(am.isBlocking()) {
                        return requestCode >= 0?am.getResult():null;
                    }
                    break;
                }
            }
        }
    }

    try {
        intent.setAllowFds(false);
        intent.migrateExtraStreamToClipData();
        int var16 = ActivityManagerNative.getDefault().startActivity(whoThread, intent, intent.resolveTypeIfNeeded(who.getContentResolver()), token, target != null?target.mEmbeddedID:null, requestCode, 0, (String)null, (ParcelFileDescriptor)null, options);
        checkStartActivityResult(var16, intent);
    } catch (RemoteException var14) {
        ;
    }

    return null;
}

其实这个类是一个Binder通信类,相当于IPowerManager.java就是实现了IPowerManager.aidl,我们再来看看getDefault这个函数

public static IActivityManager getDefault() {
    return (IActivityManager)gDefault.get();
} 

getDefault方法得到一个IActivityManager,它是一个实现了IInterface的接口,里面定义了四大组件的生命周期

public static IActivityManager asInterface(IBinder obj) {
    if(obj == null) {
        return null;
    } else {
        IActivityManager in = (IActivityManager)obj.queryLocalInterface("android.app.IActivityManager");
        return (IActivityManager)(in != null?in:new ActivityManagerProxy(obj));
    }
} 

最终返回一个ActivityManagerProxy对象也就是AMP,AMP就是AMS的代理对象,说到代理其实就是代理模式,关于什么是代理模式以及动态代理和静态代理的使用可以持续关注我,后面会单独写篇文章进行介绍。

AMP的startActivity方法

public int startActivity(IApplicationThread caller, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, ParcelFileDescriptor profileFd, Bundle options) throws RemoteException {
    Parcel data = Parcel.obtain();
    Parcel reply = Parcel.obtain();
    data.writeInterfaceToken("android.app.IActivityManager");
    data.writeStrongBinder(caller != null?caller.asBinder():null);
    intent.writeToParcel(data, 0);
    data.writeString(resolvedType);
    data.writeStrongBinder(resultTo);
    data.writeString(resultWho);
    data.writeInt(requestCode);
    data.writeInt(startFlags);
    data.writeString(profileFile);
    if(profileFd != null) {
        data.writeInt(1);
        profileFd.writeToParcel(data, 1);
    } else {
        data.writeInt(0);
    } 

主要就是将数据写入AMS进程,等待AMS的返回结果,这个过程是比较枯燥的,因为我们做插件化的时候只能对客户端Hook,而不能对服务端操作,所以我们只能静静的看着。(温馨提示:如果文章到这儿你已经有点头晕了,那就对了,研究源码主要就是梳理整个流程,千万不要纠结源码细节,那样会无法自拔)。

AMS处理Launcher的信息

AMS告诉Launcher我知道了,那么AMS如何告诉Launcher呢?

Binder的通信是平等的,谁发消息谁就是客户端,接收的一方就是服务端,前面已经将Launcher所在的进程传过来了,AMS将其保存为一个ActivityRecord对象,这个对象中有一个ApplicationThreadProxy即Binder的代理对象,AMS通ApplicationTreadProxy发送消息,App通过ApplicationThread来接收这个消息。

Launcher收到消息后,再告诉AMS,好的我知道了,那我走了,ApplicationThread调用ActivityThread的sendMessage方法给Launcher主线程发送一个消息。这个时候AMS去启动一个新的进程,并且创建ActivityThread,指定main函数入口。

启动新进程的时候为进程创建了ActivityThread对象,这个就是UI线程,进入main函数后,创建一个Looper,也就是mainLooper,并且创建Application,所以说Application只是对开发人员来说重要而已。创建好后告诉AMS微信启动好了,AMS就记录了这个APP的登记信息,以后AMS通过这个ActivityThread向APP发送消息。

这个时候AMS根据之前的记录告诉微信应该启动哪个Activity,微信就可以启动了。

public void handleMessage(Message msg) {
    ActivityThread.ActivityClientRecord data;
    switch(msg.what) {
    case 100:
        Trace.traceBegin(64L, "activityStart");
        data = (ActivityThread.ActivityClientRecord)msg.obj;
        data.packageInfo = ActivityThread.this.getPackageInfoNoCheck(data.activityInfo.applicationInfo, data.compatInfo);
        ActivityThread.this.handleLaunchActivity(data, (Intent)null);
        Trace.traceEnd(64L); 
ActivityThread.ActivityClientRecord 

就是AMS传过来的Activity

data.activityInfo.applicationInfo 

总结

所得到的属性我们称之为LoadedApk,可以提取到apk中的所有资源,那么APP内部是如何页面跳转的呢,比如我们从ActivityA跳转到ActivityB,我们可以将Activity看作Launcher,唯一不同的就是,在正常情况下ActivityB和ActivityA所在同一进程,所以不会去创建新的进程。

APP的启动流程就是这样了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 互联网又出大瓜,知乎1000w热议,B站后台代码被开源至Github?

    最近,程序员这个圈子可谓热闹非凡,前不久,一个神奇的开源项目996.ICU诞生,几天时间就有超过10w+ star 和 1w+ fork ,可谓是Github...

    Android技术干货分享
  • 揭秘:Android屏幕中你不知道的刷新机制

    之前在整理知识的时候,看到android屏幕刷新机制这一块,以前一直只是知道,Android每16.6ms会去刷新一次屏幕,也就是我们常说的60fpx,那么问题...

    Android技术干货分享
  • App相互唤醒的几种方式

    下文皆使用Client表示操作的App,Server表示需要被唤起的远端App,Server的包名为“com.jxx.server”

    Android技术干货分享
  • APP启动流程解析

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/huangliniqng/article/detail...

    黄林晴
  • torch.as_tensor()

    torch.as_tensor(data, dtype=None,device=None)->Tensor : 为data生成tensor。

    于小勇
  • ES[7.6.x]学习笔记(四)字段类型(mapping)

    在上一节中,我们创建了索引,在创建索引的时候,我们指定了mapping属性,mapping属性中规定索引中有哪些字段,字段的类型是什么。在mapping中,我们...

    小忽悠
  • ES[7.6.x]学习笔记(四)字段类型(mapping)

    在上一节中,我们创建了索引,在创建索引的时候,我们指定了mapping属性,mapping属性中规定索引中有哪些字段,字段的类型是什么。在mapping中,我们...

    小忽悠
  • 14-成绩分类1

    凯茜的老爸
  • Swift基础语法(一)

    Swift语言是苹果公司在Apple WWDC 2014上退出的,用来撰写MacOS和iOS应用程序。

    拉维
  • redis学习(十四)

    SpringBoot 操作数据:spring-data jpa jdbc mongodb redis! SpringData 也是和 SpringBoot 齐...

    崔笑颜

扫码关注云+社区

领取腾讯云代金券