android 正在启动分析,Android P APP冷启动过程全解析(之二)

我们在第一篇文章Android P APP冷启动过程全解析(之一)中讲述

了APP冷启动的请求阶段,这一篇主要讲述进程启动阶段,包括以下子阶段:

7.AMS请求创建进程

8.Zygote fork进程

9.初始化 Runtime

10.注册进程到system_server

11.创建application

下面我们来逐个分析:

7、AMS请求创建进程

在resume目标activity的过程中,如果目标activity所在的进程还没启动,那么AMS会通过startSpecificActivityLocked来启动进程:

ASS.startSpecificActivityLocked(...)

// 再次查找进程有没有启动

ProcessRecord app = mService.getProcessRecordLocked(r.processName,

r.info.applicationInfo.uid, true);

if (app != null && app.thread != null) {

realStartActivityLocked(...); // 这里会真正地startActivity

}

AMS.startProcessLocked(r.processName,...)

app = newProcessRecordLocked(info, processName, isolated, isolatedUid);

AMS.startProcessLocked(app,hostingType,...)

// 新进程的进入java世界的入口类

final String entryPoint = "android.app.ActivityThread";

AMS.startProcess(...)

Process.start(entryPoint,...)

ZygoteProcess.start(...)

ZygoteProcess.startViaZygote(...)

zygoteSendArgsAndGetResult(openZygoteSocketIfNeeded(abi), argsForZygote)

AMS.handleProcessStartedLocked(app,...)

整个过程并不是很复杂,首先AMS会创建进程对应的实体ProcessRecord,然后指定新进程起来以后进入java世界的入口点:

final String entryPoint = "android.app.ActivityThread";

即android.app.ActivityThread类,当然还有很多其他的参数,例如processName,uid,targetSdkVersion等。

然后AMS委托Process类来新建进程,而最后干活的是ZygoteProcess,这个类维护着AMS和Zygote进程之间的通讯,通讯的方式是LocalSocket。

最后,ZygoteProcess会把所有启动参数都通过socket的写入流发送给Zygote。这样子,一个创建进程的请求就已经成功发送了。理论上,只要知道Zygote的本地地址,APP也是可以通过向Zygote发送请求创建进程的。

请求发送以后,ZygoteProcess会马上从socket中读int类型的数据,这个int类型的数据便是Zygote返回的新建进程的pid,只有pid>0,那么就可以认为新进程创建成功了!

之后,AMS会调用handleProcessStartedLocked来处理后面的事情,并添加记录,等待新进程起来以后attachApplication。

8、Zygote fork进程

Zygote,含义为受精卵,是人的第一个细胞,其他细胞都是由其分裂出来的。对于Android,它是第一个java进程,其他所有Java都是由其fork出来的。Java进程也是普通的Linux进程,只不过它运行着Java虚拟机,所以称为Java进程。一般来说fork都是Linux进程的行为,Java进程是不会调用fork的,Zygote进程起来的时候,已经启动了ART虚拟机,其他参数也初始化好了。Android用这种fork的方式,克隆出一个和原来进程几乎完全相同的进程,新进程不用在进行初始化操作,只需要修改一些关键参数就可以了,这样子就极大地加快了新建进程的速度。

Zygote进程有init进程fork出来,在其初始化了以后,会运行runSelectLoop来等待客户端进行socket连接,如下:

ZygoteServer.runSelectLoop:

while (true) { // 死循环

ZygoteConnection connection = acceptCommandPeer(abiList); // 获取连接

final Runnable command = connection.processOneCommand(this); // 执行fork请求

if (mIsForkChild) {

return command; //只有子进程才会返回

} else {

connection.closeSocket(); // 关闭连接,等待下一个请求

} finally {

mIsForkChild = false; // 重置标志

}

}

ZygoteConnection.processOneCommand:

args = readArgumentList(); // 读取请求方传过来的参数

parsedArgs = new Arguments(args); // 解析参数

pid = Zygote.forkAndSpecialize(...)

if (pid == 0) {

// 子进程

zygoteServer.setForkChild(); // 就是将mIsForkChild置为true

zygoteServer.closeServerSocket();

IoUtils.closeQuietly(serverPipeFd);

return handleChildProc(...) // 返回command

} else {

...

return null; // 返回null

}

注:上面代码是伪代码(其实本人所以贴的代码都是伪代码~)。

runSelectLoop是一个死循环,ZygoteServer在这个循环中一直等待客户端连接,调用acceptCommandPeer获取连接,然后通过processOneCommand进行fork。processOneCommand函数调用native方法fork出一个子进程,父进程和子进程会分两次返回。pid = 0 说明是子进程,将mIsForkChild置为true,然后调用handleChildProc进行初始化,否则是父进程。最终子进程会返回一个Runnable,该Runable的作用就是反射调用android.app.ActivityThread的main方法。

9、初始化 Runtime

接上面,子进程被fork出来以后,首先调用了handleChildProc来进行初始化:

ZygoteConnection.handleChildProc(...)

Process.setArgV0(parsedArgs.niceName);

ZygoteInit.zygoteInit(...)

RuntimeInit.commonInit();

Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler(...))

RuntimeInit.applicationInit(targetSdkVersion, argv, classLoader)

VMRuntime.getRuntime().setTargetHeapUtilization(0.75f);

VMRuntime.getRuntime().setTargetSdkVersion(targetSdkVersion);

