详解Android应用main函数的调用

启动App进程

Activity启动过程的一环是调用ActivityStackSupervisor.startSpecificActivityLocked,如果App所在进程还不存在,首先调用AMS的startProcessLocked:

void startSpecificActivityLocked(ActivityRecord r,
boolean andResume, boolean checkConfig) {
// Is this activity's application already running?
ProcessRecord app = mService.getProcessRecordLocked(r.processName,
r.info.applicationInfo.uid, true);
r.task.stack.setLaunchTime(r);
if (app != null && app.thread != null) {
//...
}
mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
"activity", r.intent.getComponent(), false, false, true);
}

startProcessLocked函数有多个重载,看最长的那个,最重要是调用了Process.start。

if (entryPoint == null) entryPoint = "android.app.ActivityThread";
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);

第一个参数是android.app.ActivityThread,执行的目标,后文用到再说。

Process.start简单地调用了startViaZygote,封装一些参数,再调用zygoteSendArgsAndGetResult。顾名思义,接下来进程的启动工作交给Zygote。

Zygote

Zygote翻译过来意思是“受精卵”,这也是Zygote的主要工作——孵化进程。概括Zygote的主要工作有以下三点,ZygoteInit的main函数也清晰地体现了。Zygote的启动和其他作用另文分析,这次关注Zygote对Socket的监听。

1.registerZygoteSocket:启动Socket的Server端

2.preload:预加载资源

3.startSystemServer:启动system_server进程

public static void main(String argv[]) {
// Mark zygote start. This ensures that thread creation will throw
// an error.
ZygoteHooks.startZygoteNoThreadCreation();
try {
//...
registerZygoteSocket(socketName);
//...
preload();
//...
if (startSystemServer) {
startSystemServer(abiList, socketName);
}
//...
runSelectLoop(abiList);
//...
} catch (MethodAndArgsCaller caller) {
caller.run();
} catch (Throwable ex) {
//...
}
}

最后Zygote执行runSelectLoop,无限循环等待处理进程启动的请求。

private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
fds.add(sServerSocket.getFileDescriptor());
peers.add(null);
while (true) {
StructPollfd[] pollFds = new StructPollfd[fds.size()];
for (int i = 0; i < pollFds.length; ++i) {
pollFds[i] = new StructPollfd();
pollFds[i].fd = fds.get(i);
pollFds[i].events = (short) POLLIN;
}
try {
Os.poll(pollFds, -1);
} catch (ErrnoException ex) {
throw new RuntimeException("poll failed", ex);
}
for (int i = pollFds.length - 1; i >= 0; --i) {
if ((pollFds[i].revents & POLLIN) == 0) {
continue;
}
if (i == 0) {
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
fds.add(newPeer.getFileDesciptor());
} else {
boolean done = peers.get(i).runOnce();
if (done) {
peers.remove(i);
fds.remove(i);
}
}
}
}
}

与Zygote通信使用的IPC方式是socket,类型是LocalSocket,实质是对Linux的LocalSocket的封装。与记忆中socket绑定需要IP和端口不同,LocalSocket使用FileDescriptor文件描述符,它可以表示文件,也可以表示socket。

runSelectLoop维护了两个列表,分别保存文件描述符FileDescriptor和ZygoteConnection,两者一一对应。index=0的FileDescriptor表示ServerSocket,因此index=0的ZygoteConnection用null填充。

在每次循环中,判断fds里哪个可读:

  • 当i=0时,表示有新的client,调用acceptCommandPeer创建ZygoteConnection并保存
  • 当i>0时,表示已建立连接的socket中有新的命令,调用runOnce函数执行

fork进程

runOnce函数非常长,我们只关注进程创建部分。

boolean runOnce() throws ZygoteInit.MethodAndArgsCaller {

//...
try {
//...
pid = Zygote.forkAndSpecialize(parsedArgs.uid, parsedArgs.gid, parsedArgs.gids,
parsedArgs.debugFlags, rlimits, parsedArgs.mountExternal, parsedArgs.seInfo,
parsedArgs.niceName, fdsToClose, parsedArgs.instructionSet,
parsedArgs.appDataDir);
} catch (ErrnoException ex) {
//...
}
try {
if (pid == 0) {
// in child
IoUtils.closeQuietly(serverPipeFd);
serverPipeFd = null;
handleChildProc(parsedArgs, descriptors, childPipeFd, newStderr);
// should never get here, the child is expected to either
// throw ZygoteInit.MethodAndArgsCaller or exec().
return true;
} else {
// in parent...pid of < 0 means failure
IoUtils.closeQuietly(childPipeFd);
childPipeFd = null;
return handleParentProc(pid, descriptors, serverPipeFd, parsedArgs);
}
} finally {
//...
}
}

