android进程常驻

Android 进程常驻,顾名思义,就是要让我们的进程在内存中永远存在,换句话说就是进程保活,臭不要脸的说法就是关不了,杀不死,干不掉。这不是耍流氓,是很多场景如果要想为用户服务,就必须有一个进程常驻,以便在特定的时候做特定的事情。比如在Android中,许多BroadcastReceiver事件不支持静态注册,也就是说如果我想接受屏幕开关的系统广播,必须要在进程中动态注册,如果没有一个常驻进程,那么锁屏应用就无法正常为用户服务;另外IM类应用,也需要在后台维护一个长链接,以便于在最及时的时间里将信息传达给用户。诚然,但凡进程常驻内存,无论怎样优化,都会或多或少的增加一些额外的性能开支,在为用户最负责任的服务,最高品质的体现我们的价值的前提下,我们要尽可能减少内存和电量的消耗,这个后面会说到。这里吐槽一下一些无良开发者,为一些完全不必要的业务常驻一个进程,这样只会加快用户卸载的速度,最让人忍受不了的是,代码低效,保活无力,还特么烧电!最后我想说的是,不以服务用户为目的的内存常驻都是耍流氓!

闲淡少扯。

进入正题。

知己知彼,方能百战不殆。要想保活,就要先知道是怎么死的。死因大概有三:第一被系统干死;第二被第三方安全软件干死,比如知名的有奇虎360、CleanMaster(当然包括是否有root权限,有无root天壤之别);第三就是被用户手动干死,比如小米自带的系统一键清理、手机设置里应用管理器的ForceClose。下面开始分析。

Android系统是基于Linux系统,他继承了Linux的内存管理策略,即进程退出并不会立刻杀死,而是在系统内存吃紧的时候再来按照优先级杀掉进程,这不同于微软的Windows,Windows在进程退出的同时一定杀掉进程,将内存释放干净。一个是尽可能的充分利用内存,来提高再次开启进程的速度;一个是放着大块大块的内存宁可永远闲置也不用。作为一个移动设备的操作系统,Android继承前者无可厚非,他继承了Linux的lowmemorykiller。

在系统的/sys/module/lowmemorykiller/parameters目录下,我们cat文件minfree,他记录了6个数字,分别对应了六个进程类型的斩首的阀值(单位为page,1page=4kb)。如图


(三星note3)

