谈谈Android的进程

Sun 01 November 2015

谈谈Android的进程

原创内容转载请注明源地址: http://jksoftcn.com/

Android的底层虽然是Linux,但是它的进程管理机制和Linux还是有些差异的。

传统层面的进程

传统层面的进程规则还是和Linux一样

init是1号进程,这是Linux内核启动的第一个应用层程序,其他进程都是1号进程的子孙。 进程可以fork出自己的子进程。这意味着你使用JNI在APP中调用fork函数fork出子进程。

Android层面的进程

APP进程虽然本质上是Linux进程,但这个层面的游戏规则不一样了

所有正常启动的APP进程都是zygote创建出来的子进程。因为zygote已经创建好虚拟机了,并且预加载了framework,由zygote fork可以让其他APP进程直接使用zygote创建好的虚拟机环境,省了很多事。

一个APP可以拥有多个进程,即:不同的组件运行在不同的进程。但同一个APP的不同进程间是平级的,没有从属关系,父进程都是zygote。看了下面的内容,应该能理解这么设计的意图。

APP默认的进程名是APP的包名,可以指定组件运行在:xxx这样相对命名进程里面,实际上是packageName:xxx

不同的APP(不管签名是否一样)都可以共用一个进程名(其实进程名只是cmdline)。某ROM里面Low memory Killer的白名单用的就是进程名,这是一个漏洞。

APP进程执行的代码由两部分组成:apk里面的dex文件,加系统的framework。并且APP进程的入口main不在apk中,这和桌面的Java程序不同,相同的是都有main

网上有个关于Android面试的笑话,面试官问APP的入口是什么?回答ActivityThread中的main,面试官纠正是ActivityonCreate

呵呵,不要学那个面试官。APP的入口确实在ActivityThread中。zygotefork出我们的APP进程后就是执行这里的main,但执行后只创建了一个消息循环,没有干其他的事情。

因为ActivityService都不是APP进程主动创建的,而是被动创建执行的。被动的执行系统服务的远程调用,把系统的请求丢进消息队列里面去执行。

APP启动

从上图(该图来源于网络)可以看出APP进程在创建后收到两条指令:

  1. BIND_APPLICATION,要求进程构造出Application对象,激活Application对象(attach),调用onCreate
  2. LAUNCH_ACTIVITY,要求进程构造出指定的Activity对象,激活Activity对象(attach),调用onCreate

实际上指令顺序仅是一种场景。还有其他场景,如:有可能进程是因为要启动服务而起来,或收到广播而起来,但有一个是绝对不变的,就是每个APP的进程,都必定实例化了一个Application对象。

为什么关联进程不是由主进程fork的,那是因为关联进程只需要运行指定的组件加Application。非要这么搞弊大于利,如:内存占用,继承来的fd,系统服务缺少进程记录,请求发不过来等等。

不使用Android SDK枚举APP进程

Android 5.0前我们可以使用ActivityManager.getRunningAppProcesses()枚举进程 但5.0后系统限制了我们使用这个API

SDK中的实现在ActivityManagerService这个类中。5.0系统要求android.Manifest.permission.REAL_GET_TASKS权限加系统签名。为了兼容旧的system APP,过渡期允许android.Manifest.permission.GET_TASKS权限。

说白了,普通应用再也没法调用这个API了。怎么办了?

Android层面没权限,但在Linux层面我们却是有权限的,Linux的内核维护了procfs这样一个虚拟的文件系统。在/proc下面,每个数字命名的文件夹都代表一个进程。我们有权限枚举/proc这个目录,而且每个进程对应目录下面的cmdline, stat文件我们也有读取的权限。

枚举APP进程的方法就变得很简单:

  1. 通过/proc枚举出所有的进程,和其父进程PID
  2. 找出zygote进程的PID
  3. 所有父进程PID是zygote的PID的进程,都是APP进程

如何结束APP进程

如果是APP结束自己的进程很简单,但一般的APP没有这个需求,并且系统也不推荐你主动退出。

桌面Java程序退出进程的方法:System.exit(),在Android上一样可以使用。

另外Android也有专门的API:

android.os.Process.killProcess(android.os.Process.myPid());

如果要结束其他APP的进程,在Linux层面普通APP是没有权限的,因为不同APP(排除指定共享UID,签名一样的不同APP)运行的UID是不一样的。虽然Linux层面没戏,但是Android层面还是有办法的,系统给用户提供了一个强制停止应用的方法。

强制停止是按UID来结束进程的,相同UID不管是zygote fork出来的,还是APP自己用JNI fork出来的,是一起杀的。因此用子进程监控父进程是否被杀对于这种情况不适用。

普通APP可以利用辅助功能模拟点击去执行这个强制停止功能,就和用户自己操作一样。辅助功能当然不是能随便获得的,需要用户到设置里面勾选。

如何保证进程不退出

暂时只讨论进程不被系统干掉

系统的Low Memory Killer是按进程的oom_adj来选择进程杀死的。对于普通的APP,没界面的比有界面的先被杀。

大部分APP都想让某个Service能一种跑着,肯定是没有界面的。怎么办?目前有两个黑暗技术能达到目的:

  1. 在跑Service的进程里面,跑一个你看不到的悬浮窗
  2. Service设为前台服务, 就是一直有个常驻通知栏的

现在很多ROM给悬浮窗搞了权限控制,悬浮窗都不是能随便显示的了,因此方法1现在不可靠。方法2看上去有一个常驻的通知栏,但却是有方法让它不显示出来。

总结

上面描述的都是一个普通APP。系统APP和以root用户跑的APP情况不一样。有空了再补充 跨进程共享组件

Category: Android Tagged: Android Linux Process

comments

Page 1 of 1