首先调用了forkAndSpecialize函数,创建进程返回一个pid。

public static int forkAndSpecialize(int uid, int gid, int[] gids, int debugFlags,
int[][] rlimits, int mountExternal, String seInfo, String niceName, int[] fdsToClose,
String instructionSet, String appDataDir) {
VM_HOOKS.preFork();
int pid = nativeForkAndSpecialize(
uid, gid, gids, debugFlags, rlimits, mountExternal, seInfo, niceName, fdsToClose,
instructionSet, appDataDir);
// Enable tracing as soon as possible for the child process.
if (pid == 0) {
Trace.setTracingEnabled(true);
// Note that this event ends at the end of handleChildProc,
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "PostFork");
}
VM_HOOKS.postForkCommon();
return pid;
}

在forkAndSpecialize中核心就是利用JNI调用native的fork函数,调用之前会执行VM_HOOKS.preFork(),调用之后执行VM_HOOKS.postForkCommon()。

VM_HOOKS.preFork()的功能是停止Zygote的4个Daemon子线程的运行,确保Zygote是单线程,提升fork效率。当线程停止之后初始化gc堆。VM_HOOKS.postForkCommon()可以看作是逆操作,关于虚拟机更加深入的内容暂不讨论。

native private static int nativeForkSystemServer(int uid, int gid, int[] gids, int debugFlags,
int[][] rlimits, long permittedCapabilities, long effectiveCapabilities);

nativeForkSystemServer是一个native函数,对应com_android_internal_os_Zygote.cpp的com_android_internal_os_Zygote_nativeForkAndSpecialize,继续调用了ForkAndSpecializeCommon,最核心一句则是调用fork函数。

pid_t pid = fork();

简单回忆fork函数作用,它复制当前进程,属性和当前进程相同,使用copy on write(写时复制)。执行函数后,新进程已经创建,返回的pid=0;对于被复制的进程,返回新进程的pid;出现错误时,返回-1。

因此,执行forkAndSpecialize函数后,runOnce后续的代码分别在两个进程中执行,判断当前的pid,区分是在当前进程还是新进程。

  • pid == 0:新进程,调用handleChildProc
  • pid != 0:当前进程,调用handleParentProc

handleParentProc函数是当前进程进行清理的过程,比较简单。我们重点来看新进程开展工作的handleChildProc函数。

新进程的初始化

private void handleChildProc(Arguments parsedArgs,
FileDescriptor[] descriptors, FileDescriptor pipeFd, PrintStream newStderr)
throws ZygoteInit.MethodAndArgsCaller {
//...
if (parsedArgs.invokeWith != null) {
WrapperInit.execApplication(parsedArgs.invokeWith,
parsedArgs.niceName, parsedArgs.targetSdkVersion,
VMRuntime.getCurrentInstructionSet(),
pipeFd, parsedArgs.remainingArgs);
} else {
RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion,
parsedArgs.remainingArgs, null /* classLoader */);
}
}

两种启动方式:

  • WrapperInit.execApplication
  • RuntimeInit.zygoteInit

第一种的目的不太懂,先挂起,后续分析。

第二种RuntimeInit.zygoteInit,继续调用applicationInit,离我们的目标越来越近了,最终调用到invokeStaticMain。

private static void invokeStaticMain(String className, String[] argv, ClassLoader classLoader)
throws ZygoteInit.MethodAndArgsCaller {
Class<?> cl;
try {
cl = Class.forName(className, true, classLoader);
} catch (ClassNotFoundException ex) {
//...
}
Method m;
try {
m = cl.getMethod("main", new Class[] { String[].class });
} catch (NoSuchMethodException ex) {
//...
}
int modifiers = m.getModifiers();
if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {
throw new RuntimeException(
"Main method is not public and static on " + className);
}
throw new ZygoteInit.MethodAndArgsCaller(m, argv);
}

