服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Android - 详解Android应用main函数的调用

详解Android应用main函数的调用

2022-10-19 14:26展翅而飞 Android

Android常识,App主线程初始化了Looper,调用prepare的地方是ActivityThread.main函数。问题来了,App的main函数在哪儿调用,下面我们来一起学习一下吧

启动App进程

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

?
1
2
3
4
5
6
7
8
9
10
11
12
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。

?
1
2
3
4
5
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进程

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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,无限循环等待处理进程启动的请求。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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函数非常长,我们只关注进程创建部分。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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()可以看作是逆操作,关于虚拟机更加深入的内容暂不讨论。

?
1
2
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函数。

?
1
pid_t pid = fork();

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

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

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

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

新进程的初始化

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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,前面有说过。

?
1
entryPoint = "android.app.ActivityThread"

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

?
1
2
3
4
5
6
7
8
9
10
11
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

延伸 · 阅读

精彩推荐
  • AndroidAndroid编程之蓝牙测试实例

    Android编程之蓝牙测试实例

    这篇文章主要介绍了Android编程之蓝牙测试,较为详细的分析了Android蓝牙测试的相关运行环境与调试技巧,非常具有实用价值,需要的朋友可以参考下...

    jdh995722021-03-16
  • Androidandroid @override 报错解决方案

    android @override 报错解决方案

    android @override 报错:就是说Java 1.5的编译器默认对父类的方法进行覆盖,采用@Override进行说明;但1.6已经扩展到对接口的方法;所以如果还是以Java 1.5的编译...

    Android教程网11662020-12-26
  • AndroidAndroid动态布局小结

    Android动态布局小结

    android动态布局相比静态布局,动态布局不用再将xml转变了布局代码,提高了一定的效率,本篇文章给大家介绍android动态布局小结,对android动态布局相关知...

    bluejww4862021-05-08
  • Androidandroid音乐播放器监听电话状态实现代码

    android音乐播放器监听电话状态实现代码

    在手机上播放音乐的时候,我们希望监听电话的状态,当然在MID上没有电话功能,不需要监听...

    Android开发网9352021-02-06
  • AndroidAndroid中Service服务详解(一)

    Android中Service服务详解(一)

    这篇文章主要介绍了Android中Service服务,详细介绍了Service服务的概念、功能及简单使用方法,需要的朋友可以参考下...

    残缺的孤独10952021-05-11
  • AndroidAndroid开发中优秀的app 异常处理机制

    Android开发中优秀的app 异常处理机制

    这篇文章主要介绍了Android开发中优秀的app 异常处理机制 的相关资料,需要的朋友可以参考下...

    Android开发网8202021-04-19
  • AndroidAndroid开发之AlarmManager的用法详解

    Android开发之AlarmManager的用法详解

    这篇文章主要介绍了Android开发之AlarmManager的用法,是Android应用开发中非常实用的技能,需要的朋友可以参考下...

    Android开发网4892021-03-05
  • AndroidAndroid自定义View实现自动转圈效果

    Android自定义View实现自动转圈效果

    这篇文章主要为大家详细介绍了Android自定义View实现自动转圈效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    Cyq_09278062022-02-17