软件编程
位置:首页>> 软件编程>> Android编程>> 详解Android应用main函数的调用

详解Android应用main函数的调用

作者:展翅而飞  发布时间:2021-09-30 12:01:19 

标签: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进程时,你会发现逻辑都是一样。

来源:https://www.jianshu.com/p/302fe75d6778

0
投稿

猜你喜欢

  • 一、微服务简介 Ⅰ、我对微服务的理解微服务是软件开发的一种架构方式,由单一的应用小程序构成的小服务;一个软件系统由多个服务组成;在微服务中,
  • 本文实例讲述了JAVA获取任意http网页源代码。分享给大家供大家参考,具体如下:JAVA获取任意http网页源代码可实现如下功能:1. 获
  • 什么是栈和队列栈如果用数组模拟的话是类似于一个U形桶状堆栈空间,地下是封口的,只能从顶部一个地方进出,它的进出都是有顺序的,看下图:如果是进
  • Lombok简介和其他语言相比,Java经常因为不必要的冗长被批评。Lombok提供了一系列注解用以在后台生成模板代码,将其从你的类中删除,
  • 本文实例讲述了Java对XML文件增删改查操作。分享给大家供大家参考,具体如下:xml文件:<?xml version="1
  • 前言数据驱动测试是相同的测试脚本使用不同的测试数据执行,测试数据和测试行为完全分离。数据驱动是做自动化测试中很重要的一部分,数据源的方案也是
  • 前言Unity在运行时可以将一些物体进行合并,从而用一个绘制调用来渲染他们。这一操作,我们称之为“批处理”,能得到越好的渲染性能。Unity
  • Java关于Map的四种取值方式map的主要作用是什么?可以通过创建一个map的实现类 来存放 数据 值 和值的描述 也可以通过描述去取得数
  • 这篇文章主要介绍了SpringBoot2整合activiti6环境搭建过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定
  • Android Dialog 动画实例详解动画描述: 动画与底部菜单一样出现和消失制作过程:1. 创建两个动画文件window_in.xml
  • 在之前我们分析了Android6.0系统在启动时安装应用程序的过程,这些应用程序安装好之后,Launcher应用就负责把它们在桌面上展示出来
  • Java中的static关键字可以用于修饰变量、方法、代码块和类,还可以与import关键字联合使用,使用的方式不同赋予了static关键字
  • 本文实例讲述了C#简单输出日历的方法。分享给大家供大家参考。具体如下:用C#输出日历,此功能可用于Ajax方式列出计划日程相关的内容,由于是
  • 目录一、事出有因二、解决方案困境三、柳暗花明,终级解决方案第一种实现方案第二种实现方案第三种实现方案四、引发的思考一、事出有因最近有一个场景
  • 这篇文章主要介绍了Springboot如何设置静态资源缓存一年,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,
  • paras.xml文件<?xml version="1.0" encoding="UTF-8"
  • SpringMVC在接收集合请求参数时,需要在Controller方法的集合参数里前添加@RequestBody,而@RequestBody
  • 一、关于堆JDK1.8中的PriortyQueue(优先级队列)底层使用了堆的数据结构,而堆实际就是在完全二叉树的基础之上进行了一些元素的调
  • 本文实例为大家分享了Android PopupWindow增加半透明蒙层的具体代码,供大家参考,具体内容如下先看效果图:实现代码:BaseP
  • 本文实例讲述了C#直线的最小二乘法线性回归运算方法。分享给大家供大家参考。具体如下:1.Point结构在编写C#窗体应用程序时,因为引用了S
手机版 软件编程 asp之家 www.aspxhome.com