在这里使用反射获得需要被执行的类和函数,目标函数当然就是main,目标类来自ActivityManagerService.startProcessLocked里的变量entryPoint,前面有说过。

entryPoint = "android.app.ActivityThread"

最后一句执行throw new ZygoteInit.MethodAndArgsCaller(m, argv),让人有些费解。

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

通过上面的分析,容易知道MethodAndArgsCaller就是App的主线程,里面的run方法实现了反射的调用。什么时候触发执行,为什么要这样设计?

既然MethodAndArgsCaller是异常,抛出它肯定某个地方会接收,回顾一路的调用链:

  • ZytoteInit.main
  • ZytoteInit.runSelectLoop
  • ZygoteConnection.runOnce
  • ZygoteConnection.handleChildProc
  • RuntimeInit.zygoteInit
  • RuntimeInit.applicationInit
  • RuntimeInit.invokeStaticMain

看前面的ZytoteInit.main函数,catch了MethodAndArgsCaller异常,直接调用了run函数。注释里解释了为什么要这样做:

This throw gets caught in ZygoteInit.main(), which responds by invoking the exception's run() method. This arrangement clears up all the stack frames that were required in setting up the process.

函数在虚拟机是保存在栈中,每调用一个函数,就将函数相关数据压入栈;执行完函数,将函数从栈中弹出。因此,栈底的就是main函数。

在上面的研究中,新进程创建后,经历一系列函数的调用才到main函数,如果直接调用main函数,调用链中关于初始化的函数会一直存在。为了清理这部分函数,使用了抛出异常的方式,没有捕获异常的函数会马上结束,ZytoteInit.main之上的函数都会结束,达到清理的目的。

