好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Android启动这些事儿,你都拎得清吗?

前言

作为一个应用工程师,除了写一些业务代码,性能优化也是我们需要关注的点!

如果想要去做启动优化,那么去了解启动过程就是一个绕不过去的坎儿。

那么除了关于启动过程的那些代码,我们还应该去知道什么呢?

一、多进程那些事儿

在大家很早学习 Android 的时候,想必就知道,每一个 Android App 就代表着一个进程。

1. 为什么要开启进程?

为什么要开启一个新的进程呢?

在 Linux 中,线程和进程可没多大区别,内核并没有给线程准备特别的调度算法或者特殊的数据结构,相反,线程被视为一个与其他进程共享某些资源的进程。

看到了吗?线程之间可是会共享资源的,比如地址空间,你肯定不想发送微信的时候,其他应用都能知道,你发了什么吧。

2. 如何开启一个应用进程

想要开启一个应用进程可不容易,大家可能都听说过,Linux 进程的创建都通过 fork() 方法,Android 自然也是同理,所有的应用进程的父进程都是 Zygote 进程,它是 Android 系统的两大之主进程之一。

image.png

点击 App 图标后, Launcher 进程会通知 AMS(ActivityManagerService) , AMS 就会调用自身的 startProcessLocked 方法,接着会调用 Process#start() 方法,透过层层嵌套,我们可以看到连接 Socket 的地方:

 public static ZygoteState connect(LocalSocketAddress address) throws IOException {
    //...
    final LocalSocket zygoteSocket = new LocalSocket();
    try {
        zygoteSocket.connect(address);
        zygoteInputStream = new DataInputStream(zygoteSocket.getInputStream());
        zygoteWriter = new BufferedWriter(new OutputStreamWriter(zygoteSocket.getOutputStream()), 256);
    } catch (IOException ex) {
        //...
    }
    //...
    return new ZygoteState(zygoteSocket, zygoteInputStream, zygoteWriter, Arrays.asList(abiListString.split(",")));
} 

Zygote 进程会开启 Socket 等待连接,连上了以后,最终会触发它的 Zygote#forkAndSpecialize() 方法:

 static int forkAndSpecialize(int uid, int gid, int[] gids, int runtimeFlags,
                             int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
                             int[] fdsToIgnore, boolean startChildZygote, String instructionSet, String appDataDir,
                             boolean isTopApp, String[] pkgDataInfoList, String[] whitelistedDataInfoList,
                             boolean bindMountAppDataDirs, boolean bindMountAppStorageDirs) {
    ZygoteHooks.preFork();
    int pid = nativeForkAndSpecialize(
            uid, gid, gids, runtimeFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
            fdsToIgnore, startChildZygote, instructionSet, appDataDir, isTopApp,
            pkgDataInfoList, whitelistedDataInfoList, bindMountAppDataDirs,
            bindMountAppStorageDirs);
    //...
    ZygoteHooks.postForkCommon();
    return pid;
} 

可以看到,一个新的进程就创建好了,并返回进程ID。

应用进程创建完了,应用还得和 AMS 、 PMS 等其他服务或者进程通信啊,所以还得创建 Binder 线程池,不然就没法和外界交流。

接下来,可到了重点部分了,系统会调用 RuntimeInit#findStaticMain() 方法,该方法最终会调用一个 Runnable 方法:

 static class MethodAndArgsCaller implements Runnable {
    //...
    public void run() {
        try {
            mMethod.invoke(null, new Object[] { mArgs });
        } catch (IllegalAccessException ex) {
            // ...
        } catch (InvocationTargetException ex) {
            // ...
        }
    }
} 

mMethod 就是 ActivityThread#main() 方法,参数都是之前传递进去的,最终通过反射去启动我们的 ActivityThread 。

3. Android特有的多进程传输方式

你以为要分析 ActivityThread#main() ,不,我们没有。

上面我们谈到了 Socket,但 Binder 才是 Android 中特有的多进程通信方式,并且在下面会被我多次提及。

往简单了说,因为 Android 中的进程的内存地址是各自独立的,但是它们都得通过内核的限制与硬件层进行交流。

