进程保护
基本概念
进程类型
- 前台进程(foreground)
- 可见进程(visible)
- 次要服务进程
- 后台进程(会被保存在 LRU 列表中)
- 内容提供者进程
- 空进程
ADJ级别
定义在ProcessList.java文件,oom_adj划分为16级,从-17到16之间取值
进程state级别
定义在ActivityManager.java文件,process_state划分18类,从-1到16之间取值
force-stop
force-stop并不会杀 persistent 进程;
当app被force-stop后,无法接收到任何普通广播(带
FLAG_INCLUDE_STOPPED_PACKAGES
的Intent除外),那么监听系统的变化来拉起进程肯定不可行;通过force-stop杀掉的进程在packagemanager中还会设置一个flag,以至于之后所有的广播都会将你过滤掉,防止你被系统的广播呼起,直到用户手动点击icon启动该应用,该flag才会置为false。当然这只针对系统广播,我们自己发的广播只要加入flag为FLAG_INCLUDE_STOPPED_PACKAGES就可以了;
当app被force-stop后,那么alarm闹钟一并被清理;
app被force-stop后,四大组件以及相关进程都被一一清理,即便多进程架构的app也无法拉起自己;
级联诛杀:当app通过ClassLoader加载另一个app,则会在force-stop的过程中会被级联诛杀;
生死与共:当app与另一个app使用了share uid,则会在force-stop的过程,任意一方被杀则另一方也被杀,建立起生死与共的强关系。
需要反射调用,且需要系统签名,即root权限
杀进程方法
- Process.killProcess(int pid): 杀pid进程
- Process.killProcessQuiet(int pid):杀pid进程,且不输出log信息
- Process.killProcessGroup(int uid, int pid):杀同一个uid下同一进程组下的所有进程
无论是系统杀(android机型上长按home键中清除),或者是他杀(第三方管理软件,如360,腾讯等)。其实现方法,基本都是借助ActivityManagerService
的removeLruProcessLocked。
5.0以下
1 | final void removeLruProcessLocked(ProcessRecord app) { |
5.0以上
1 | final void removeLruProcessLocked(ProcessRecord app) { |
同组杀
- /acct/uid_XX/pid_XX/cgroup.procs记录了这个进程的所fork()或execlp出来的任何进程,都是该进程的同组进程;
- Process.killProcessGroup中的group不是linux中的组
App主动杀(Process.killProcess)
内存不够内核杀(LowMemoryKiller)
手动停止(killPackageProcesses)
最近任务(removeTask)
进程被杀的场景
场景 | 调用接口 | 可能影响范围 |
---|---|---|
触发系统进程管理机制 | LowMemoryKiller | 由oom_adj从大到小杀进程 |
被第三方应用杀死(无Root) | killBackground或Force-stop(AccessibilityService) | 前者只杀死iim_adj > 4;后者可杀所有非系统进程 |
被第三方应用杀死(Root) | Force-stop或kill | 理论上可杀所有进程 |
厂商杀进程功能 | Force-stop或kill | 理论上可杀所有进程 |
用户点击Force-stop | Force-stop | 可杀所有非系统进程 |
关机、重启 | *** | *** |
查看进程oom_adj
查看进程id
1 | ps | grep [PackageName] |
或者在其他调试工具中查看,如:AndroidStudio
查看oom_adj
1 | cat /proc/[进程ID]/oom_adj |
- UI进程和常驻Service进程的oom_adj=0,而普通后台进程oom_adj=15
- App退到后台时,其所有的进程优先级都会降低。但是UI进程是降低最为明显的,因为它占用的内存资源最多,系统内存不足的时候肯定优先杀这些占用内存高的进程来腾出资源。所以,为了尽量避免后台UI进程被杀,需要尽可能的释放一些不用的资源,尤其是图片、音视频之类的。
保活策略
返回START_STICKY
1 | public int onStartCommand(Intent intent, int flags, int startId) { |
Service 第一次被异常杀死后会在5秒内重启,第二次被杀死会在10秒内重启,第三次会在20秒内重启,一旦在短时间内 Service 被杀死达到5次,则系统不再拉起
双service
Low Memory Killer 机制决定是否杀进程,除了内存大小,还有进程优先级。从这个原理来说,我们可以通过提高进程的优先级来保活。
值得注意的是,Android 的前台service机制。但该机制的缺陷是通知栏保留了图标。
API level < 18
调用startForeground(ID, new Notification()),发送空的Notification ,图标则不会显示。
API level >= 18
在需要提优先级的serviceA启动一个InnerService,两个服务同时
startForeground
,且绑定同样的NotificationID。Stop
掉InnerService ,这样通知栏图标即被移除。
1 |
|
no longer works in Android 7.1
监听系统广播判断Service状态
类型 | 权限/广播 |
---|---|
开机广播 | RECEIVE_BOOT_COMPLETED |
网络变化 | ACCESS_NETWORK_STATE CHANGE_NETWORK_STATE ACCESS_WIFI_STATE CHANGE_WIFI_STATE ACCESS_FINE_LOCATION ACCESS_LOCATION_EXTRA_COMMANDS |
文件挂载 | MOUNT_UNMOUNT_FILESYSTEMS |
屏幕亮、灭 | SCREEN_ON SCREEN_OFF |
锁屏、解锁 | RECEIVE_USER_PRESENT |
应用安装、卸载 | PACKAGE_ADDED PACKAGE_REMOVED |
1 | <receiver android:name="com.dbjtech.acbxt.waiqin.BootReceiver" > |
persistent进程
AndroidManifest.xml:android:persistent=”true”
force-stop并不会杀persistent进程,需要系统shareuid
重写Service的onDestroy方法
1 |
|
监听第三方应用的静态广播
通过反编译第三方 Top 应用,如:手机QQ、微信、支付宝、UC浏览器等,找出它们外发的广播,在应用中进行监听
AlarmManager唤醒
1 | public void startKeepLiveService(Context context, int timeMillis, String action) { |
账户同步
Android系统里有一个账户系统,系统定期唤醒账号更新服务,同步的事件间隔是有限制的,最短1分钟
1像素悬浮层
1 | public class MainActivity extends AppCompatActivity { |
应用间互相拉起
App之间知道包名就可以相互唤醒了,比如杀了qq,只要微信还在就能确保随时唤醒qq。还有百度全系App都通过bdshare实现互拉互保,自定义一个广播,定时发,其他app收广播自起等
C进程
要点:
- 怎样监听到进程挂掉
- 怎样把进程拉起来
Android5.0在应用退出后,ActivityManagerService不仅把主进程给杀死,也会把主进程所属的进程组一并杀死
- 让c进程脱离主进程,不要受到主进程的影响:两次fork,退出第一次fork的进程,使第二个子进程是会话组长。
- 感知主进程是否存活有两种实现方式:
- 轮训判断主进程是否存活,其pid是否变为1,耗电;
- 文件锁,需要封装 Linux 层的文件锁供上层调用;
- 循环startService(),通过执行am命令;
- 利用 Localsocket 保证 Native 进程的唯一性,不至于出现创建多个 Native 进程以及 Native 进程变成僵尸进程等问题
父进程如何监视到子进程(监视进程)的死亡?
在linux中,子进程被终止时,会向父进程发送SIG_CHLD信号,于是我们可以安装信号处理函数,并在此信号处理函数中重新启动创建监视进程;
子进程(监视进程)如何监视到父进程死亡?
当父进程死亡以后,子进程就成为了孤儿进程由Init进程领养,于是我们可以在一个循环中读取子进程的父进程PID,当变为1就说明其父进程已经死亡,于是可以重启父进程。
最终还是执行am命令唤起常驻进程
方案3
实际运行的c进程(可执行二进制文件)
1 | /data/data/com.taobao.idlefish/files/DaemonServer |
AppWidget
添加一个桌面组件,这个组件也是App的一部分, 但它却是一个广播,一直会被系统持续唤醒。
参考
JobScheduler
Android5.0 以上版本提供了 JobScheduler 接口,系统会定时调用该进程以使应用进行一些逻辑操作