前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >详解Android应用main函数的调用

详解Android应用main函数的调用

作者头像
砸漏
发布2020-11-04 10:00:59
7620
发布2020-11-04 10:00:59
举报
文章被收录于专栏:恩蓝脚本恩蓝脚本

启动App进程

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

代码语言:javascript
复制
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。

代码语言:javascript
复制
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进程

代码语言:javascript
复制
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,无限循环等待处理进程启动的请求。

代码语言:javascript
复制
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函数非常长,我们只关注进程创建部分。

代码语言:javascript
复制
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。

代码语言:javascript
复制
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()可以看作是逆操作,关于虚拟机更加深入的内容暂不讨论。

代码语言:javascript
复制
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函数。

代码语言:javascript
复制
pid_t pid = fork();

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

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

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

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

新进程的初始化

代码语言:javascript
复制
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。

代码语言:javascript
复制
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,前面有说过。

代码语言:javascript
复制
entryPoint = "android.app.ActivityThread"

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

代码语言:javascript
复制
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进程时,你会发现逻辑都是一样。

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-09-11 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档