findStaticMain(args.startClass,) // 反射查找main方法

handleChildProc函数里面进行了一些最基本的初始化操作,包括:

设置UncaughtExceptionHandler,默认是KillApplicationHandler,即默认情况下未捕捉的异常会使我们的APP挂掉

通过setTargetHeapUtilization设置Java虚拟机的堆内存利用率的百分比,当实际的利用率偏离这个百分比的时候,虚拟机会在GC的时候调整堆内存大小,让实际占用率向个百分比靠拢 。默认值是0.75。

设置targetSdkVersion,这个值会限制我们对某些API的调用。

此外还有一些其他的初始化,在这之后,会通过findStaticMain来发射查找main方法,封装成runnable返回并最终被调用。

反射后会进入到ActivityThread的main函数中,继续做一些初始化工作:

ActivityThread.main(String[] args)

Environment.initForCurrentUser(); // 初始化用户环境

Looper.prepareMainLooper(); // 准备MainLooper

ActivityThread thread = new ActivityThread();

thread.attach(false, startSeq);//告诉AMS我已经起来啦,startSeq是标识

Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited"); // 永远不应该走到这里

ActivityThread的main函数主要做了两件事:

调用attach,告诉AMS进程应启动完毕,可以进行其他事情了。

初始化主线程的looper并loop。

这里重点讲第2点,第1点在下一节讲述。我们都知道主线程是默认实现了looper的,就是这个时候实现的。调用了Looper.loop()后,整个线程就陷入死循环,常规的调用流程已经结束了。后面主线程所作的任何事情都是通过往MainThreadHandler中发送消息来完成的,所以说Android系统是基于消息驱动的。

10、注册进程到system_server

上面说过,这个过程是通过ActivityThread.attah函数来完成的,如下:

ActivityThread.attah(...)

final IActivityManager mgr = ActivityManager.getService();

mgr.attachApplication(mAppThread, startSeq);

AMS.attachApplication(...)

AMS.attachApplicationLocked(...)

final ProcessRecord pending = mPendingStarts.get(startSeq);

AppDeathRecipient adr = new AppDeathRecipient(app, pid, thread); // 绑定死亡通知

thread.asBinder().linkToDeath(adr, 0);

mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); // 移除进程启动超时消息

ApplicationThread.bindApplication(..)

ASS.attachApplicationLocked(...) // 检查有没有待启动的activity

ActiveServices.attachApplicationLocked(...) // 检查有没有待启动的service

AMS.sendPendingBroadcastsLocked // 检查有没有待启动的receiver

因为进程一般都是由AMS请求的,所以这里相当于一个回调,为了防止其他进程非法attach到AMS,保证安全,需要验证身份。这个验证就是通过startSeq来完成的,这个startSeq其实是由AMS传过去的,现在它在Zygote和APP进程中转了一圈又回来了。AMS会根据startSeq从mPendingStarts取出待启动的进程的ProcessRecord ,如果ProcessRecord 为空,那么说明进程启动有误或者这个进程不是由AMS启动的,AMS会马上杀死该进程。

在验证进程启动没问题之后,AMS会做一下3件事情:

为进程绑定死亡通知AppDeathRecipient,这样,进程被杀的时候,AMS会通过AppDeathRecipient来进行清理工作。

通知APP执行bindApplication,即创建Application,这个会在下一节中讲述

检查有没有四大组件等待着在该进程中运行,如果有,继续执行四大组件。不过这里只有Activity、Service和BroadCastReceiver三种组件,ContentProvider的发布会在bindApplication的时候进行。

11、创建application

上一节说到,进程启动没问题以后,AMS会通知APP创建Appication,主要涉及到的函数是ActivityThread.handleBindApplication:

Process.setStartTimes(...)

mConfiguration = new Configuration(data.config);

//获取该应用的packageInfo

data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);

ImageDecoder.sApiLevel = data.appInfo.targetSdkVersion;

//dpi相关

mCurDefaultDisplayDpi = data.config.densityDpi;

Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT);

// 设置时间格式

DateFormat.set24HourTimePref(is24Hr);

// 设置strictMode相关的东西

StrictMode.initThreadDefaults(data.appInfo);

penaltyDeathOnNetwork(...)

StrictMode.initVmDefaults(data.appInfo);

penaltyDeathOnFileUriExposure(...)

app = data.info.makeApplication(...)

installContentProviders(app, data.providers); // 发布所有ContentProvider

mInstrumentation.callApplicationOnCreate(app); // 回调Application.onCreate

创建application的过程主要是初始化APP的Context,各种资源的初始化,包括设定显示相关的configuration、初始化packageInfo、设置默认的Dpi、设置时间格式、设置strictMode,设置字体资源等等,为以后四大组件的运行提供运行环境。

其中packageInfo包括app的所有资源,例如data目录,res的目录,lib目录的。而strictMode会设定不能在主线程中访问网络,也不能在Uri中显式加入file。然后创建Application,用它去发布所有的ContentProvider,才回调Application.onCreate,所以不要惊讶,ContentProvider发布是比Application的回调都要早。至此,APP的运行所需要的所有资源已经准备完毕,

四大组件可以运行了。

第二阶段总结

第二阶段主要是通过Zygotefork出来APP运行的实体——进程,然后初始化APP运行的环境好上下文,准备好各种资源,为四大组件的运行做好准备。从某种意义上来说,进程也不过是四大组件运行的必须资源罢了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值