最后补充一点,从handleChildProc函数开始,一系列过程调用了ActivityThread的main函数,这不是启动App独有的,后续研究启动SystemServer进程时,你会发现逻辑都是一样。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Caused by: android.os.NetworkOnMainThreadException错误解决办法

    好久不写Android代码手都生了,找出自己之前写的程序发现跑不了了,也没啥特别的错误提示,就看到一句有用的错误Caused by: android.os.NetworkOnMainThreadException,查了下原因上在4.0之后在主线程里面执行Http请求都会报这个错,大概是怕Http请求时间太长造成程序假死的情况吧. 解决办法有两个思路,分别是: 第一种方法:直接忽视,强制使用(强烈不推荐,但是修改简单) 在MainActivity文件的setContentView(R.layout

  • android工程下不能运行java main程序的解决方法

    右击有main方法的类 ===> Run as ===> Run Configurations ===>双击java application ===> 单击有main方法的类 ===>选中classpath选项卡 ===> remove掉Bootstrap Entries下的android.jar ===> 然后点击advanced ===> Add Library ===>JRE System Library ===>next ===>最

  • 详解Android应用main函数的调用

    启动App进程 Activity启动过程的一环是调用ActivityStackSupervisor.startSpecificActivityLocked,如果App所在进程还不存在,首先调用AMS的startProcessLocked: void startSpecificActivityLocked(ActivityRecord r, boolean andResume, boolean checkConfig) { // Is this activity's application alr

  • 详解Android Scroller与computeScroll的调用机制关系

    Android ViewGroup中的Scroller与computeScroll的有什么关系? 答:没有直接的关系 知道了答案,是不是意味着下文就没必要看了,如果说对ViewGroup自定义控件不感兴趣,可以不用看了. 1.Scroller到底是什么? 答:Scroller只是个计算器,提供插值计算,让滚动过程具有动画属性,但它并不是UI,也不是滑动辅助UI运动,反而是单纯地为滑动提供计算. 无论从构造方法还是其他方法,以及Scroller的属性可知,其并不会持有View,辅助ViewGrou

  • 解析在main函数之前调用函数以及对设计的作用详解

    前几天为新员工写一个简单的测试框架,可让他们方便的写测试用例并且执行.期间遇到一个问题就是如何让他们增加测试用例而用不影响测试框架的代码?c++的单件模式可以解决这个问题,但是其中一个难点是要在main之前注册单件.c++可以通过构造函数来实现注册,c如何注册?最后查了下资料,原来可以定义在main之前调用的函数!有了这个特性可以改善c的模块化设计.特性介绍:如果想定义在main函数之前调用的函数,可以在函数的声明之后加上一句"__attribute__((constructor))"

  • 详解Android 消息处理机制

    摘要 Android应用程序是通过消息来驱动的,当Android主线程启动时就会在内部创建一个消息队列.然后进入一个无限循环中,轮询是否有新的消息需要处理.如果有新消息就处理新消息.如果没有消息,就进入阻塞状态,直到消息循环被唤醒. 那么在Android系统中,消息处理机制是怎么实现的呢?在程序开发时,我们经常会使用Handler处理Message(消息).所以可以知道Handler是个消息处理者,Message是消息主体.除此之外还有消息队列和消息轮询两个角色.它们分别是MessageQueu

  • 详解Android Activity的启动流程

    前言 activity启动的流程分为两部分:一是在activity中通过startActivity(Intent intent)方法启动一个Activity:二是我们在桌面通过点击应用图标启动一个App然后显示Activity:第二种方式相较于第一种方式更加全面,所以本文会以第二种流程来分析. 简要 我们手机的桌面是一个叫做Launcher的Activity,它罗列了手机中的应用图标,图标中包含安装apk时解析的应用默认启动页等信息.在点击应用图标时,即将要启动的App和Launcher.AMS

  • 详解Android系统启动过程

    计算机是如何启动的 计算机的硬件包括:CPU,内存,硬盘,显卡,显示器,键盘鼠标等输入输出设备.所有的软件都是存放在硬盘中,程序执行时,需要将程序从硬盘上读取到内存中,然后加载到CPU中来运行.当按下开机键时,内存中什么都没有,因此需要借助某种方式,将操作系统加载到内存中,而完成这项任务的就是BIOS. 引导阶段 BIOS:BIOS是主板芯片上的一个程序,计算机通电后,第一件事情就是读取BIOS. BIOS首先进行硬件检测,检查计算机硬件能否满足运行的基本条件.如果硬件出现问题,主板发出不同的蜂

  • 详解Android中的Service

    Service简介: Service是被设计用来在后台执行一些需要长时间运行的操作. Android由于允许Service在后台运行,甚至在结束Activity后,因此相对来说,Service相比Activity拥有更高的优先级. 创建Service: 要创建一个最基本的Service,需要完成以下工作:1)创建一个Java类,并让其继承Service 2)重写onCreate()和onBind()方法 其中,onCreate()方法是当该Service被创建时执行的方法,onBind()是该S

  • 详解C语言gets()函数与它的替代者fgets()函数

    在c语言中读取字符串有多种方法,比如scanf() 配合%s使用,但是这种方法只能获取一个单词,即遇到空格等空字符就会返回.如果要读取一行字符串,比如: I love BIT 这种情况,scanf()就无能为力了.这时我们最先想到的是用gets()读取. gets()函数从标准输入(键盘)读入一行数据,所谓读取一行,就是遇到换行符就返回.gets()函数并不读取换行符'\n',它会吧换行符替换成空字符'\0',作为c语言字符串结束的标志. gets()函数经常和puts()函数配对使用,puts

  • 详解Android StrictMode严格模式的使用方法

    Android 2.3提供一个称为严苛模式StrictMode的调试特性,Google称该特性已经使数百个Android上的Google应用程序受益.那它都做什么呢?它将报告与线程及虚拟机相关的策略违例.一旦检测到策略违例policy violation,你将获得警告,其包含了一个栈trace显示你的应用在何处发生违例.你可以强制用警告代替崩溃crash,也可以仅将警告计入日志让你的应用继续执行.StrictMode是一个十分有用的类,它可以很方便地应用于检查Android应用程序的性能和存在的

  • 详解Android 基于TCP和UDP协议的Socket通信

    本来想讲一下基础的网络通信方面的知识点,发现太枯燥乏味了,不过笔试中也经常会问到这方面的问题,所以关于通信方面的知识点,小编会放到面试中去,因为实战中也就面试会用到这方面知识点 Android与服务器的通信方式主要有两种,一是Http通信,一是Socket通信.两者的最大差异在于,http连接使用的是"请求-响应方式",即在请求时建立连接通道,当客户端向服务器发送请求后,服务器端才能向客户端返回数据. 而Socket通信中基于TCP/IP协议的通信则是在双方建立起连接后就可以直接进行数

随机推荐