如果不存在内核,就会发生很多糟糕的情况,比如说,一共有8GB的运行内存,起点读书说我全要了,剩下的其他应用还怎么玩!

生气

多进程通信也得通过内核,分给进程的一般都是虚拟地址,每一段虚拟地址都有实际的物理地址与之对应。当发生进程通信时,一般都会将数据从进程A的用户空间(用户可以操作的内存空间)复制到内核的缓冲区,再从内核的缓冲区复制到进程B的用户空间。

多进程通信

但是 Binder 就不同了, Binder 会通过 mmap 将内核中的虚拟地址和用户空间的虚拟地址做映射,如果进程B使用了 Binder ,当数据从进程A复制到内存缓存区的时候,整个通信过程就结束了,因为使用了内存映射,数据直接映射到了进程B的内存空间。

Binder通信

看到这儿,相信大家对 Binder 有了基础的印象了,再来看一下它的使用流程即可。

Binder使用流程

Binder的实现是典型的 C\S 的结构:

Client :服务的请求方。 Server :服务的提供方。 Service Manager :为 Server 提供 Binder 的注册服务,为 Client 提供 Binder 的查询服务, Server 、 Client 和 Service Manager 的通讯都是通过Binder。 Binder驱动 :负责Binder通信机制的建立,提供一系列底层支持。

从上图中, Binder 通信的过程是这样的:

Server 在 Service Manager 中注册: Server 进程在创建的时候,也会创建对应的Binder实体,如果要提供服务给 Client ,就必须为 Binder 实体注册一个名字。 Client 通过 Service Manager 获取服务: Client 知道服务中 Binder 实体的名字后,通过名字从 Service Manager 获取 Binder 实体的引用。 Client 使用服务与 Server 进行通信: Client 通过调用 Binder 实体与 Server 进行通信。

以上的知识可能有点浅显,权当抛砖引玉,如果想要深入的学习,大家可自行了解。

看到这里,相信大家可能会有一个疑惑, Binder 是 Android 中特有的高效跨进程传输方式,Zygote 为什么没有选择 Binder 而是选择 Socket 作为跨进程通信方式呢?

其实我也有点疑惑,大家可以看看下面的解答:

❝《知乎:zygote进程为什么不启用binder机制?》

二、dex那些事儿

我们的知道,解压一个应用的 apk 文件,里面可能会有若干个 dex 文件。

apk解压以

后它是经工具软件将所有的 Java 字节码文件(.class)转化形成的。

Dex文件

这些 dex 文件怎么就可以直接运行了呢?

转化过程

从图片中我们也可以看出来了,需要根据情况讨论,因为 Android 的虚拟机分为 Dalvik 和 ART。

对于 Dalvik,安装过程会提取出 .odex ,不过这个只是优化过的字节码文件,最后,在运行过程,Dalvik 还要通过 Jit(即时编译) 还需要转化成机器可以识别的机器码。

对于 ART,它会在安装过程中,解析成 .oat 文件,这个文件装的可不是字节码,而是实实在在的机器码,少了 Jit,整个运行速度和启动速度都大大加快了。

不过呢,在ART中,整个安装过程和应用升级都比较耗时,所以,在 Android 7.0 以后,既采用了 JIT 又采用了 AOT,简单来说就是:

第一次启动采用 JIT,将热点函数包含的 dex 解析成字节码。 等到应用空闲的时候,再执行 AOT 过程,进行编译。

经过了这一大串, dex 文件就变成了机器可以识别的机器码了,这里再提醒一下,Android 系统可不是直接识别 .class 文件的,它需要将 .dex 映射到内存中,并通过 PathClassLoader 和 DexClassLoader 将 APP 中的类加载到内存里。

三、进入Android启动过程

从上面我们已经知道了,我们的应用启动入口在 ActivityThread#main() 方法。

1. 启动过程中的通信机制

在正式了解启动过程之前,我想我们还得了解一下启动过程的通信机制,这也是启动过程的主线,虽然我知道你们已经迫不及待了!