(三星s5


六个进程类型从左往右分别为:ForegroundProgress/VisibleProgress/SecondaryService/HiddenProgress/ContentProvider/EmptyProgress

1、前台进程(比如正在显示的activity所在的进程)

2、可见进程(比如输入法进程)

3、次要服务进程

4、隐藏进程

5、内容提供者进程

6、空进程

这些数字所代表到所谓斩首阀值,代表系统剩余内存达到他们对应的阀值的时候,就开始操刀斩首对应的进程了,所以在上图可以看出,当s5剩余内存为56320*4kb约等于200m的时候开始清理空进程,而note3则是120m才开始。


六个进程类型定义于kernel层,在framework层,做了更细致的划分,以便于进程查看管理。他们定义在com.android.server.am.ActivityManager.RunningProgressInfo中,如下图。开发者可以使用Android 开放的API拿到对应进程的RunningProgressInfo来查看他的importance。




当斩首开始,同样一个级别的进程有好多个,究竟先杀谁呢?比比谁的哈希值大就先杀谁?当然不是。Kernel层定义了六个进程类型对应的adj,他们定义在/sys/module/lowmemorykiller/parameters/adj


数值越大越容易被杀,同时Android 增加了两个Linux中没有的进程adj,他们分别是CORE_SERVER_ADJ=-12、SYSTEM_ADJ = -16,定义在ActivityManagerService中。我们可以在在shell下ps查看所有进程,找到对应进程pid,然后再/proc/pid/oom_adj下查看对应进程的adj。


上图是在手机三星s5上,系统为android5.1,应用中在MainActivity启动了两个Service,并且两个Service 都在menifest中定义为其他进程


从上到下分别为activity的进程,正常的service进程,设置为前台service的service进程。

然后分别到对应的进程号pid的目录下查看对应的adj。

每次cat都对应了ui上的操作:

从上往下分别是:

应用安装完毕,5.1系统会弹出一个activity来确认应用权限,此时我们的activity为onstrop状态(5114进程第一次cat)

关闭权限确认的界面,此时我们的activity为onresume状态(5114进程第二次cat)

点击back键,我们的activity为ondestroy状态,且该进程内无其他运行组件(5114进程第三次cat)

正常的service进程,没有做任何设置(5155进程cat)

设置为前台service的进程(5156进程cat)

最后根据进程的oom_score来决定究竟斩谁的首,那么oom_score是怎么算出来的呢?他正是通过刚才讲的oom_adj、占用内存大小、启动时间等加权算出oom得分,得分最高者立斩。那么oom_adj的值又是谁在维护他呢,activity的生命周期是只有framework才知道,kernel不知道,所以这件事自然是由总管家ActivityManagerService来做


然而真正的lowmemorykiller却是在kernel层,源码位于Android 源码/kernel/drivers/staging/android/lowmemorykiller

以下是具体杀进程的代码片段



是的,信号。当然没这么简单!后面再说


那么第三方安全软件是怎么杀进程的呢?首先系统提供的方法就有


注释讲的明白,会在过会儿需要的时候再启起来,只需配置一个start_stick就杀不死,这个太low。

那么来看看framework自己是怎么管理的,杀掉前台进程以外的所有进程


向下跟代码


继续


与killProcessQuiet不同的是killProcess,发送的信号不同


通过反射,需要root权限

再看看系统设置里面的force close(话说百分之九十五以上的应用都难逃系统的fc)是怎么杀掉进程的呢?


看到了吗?第一段注释,会杀掉所有和该packageName共享uid的进程,会取消掉该进程所有闹钟!百分之九十五的保活难逃此劫,但是marsdaemon可以做到,这里先卖个关子,以后慢慢讲,正在整理代码,上传github。


通过fc杀掉的进程在packagemanager中还会设置一个flag,以至于之后所有的广播都会将你过滤掉,防止你被系统的广播呼起,直到用户手动点击icon启动该应用,该flag才会置为false。当然这只针对系统广播,我们自己发的广播只要加入一个flag值就ok了。

force close 方法这么屌,第三方能不能用呢?我们根据这个方法要求的permission,在源码中找到该permission的定义


需要系统签名!所以第三方只能呵呵?但是!

但是!劳动人民的智慧是无穷尽的,最近好多第三方安全软件,推出了免root功能,除了利用短暂的系统漏洞侵入系统外,都是利用了android的AccessibilityHelper,这个功能初衷是帮助老年人或者视力有障碍的人群进行模拟点击操作,只要应用经过用户授权,就可以获取当前屏幕的所有控件以及空间上的信息,模拟手指触屏操作。于是乎,所谓的免root是让你给他们授权,然后他们给你在手机上点点点,点到settings中找到对应package点击force close,被用户看到你在操控他的手机当然不好,于是上面盖一个window当遮羞布,给你显示个进度条,后面不停点点点,就是这样。有空再发一下AccessibilityHelper 的demo。

此外有了root权限,可以直接用代码在terminal中调用kill -9 pid命令,等同于发送一个kill信号来杀死进程。

说了这么多,第三方的安全软件进程清理到底具体采用了哪种手段,以及使用了其他我不知道的方法。但是归根结底都是一个kill,所以我们只关心进程死掉的一瞬间。


上一篇讲了系统管理进程和强杀进程的过程原理,今天就开始想一下,在此基础上,如何实现保活,当然作为一个android开发,最先想到的肯定是在framework层有没有什么机制可以利用实现保活,当时整理了以下几点(是对照自己当时写的ppt整理的,有些细节已经忘记):


1、将Service设置为前台进程

2、在service的onstart方法里返回 STATR_STICK

3、添加Manifest文件属性值为android:persistent=“true”

4、覆写Service的onDestroy方法

5、添加广播监听android.intent.action.USER_PRESENT事件以及其他一些可以允许的事件

6、服务互相绑定

7、设置闹钟,定时唤醒

8、账户同步,定时唤醒

9、native层保活






好的,然后我们一个一个的来说



1、将Service设置为前台进程。

本质是修改了Service所在进程的进程优先级,详情请参照 Android 进程常驻(1)----开篇 。有了前台进程的优先级,在android系统清理内存的时候,他被杀死的优先级仅高于前台的activity,也就是正在和用户交互的页面,而且使用ddms杀进程他也可以自己启动起来。

但是,然,并,卵!

首先ddms杀进程和在系统设置的正在运行中杀进程本身就不具威胁,在系统设置的所有应用中选择强行停止,仍然可以强停掉,360,cm等软杀更是能轻而易举杀死他。而且他还有一个缺点,在api 17以上,设置了一个前台服务,他会以一个无法消除的notification的样式出现在用户的手机状态栏里,大大降低了用户体验。看源码




所以一般这么用







2、在service的onstart方法里返回 STATR_STICK

没多大用,放在常规应用里的service ok,但保活的话还是差些。。。这里不多说了直接看源码注释




其他几个返回值分别代表了

START_STICKY_COMPATIBILITY:START_STICKY的兼容版本,但不保证服务被kill后一定能重启。 

START_STICKY:系统就会重新创建这个服务并且调用onStartCommand()方法,但是它不会重新传递最后的Intent对象,这适用于不执行命令的媒体播放器(或类似的服务),它只是无限期的运行着并等待工作的到来。

START_NOT_STICKY:直到接受到新的Intent对象,才会被重新创建。这是最安全的,用来避免在不需要的时候运行你的服务。

START_REDELIVER_INTENT:系统就会重新创建了这个服务,并且用最后的Intent对象调。等待中的Intent对象会依次被发送。这适用于如下载文件。






3、添加Manifest文件属性值为android:persistent=“true”

如果你的应用能设置这个属性,可以全文跳过我这个系列所有文章。因为他真的可以杀不死,像系统的keyguard进程,media进程,且这些进程的adj都是负数,代表了前台activity黑屏了他们也不会死。但是这个属性需要系统shareuid,然后编译不过,因为需要系统签名,什么?你用系统签名?请忽略我全部

所以然并卵。





4、覆写Service的onDestroy方法

你会说这么low也行?为了把我知道的所有方式都列举出来,所以这里也说一下,在设置里面的正在运行,注意是正在运行里面,点击关闭,会走onDestroy回调方法,你在这里可以把自己启动起来。

但是仍然然并卵,设置里面的force close才是真正的劲敌,还有root了的360,cm这些boos才是我们要解决的对象。




5、添加广播监听android.intent.action.USER_PRESENT事件以及其他一些可以允许的事件

这个有必要说一下,这个广播相信可能有的朋友不是很清楚,这是一个android解锁的广播事件。

注意:

1.这不是SCREEN_ON\SCREEN_OFF广播,,这不是SCREEN_ON\SCREEN_OFF广播,这不是SCREEN_ON\SCREEN_OFF广播。

2.这是一个可以静态注册的广播,这是一个可以静态注册的广播,这是一个可以静态注册的广播。


所以在manifest里面注册之后不需要任何前提,理论上用户每次开屏解锁都会触发我们的onReceive事件,在这里我们可以检查进程服务是否在,不在就拉起来。

但是,这个事件只有解锁才有,如果用户的没有设置锁屏,那么这个事件就是没有的,而且我们的目标是保证进程一直存活,而不是尽可能多的活起来。所以这个当作一个补充的手段也不错,另外所有那些可以静态注册的广播都可以这样搞,前提是你不怕用户看到你申请了好多权限,当然这个USER_PRESENT事件是不需要权限的。

以下是几个常见可以静态注册的广播,另外android7取消了wifi的静态广播注册,没有证实

android.intent.action.USER_PRESENT
android.NET.conn.CONNECTIVITY_CHANGE
android.intent.action.MEDIA_MOUNTED
android.intent.action.MEDIA_UNMOUNTED
android.Net.wifi.RSSI_CHANGED
android.net.wifi.STATE_CHANGE
android.net.wifi.WIFI_STATE_CHANGED




6、服务互相绑定

这个是android里面一个特性,跨进程bind一个service之后,如果被bind的service挂掉,bind他的service会把他拉起来。依然然并卵,具体为什么以后再说。



7、设置闹钟,定时唤醒

市面上了解到的大部分应用是用这种保活方式,使用系统闹钟定时发通知过来唤醒进程。

但是,

且先不说高频的唤醒和手机厂商对于wakelock的控制上造成的耗电问题。单单保活效果上就很难过关,force close直接杀掉,没有挣扎的机会,360、cm更是随便杀。说下alarm的几个参数


AlarmManager.RTC,硬件闹钟,不唤醒手机(也可能是其它设备)休眠;当手机休眠时不发射闹钟。

AlarmManager.RTC_WAKEUP,硬件闹钟,当闹钟发躰时唤醒手机休眠;

AlarmManager.ELAPSED_REALTIME,真实时间流逝闹钟,不唤醒手机休眠;当手机休眠时不发射闹钟。

AlarmManager.ELAPSED_REALTIME_WAKEUP,真实时间流逝闹钟,当闹钟发躰时唤醒手机休眠;

 

嗯,RTC闹钟和ELAPSED_REALTIME最大的差别就是前者可以通过修改手机时间触发闹钟事件,后者要通过真实时间的流逝,即使在休眠状态,时间也会被计算。






8、账户同步,定时唤醒

这个估计也是好多人不知道一点,android系统里有一个账户系统,设置一个自己的账户,android会定期唤醒账户更新服务,我们可以自己设定同步的事件间隔。且发起更新的是系统,不会受到任何限制。看下效果,晚上下班到第二天早晨,开着cm后台自动清理




 log上来看唤醒时间一直正常,且在睡眠中是不会产生唤醒的。

使用方法也很简单:









那么,这就是我们想要的?


还不行!!!


他的局限性在于:

第一,用户会在系统设置的账户列表里面看到一个不认识的账户;

第二,同步的事件间隔是有限制的,最短1分钟,见源码,如果小雨60秒,置为60秒。而且各种国产机怎么改的源码我们未可知,是不是都能用仍然未可知;

第三,很致命,某些手机比如note3需要手动设置账户,你如何骗你的用户给你手动设置账户完了之后不卸载你;

第四,也很致命,必须联网!google提供这个组件是让你同步账户信息,不联网你同步个鬼,我们要保活,可以不联网不做事,但是不能不联网就死


但是,把他放在最后,仍然是一个很好的保活补充手段

9、native层保活

终于要到正文了。下一篇开启native保活的篇章,首先上一个github上别人的native保活方案,也是绝大多数公司采用的native方案,笔者所在公司亦是北京知名互联网公司,之前也采取的类似方案,但是,其实只是开了一个native进程定期发intent而已,缺陷是:只能简单保活,360、cm碾压其无压力,但仍有可借鉴之处,下一篇分析,然后开始我的native之旅

https://github.com/Coolerfall/Android-AppDaemon


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值