前言
Android 中创建应用进程的方式有 4 种途径:
- Zygote_Server : 用于创建app进程
- fork/vfork : 创建系统进程
- shell : 创建shell进程(会关联uid)
- app_process :app服务进程
其中,系统中最常用的是利用Zygote_Server去创建app进程,利用app_process创建Zygote_Server进程或者应用进程,利用shell进程去读取系统信息或者进行子进程托孤保活。不过所有的进程创建都是最终都会调用到linux fork函数。当然,如果继续深入就会发现,fork、pthread_create、exec都会调用到linux中的clone方法,另外进程的创建默认都利用了CopyOnWrite机制。
本篇主要介绍app_process进程,顺便提一下Zygote进程
Zygote_Server 知识点回顾
Android 中创建 app 进程使用了 3 种通信技术:
- Binder : AMS 与app进程通信
- LocalSocket : AMS 与 Zygote_Server通信
- Pipe (在进程创建完读取子进程 id,同时可以检测子进程是否正常)
Zygote_Server 是 Android 中非常重要的服务进程,无论是 System_Server、WebView_Zygote(Android O 之时是 init 进程的子进程,Android Q 是 ZygoteServer 的子进程)这样的 Android 服务进程,还是我们的各种 app,他们共同的父进程都是 Zygote。
Android Zygote_Server 历来都是单线程 + 同步非阻塞 IO 模型,在 Android 5.0 之前,使用 linux select 机制实现单线程多路复用,在 Android 5.0 之后改用 poll 机制,主要原因是 select 使用复杂,最大能监听 1024 个文件描述符(Linux 上一切皆文件),而 Poll 机制进行了改进,文件描述符 FD 数量取消了限制。当然,有人可能要问,为什么不实用 epoll 呢?
主要fork进程的频度还没有使用 Handler Looper 的频度高,epoll 在低并发场景下优势并不明显,而且 epoll 有个事件队列要维护,对于单纯 fork进程的服务,必要性不是很高。
Zygote 进程的主要作用
- 负责启动 java 虚拟机
- 加载很多需要预加载的类和系统资源
- 负责启动 systemServer ,systemServer 会启动 android 中的所有服务,基本上完成了上层框架的所有功能。
- 负责初始化新进程,其实就是 fork app 的独立进程,比如启动一个 application,那么 zygote 就负责为新启动的 ActivityThread 创建进程,并调用 AppRuntime 、ActivityThread 中的 main方法或者其他方法进行初始化。
app_process
关于app_process
app_process 分为32位和64位,主要和Zygote_Server中虚拟机支持情况相关,不过用法是一样的。我们前面说过,app_process用于创建一些Android服务进程,包括Zygote_Server进程,下面是利用app_process创建Zygote_Server进程的方式。
service zygote /system/bin/app_process32 -Xzygote /system/bin --zygote --start-system-server --socket-name=zygote
class main
priority -20
user root
group root readproc reserved_disk
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart audioserver
onrestart restart cameraserver
onrestart restart media
onrestart restart netd
onrestart restart wificond
writepid /dev/cpuset/foreground/tasks
service zygote_secondary /system/bin/app_process64 -Xzygote /system/bin --zygote --socket-name=zygote_secondary
class main
priority -20
user root
group root readproc reserved_disk
socket zygote_secondary stream 660 root system
onrestart restart zygote
writepid /dev/cpuset/foreground/tasks
app_process 用法
从前面我们看到,app_process32/app_process64位于/system/bin目录下,但这个可执行程序是允许普通app通过shell进程调用的。
app_process 入口源码
int main(int argc, const char* const argv[])
{
// These are global variables in ProcessState.cpp
mArgC = argc;
mArgV = argv;
mArgLen = 0;
for (int i=0; i<argc; i++) {
mArgLen += strlen(argv[i]) + 1;
}
mArgLen--;
AppRuntime runtime;
const char *arg;
argv0 = argv[0];
// Process command line arguments
// ignore argv[0]
argc--;
argv++;
// Everything up to '--' or first non '-' arg goes to the vm
int i = runtime.addVmArguments(argc, argv);
// Next arg is parent directory
if (i < argc) {
runtime.mParentDir = argv[i++];
}
// Next arg is startup classname or "--zygote"
if (i < argc) {
arg = argv[i++];
if (0 == strcmp("--zygote", arg)) {
bool startSystemServer = (i < argc) ?
strcmp(argv[i], "--start-system-server") == 0 : false;
setArgv0(argv0, "zygote");
set_process_name("zygote");
runtime.start("com.android.internal.os.ZygoteInit",
startSystemServer);
} else {
set_process_name(argv0);
runtime.mClassName = arg;
// Remainder of args get passed to startup class main()
runtime.mArgC = argc-i;
runtime.mArgV = argv+i;
LOGV("App process is starting with pid=%d, class=%s.\n",
getpid(), runtime.getClassName());
runtime.start();
}
} else {
LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
fprintf(stderr, "Error: no class name or --zygote supplied.\n");
app_usage();
return 10;
}
}
从if分支我们可以看到,除了Zygote_Server以外,其他普通的进程也是可以直接进行创建的。另外进程名称的设置是set_process_name来完成的,这个主要作用是可以方便利用ps -ef进行进程查询。
这里,我们不会深入Zygote的创建。本篇博客仍然会以“实用性”的路线为主,理论性、面试相关等分析性文章暂不是重点。
app_process 基本用法
app_process [vm-options] [工作目录] [options] 类名 [类的main方法参数] [类的main方法参数] ....
参数解释如下
vm-options – VM 选项
work-dir –工作目录(如/system/bin,/data/app,/data/...)
options –运行的参数 :
–-zygote
–-start-system-server
–-application (api>=14)
–-nice-name=nice_proc_name (api>=14) (只有非--zygoate模式下该选项才会生效)
[启动类名] –包含main方法的主类 (如com.android.internal.os.WrapperInit)
main-options –启动时候传递到main方法中的参数
一个完整调用的例子
app_process -Djava.class.path=/sdcard/classes.dex /data/local/tmp --application --nice-name=helloservice com.apptest.bash.HelloWorld 1 2 a
一般来说,默认都是 --application 模式,加上这个参数和不加,都能创建进程,只是 IO 输入输出的位置不同
此外,我们还可以使用 apk
app_process -Djava.class.path=/sdcard/app.apk /data/local com.apptest.bash.HelloWorld
关于几种模式的
- 传入 –zygote 会启动 com.android.internal.os.ZygoteInit,否则启动 com.android.internal.os.RuntimeInit。
- –start-system-server 只在启动 zygote 时有效。
- 在非 zygote 模式中,有无 –application 的选项的区别只是是否将 stdout 和 stderr 重定向到 AndroidPrintStream。
- 只有在非 zygote 的情况下,–nice-name= 选项有效。
从几种模式我们知道,app_process 都会走虚拟机相关初始化逻辑,因此,可以确定的是 app_process 启动的并不是普通的 java 程序
案例
public class HelloWorld {
public static void main(String[]args){
int num = 0;
while (true) {
System.out.println("["+android.os.Process.myPid()+" ]Hello, app_process "+num);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
num++;
}
}
}
执行如下命令
app_process -Djava.class.path=/sdcard/app.apk /data/local --nice-name=hellworld com.apptest.bash.HelloWorld
这个类中调用了 android.os.Process.myPid(),运行结果如下,可以发现,Android 相关 api 可以正常运行
[8062 ]Hello, app_process 0
[8062 ]Hello, app_process 1
[8062 ]Hello, app_process 2
[8062 ]Hello, app_process 3
[8062 ]Hello, app_process 4
[8062 ]Hello, app_process 5
[8062 ]Hello, app_process 6
[8062 ]Hello, app_process 7
[8062 ]Hello, app_process 8
[8062 ]Hello, app_process 9
可以明确知道,Android 相关服务通过 com.android.internal.os.RuntimeInit 进行了初始化, ,否则我们无法调用android.os.Process.myPid()的,至于com.android.internal.os.ZygoteInit 肯定是没有走的,原因是你看不到任何Application的启动日志。
总结
我们知道,Android 中 app 主进程和其他进程都是 Zygote 的子进程,那么 app_process 启动的进程又是怎么的呢?
对于用户而言,我们一般都是 --application 模式启动,影响进程父进程的主要因素和用户相关。
总结如下:
- 在Android中,app进程创建的任何进程UID是一样的
- 在Android中,app使用app_process是需要shell进程创建的,但shell进程的UID依然可以传递,即便shell被kill之后,托孤到init进程的进程依然保持原有UID
- Android 按分组杀进程,但是对于UID相同但不在同一分组的会有一些延迟,这也是提高保活成功率的手段之一。
在 Android 系统中,用户 id 一致,意味着进程之间的相关信息可以互相访问,包括杀进程,清理私有目录都会被允许,具体可参考sharedUserId 的用法。
android:sharedUserId="com.jym.app"
应用范围
app_process 主要应用于伴随服务,比如保活、线上进程状态实时观测、实时日志、app屏幕录制等方面。
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。