等待

稍微了解过启动过程的同学应该都知道 ActivityThread 、 ApplicationThread 和 ActivityManagerService 这三个角色:

角色 ActivityThread (下称AT):应用的启动入口,进行应用启动的工作。 ActivityManagerService (下称AMS):Android系统中最重要的系统服务之一,负责四大组件的启动、管理和调度,同时也管理应用进程。 ApplicationThread AMS 与 AT 通信的桥梁,AT 的内部类。

上面我们了解过 Android 系统的两大支柱进程之一的 Zygote 进程 ,另外一个就是 SystemServer 进程。

AMS 就处于 SystemServer 进程,还有很多大家耳熟能详的服务都在这里边, AMS 基于 Binder 实现的,所以 ActivityThread 能够在应用进程联系远在 SystemServer 进程的 AMS 。

那 AMS 如何联系 ActivityThread 呢?

巧了,也是 Binder ,它的实现类是 ApplicationThread ,不过它是一个匿名 Binder (没有在 Server Manager 注册的 Binder ),之所以这么设计,我想更多的是基于安全方面考虑的。

ActivityThread通信.png

AT 和 AMS 的通信正如图上所描述的那样。

2. 入口ActivityThread

终于到了代码解释环节了!

进入 main 方法:

 public static void main(String[] args) {
    //...
    Looper.prepareMainLooper();
    //... 省略

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

    //...
    Looper.loop();
} 

重点是 ActivityThread#attach() 方法, system 变量传递的是 false :

 private void attach(boolean system, long startSeq) {
    // ...
    if (!system) {
        //. ...
        RuntimeInit.setApplicationObject(mAppThread.asBinder());
        final IActivityManager mgr = ActivityManager.getService();
        try {
            mgr.attachApplication(mAppThread, startSeq);
        } catch (RemoteException ex) {
            // ...
        }
        // Watch for getting close to heap limit.
        BinderInternal.addGcWatcher(new Runnable() {
            @Override public void run() {
                //...
             }
        });
    } else {
        //...
    }

    //...
} 

我们可以看到这个方法先是直接获取 AMS ,之后就直接将 ApplicationThread 对象传了过去。

2. 进入AMS

点进 ActivityManagerService#attachApplication() 方法,该方法又调用了 ActivityManagerService#attachApplicationLocked() 方法,简化一下:

 private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {
    ProcessRecord app;
    if (pid != MY_PID && pid >= 0) {
        synchronized (mPidsSelfLocked) {
            app = mPidsSelfLocked.get(pid); // 根据pid获取ProcessRecord
        }
    }
    ...

    ApplicationInfo appInfo = app.instrumentationInfo != null
            ? app.instrumentationInfo : app.info;

    thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
            profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
            app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
            isRestrictedBackupMode || !normalMode, app.persistent,
            new Configuration(mConfiguration), app.compat,
            getCommonServicesLocked(app.isolated),
            mCoreSettingsObserver.getCoreSettingsLocked());
    ...

    return true;
} 

注意一下,上面的方法在 AMS 中,就直接调用了 ApplicationThread#bindApplication() 方法了,这可是跨进程。

之前我们也提过了, ApplicationThread 也是 Binder ,并且我们也没有发现 ApplicationThread 向 Service Manager 注册 Binder 的任何代码,这也更加证实它是一个匿名 Binder ,不信,我们可以看一下 ApplicationThread 的代码:

 private class ApplicationThread extends IApplicationThread.Stub {
    //...
}

public class ActivityManagerService extends IActivityManager.Stub
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
    // ...    
} 

ApplicationThread 和 ActivityManagerService 貌似都改成 AIDL 去实现 Binder 了,没毛病!

3. 进入ApplicationThread

ApplicationThread 自己并没有处理,而是交给了 H 的实例,从下面的代码中,我们就能看出 H 是一个 Handler :

 public final void bindApplication(
        //... 参数省略
        ) {
    //...
    AppBindData data = new AppBindData();
    //...
    sendMessage(H.BIND_APPLICATION, data);
} 

H 也是 ActivityThread 的内部类,于是直接调用 ActivityThread#handleBindApplication() 方法,简化一下:

 private void handleBindApplication(AppBindData data) {
    mBoundApplication = data;
    Process.setArgV0(data.processName);//设置进程名
    // ...
    //获取LoadedApk对象
    data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
    // ...
    // 创建ContextImpl上下文
    final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
    //...
    try {
        // 此处data.info是指LoadedApk, 通过反射创建目标应用Application对象
        Application app = data.info.makeApplication(data.restrictedBackupMode, null);
        // 初始化ContentProvider
        installContentProviders(app, data.providers);
        mInitialApplication = app;
        // ...
        mInstrumentation.onCreate(data.instrumentationArgs);
        //回调onCreate 
        mInstrumentation.callApplicationOnCreate(app);
    } finally {
        StrictMode.setThreadPolicy(savedPolicy);
    }
} 

我们可以看见,主要做了以下几件事:

创建 ContextImpl 创建 Application 初始化 ContentProvider 回调 Application#onCreate()

4. 创建Application

data.info 的类型是 LoadedApk ,它保存了很多跟 Apk 相关的信息。

进入 LoadedApk#makeApplication() 方法,这是 Application 的创建方法,注意第二个参数传了一个 null :

 public Application makeApplication(boolean forceDefaultAppClass,
                                   Instrumentation instrumentation) {
    Application app = null;
    //...
    try {
        final java.lang.ClassLoader cl = getClassLoader();
        // ...
        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        // ...
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
    } catch (Exception e) {
        //...
    }
    if (instrumentation != null) {
        // instrumentation为空,所以走不进这个方法
        try {
            instrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            //...
        }
    }
    return app;
} 

实际的创建 Application 方法又交给了 mActivityThread.mInstrumentation ,它的类型 Instrumentation ,这个类可是一个大管家,无论是 Application 还是 Activity ,最后都会交给它来处理:

 public Application newApplication(ClassLoader cl, String className, Context context) {
    // 利用反射创建的Application
    Application app = getFactory(context.getPackageName())
            .instantiateApplication(cl, className);
    app.attach(context);
    return app;
} 

Application 创建完成后还会调用 Application#attach() 方法,在这个方法,我们看到了熟悉的方法:

 final void attach(Context context) {
    attachBaseContext(context);
    // ...
} 

常用的方法 Application#attachBaseContext() 就是这个时候回调的。

5. 初始化ContentProvider

这里没什么好说的,只有一点, ContentProvider 的初始化时机要早于 Application#onCreate() 方法。

因为之前我就出现过在 ContentProvider#onCreate() 调用了某个 SDK,而这个 SDK 在 Application#onCreate() 才回完成初始化,结果就闪退了。

表情

6. 调用 Application#oncreate()

回到步骤3中的方法, mInstrumentation 的类型是 Instrumentation ,它也是通过反射创建的,最终会执行 Instrumentation#callApplicationOnCreate 方法:

 public void callApplicationOnCreate(Application app) {
    app.onCreate();
} 

在这个方法中,就可以看到我们的 Application#onCreate() 方法得以执行,这也是我们通常用来初始化 SDK 的地方。

在这完成以后,就会通过 AMS 启动第一个 Activity ,还是同样的通信方式,就不和大伙继续展开了。

总结

看到这儿,相信大伙儿对应用已经有了初步的认识,如果有什么争议的地方,评论区见!

再谈一些关于知识点的事,可能大家会认为学习一个个知识点是比较枯燥的事。

比如去了解Linux内核的一些知识、去了解Apk的组成、去看应用的启动流程,但是当你把这些看似不连贯的点都能够连贯起来的时候,你就会发现这还是挺有意思的!

参考文章

❝《Dalvik,ART与ODEX相爱相生》 《说一说Android的Dalvik,ART与JIT,AOT》 《Android AMS 与 APP 进程通信》 《理解Application创建过程》 《深入理解Android虚拟机》 《Linux内核设计与实现》

查看更多关于Android启动这些事儿,你都拎得清吗?的详细内容...

  阅读:53次