从Android进程优先级开始谈APP保活的意义

1、APP为何保活?

        Android应用互相唤醒的状况是中国特点,国外由于有 Google Play 的评价系统和基本的审核机制,不会有国内这么疯狂的流氓式设计。这样作的好处一个是方便收集用户信息,了解用户习惯,优化产品,再者给用户推送消息须要APP退出前台时可以保活,对于IM应用来说更是硬性需求。因此有些你没装过或者周围没人用的 APP ,随随便便都能日活上千万。html

       

2、保活手段

      当前业界的Android进程保活手段主要分为 黑、白、灰 三种,其大体的实现思路以下:java

黑色保活:

 不一样的app进程,用广播相互唤醒(包括利用系统提供的广播进行唤醒)linux

场景1:开机,网络切换、拍照、拍视频时候,利用系统产生的广播唤醒appandroid

场景2:接入第三方SDK也会唤醒相应的app进程,如微信sdk会唤醒微信,支付宝sdk会唤醒支付宝。由此发散开去,就会直接触发了下面的 场景3api

场景3:假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其余阿里系的app给唤醒了。(只是拿阿里打个比方,其实BAT系都差很少)缓存

没错,咱们的Android手机就是一步一步的被上面这些场景给拖卡机的。bash

针对场景1,估计Google已经开始意识到这些问题,因此在最新的Android N取消了 ACTION_NEW_PICTURE(拍照),ACTION_NEW_VIDEO(拍视频),CONNECTIVITY_ACTION(网络切换)等三种广播。微信

白色保活:

       启动前台Service网络

白色保活手段很是简单,就是调用系统api启动一个前台的Service进程,这样会在系统的通知栏生成一个Notification,用来让用户知道有这样一个app在运行着,哪怕当前的app退到了后台。这样的目的也很容易理解,就是提升进程的优先级。让Linux在内存不足的时候不会优先被杀掉,从某种意义上来讲这算是最不流氓的一种保活方式了。
app

灰色保活:

     利用系统的漏洞启动前台Service

  灰色保活,这种保活手段是应用范围最普遍。它是利用系统的漏洞来启动一个前台的Service进程,与普通的启动方式区别在于,它不会在系统通知栏处出现一个Notification,看起来就如同运行着一个后台Service进程同样。这样作带来的好处就是,用户没法察觉到你运行着一个前台进程(由于看不到Notification),但你的进程优先级又是高于普通后台进程的

3、进程的优先级

在Android系统中,进程的优先级影响着如下三个因素:

  • 当内存紧张时,系统对于进程的回收策略
  • 系统对于进程的CPU调度策略
  • 虚拟机对于进程的内存分配和垃圾回收策略

系统对于进程的优先级有以下五个分类:

  1. 前台进程
  2. 可见进程
  3. 服务进程
  4. 后台进程
  5. 空进程

前台进程 

指正在与用户进行交互的应用进程,该进程数量较少,是最高优先级进程,系统通常不会终止该进程,而判断为前台进程的因素有如下这些 

 进程中包含处于前台的正与用户交互的activity; 

 进程中包含与前台activity绑定的service;

 进程中包含调用了startForeground()方法的service;

 进程中包含正在执行onCreate(), onStart(), 或onDestroy()方法的service;

 进程中包含正在执行onReceive()方法的BroadcastReceiver. 

可视进程

 能被用户看到,但不能根据根据用户的动做作出相应的反馈, 因素 进程中包含可见但不处于前台进程的activity(如:activity执行onPause()时处于可见状态,但并不处于前台进程中) 该进程有一个与可见/前台的activity绑定数据的service  

服务进程 

没有可见界面仍在不断的执行任务的进程,除非在可视进程和前台进程紧缺资源(如:内存资源)才会被终止 因素 包含除前台进程和可视进程的service外的service的进程 

后台进程

 一般系统中有大量的后台进程,终止后台进程不会影响用户体验,随时为优先级更高的进程腾出资源而被终止,优先回收长时间没用使用过的进程。 因素 包含不在前台或可视进程的activity的进程,也就是已经调用onStop()方法后的activity 

空进程 

为提升总体系统性能,系统会保存已经完成生命周期的应用程序 ,存在与内存当中,也就是缓存,为下次的启动更加迅速而设计。

4、内存阈值

上面是进程的分类,进程是怎么被杀的呢?系统出于体验和性能上的考虑,app在退到后台时系统并不会真正的kill掉这个进程,而是将其缓存起来。打开的应用越多,后台缓存的进程也越多。在系统内存不足的状况下,系统开始依据自身的一套进程回收机制来判断要kill掉哪些进程,以腾出内存来供给须要的app, 这套杀进程回收内存的机制就叫 Low Memory Killer。那这个不足怎么来规定呢,那就是内存阈值,咱们可使用cat /sys/module/lowmemorykiller/parameters/minfree来查看某个手机的内存阈值。


注意这些数字的单位是page。 1 page = 4 kb.上面的六个数字对应的就是(MB): 72,90,108,126,144,180,这些数字也就是对应的内存阀值,内存阈值在不一样的手机上不同,一旦低于该值,Android便开始按顺序关闭进程. 所以Android开始结束优先级最低的空进程,即当可用内存小于180MB(46080*4/1024)。

读到这里,你或许有一个疑问,假设如今内存不足,空进程都被杀光了,如今要杀后台进程,可是手机中后台进程不少,难道要一次性所有都清理掉?固然不是的,进程是有它的优先级的,这个优先级经过进程的adj值来反映,它是linux内核分配给每一个系统进程的一个值,表明进程的优先级,进程回收机制就是根据这个优先级来决定是否进行回收,adj值定义在com.android.server.am.ProcessList类中,这个类路径是${android-sdk-path}\sources\android-23\com\android\server\am\ProcessList.java。

oom_score_adj

对于每个运行中的进程,Linux内核都经过proc文件系统暴露这样一个文件来容许其余程序修改指定进程的优先级:

/proc/[pid]/oom_score_adj。(修改这个文件须要root权限)

这个文件容许的值的范围是:-1000 ~ +1000之间。值越小,表示进程越重要。

当内存很是紧张时,系统便会遍历全部进程,以肯定哪一个进程须要被杀死以回收内存,此时便会读取oom_score_adj 这个文件的值。关于这个值的使用,在后面讲解进程回收的的时候,咱们会详细讲解。

PS:在Linux 2.6.36以前的版本中,Linux 提供调整优先级的文件是/proc/[pid]/oom_adj。这个文件容许的值的范围是-17 ~ +15之间。数值越小表示进程越重要。 这个文件在新版的Linux中已经废弃。

但你仍然可使用这个文件,当你修改这个文件的时候,内核会直接进行换算,将结果反映到oom_score_adj这个文件上。

Android早期版本的实现中也是依赖oom_adj这个文件。可是在新版本中,已经切换到使用oom_score_adj这个文件。oom_adj的值越小,进程的优先级越高,普通进程oom_adj值是大于等于0的,而系统进程oom_adj的值是小于0的,咱们能够经过cat /proc/进程id/oom_adj能够看到当前进程的adj值。



看到adj值是0,0就表明这个进程是属于前台进程,咱们按下Back键,将应用至于后台,再次查看



adj值变成了8,8表明这个进程是属于不活跃的进程,你能够尝试其余状况下,oom_adj值是多少,可是每一个手机的厂商可能不同,oom_adj值主要有这么几个,能够参考一下。




lowmemorykiller机制

OOM全称Out Of Memory,是Linux当中,内存保护机制的一种。该机制会监控那些占用内存过大,尤为是瞬间很快消耗大量内存的进程,为了防止内存耗尽而内核将该进程杀掉。

当Kernel遇到OOM的时候,能够有2种选择:

1) 产生kernelpanic(死机)

2) 启动OOM killer,选择一个或多个“合适”的进程,干掉那些选择中的进程,从而释放内存。

在Linux中,系统就是经过算分去杀死进程的。至于分数的值,就是这3个参数的值。简单来讲,系统是这样进行算分的:算分主要分2部分,一部分是系统打分,主要根据该进程的内存使用状况(oom_score),另外一部分是用户大份额,就是oom_score_adj。每一个进程的实际得分是综合这2个参数的值的。而oom_adj只是一个旧的接口参数,在普通的linux系统中和oom_score_adj是差很少的(可是在android中是颇有用的)。

在Android中,及时用户退出当前应用程序后,应用程序仍是会存在于系统当中,这是为了方便程序的再次启动。可是这样的话,随着打开的程序的数量的增长,系统的内存就会不足,从而须要杀掉一些进程来释放内存空间。至因而否须要杀进程以及杀什么进程,这个就是由Android的内部机制LowMemoryKiller机制来进行的。

Andorid的Low Memory Killer是在标准的linux lernel的OOM基础上修改而来的一种内存管理机制。当系统内存不足时,杀死没必要要的进程释放其内存。没必要要的进程的选择根据有2个:oom_adj和占用的内存的大小。oom_adj表明进程的优先级,数值越高,优先级月低,越容易被杀死;对应每一个oom_adj均可以有一个空闲进程的阀值。Android Kernel每隔一段时间会检测当前空闲内存是否低于某个阀值。假如是,则杀死oom_adj最大的没必要要的进程,若是有多个,就根据oom_score_adj去杀死进程,,直到内存恢复低于阀值的状态。

LowMemoryKiller的值的设定,主要保存在2个文件之中,分别是/sys/module/lowmemorykiller/parameters/adj与/sys/module/lowmemorykiller/parameters/minfree。adj保存着当前系统杀进程的等级,minfree则是保存着对应的阀值。他们的对应关系以下:



举个例子说明一下上表,当当前系统内存少于55296×4K(即216MB)时,Android就会找出当前oom_adj≥9的进程,根据进程的等级,先把oom_adj数值最大的进程给杀掉,释放他的内存,当他们的oom_adj相等时,就对比他们的oom_score_adj,而后oom_score_adj越大,也越容易杀掉。


在这里,也许有人会问,为何采用LowMemoryKiller而不用OOM呢?咱们来对比一下二者,就能够得出答案了。



使用LowMemoryKiller可使系统内存较低时,调出进程管理器结束没必要要的人进程释放空间。在安卓中,若是等到真正的OOM时,也许进程管理器就已经无法启动了。

lowMemorykiller的运行原理

上面其实已经说过,LowMemoryKiller是对多个内存阀值的控制来选择杀进程的。可是,这些阀值是怎样联系在一块儿的呢?下面就个人理解,简单说一下其运行的原理。

首先,LowMemoryKiller是随着系统的启动而启动的。当前主要的LowMemoryKiller的代码主要在\system\core\lmkd的目录下,以前的代码\kernel\drivers\staging\android\lowmemorykiller.c已经再也不使用。

LowMemoryKiller在系统启动的时候就已经由init进程一并启动了。LowMemoryKiller启动就是,就会不断监测系统的运行状况和内存状况,当内存少于minfree限定的阀值的时候,lowMemoryKiller遍历当前进程的oom_score_adj,把大于对应阀值的进程进行kill操做。例如,在当前设置中,当系统内存少于315M时,系统就会自动把进程中oom_score_adj的值少于1000的杀掉,当系统内存少于216时,系统就会自动把进程中oom_score_adj的值少于529的杀掉,如此类推。

至于oom_adj和oom_score_adj是由谁去控制并写入的呢?

在系统当中,oom_adj和oom_score_adj是由ActivityManagerService去控制的,上层应用的启动都离不开AcitivityManagerService的调用与分配资源。有关oom_adj与oom_score_adj会在之后分析ActivityManagerService的时候加入相对详细的论述。在这里就不详细说明。

有关Minfree的值的写入,其实能够找到不少个地方,可是在最开始(还在用lowmemorykiller.c)的时候,是能够在lowmemorykiller.c中设置的。可是如今已经不用lowmemorykiller.c了,因此相对设置的地方也不同了。经查找验证,Minfree的阀值控制,是由ActivictyManagerService和lowmemorykiller一并控制写入的。

5、经常使用的保活套路


前台服务

这种大部分人都了解,听说这个微信也用过的进程保活方案,这方案实际利用了Android前台service的漏洞。
原理以下
对于 API level < 18 :调用startForeground(ID, new Notification()),发送空的Notification ,图标则不会显示。
对于 API level >= 18:在须要提优先级的service A启动一个InnerService,两个服务同时startForeground,且绑定一样的 ID。Stop 掉InnerService ,这样通知栏图标即被移除

相互唤醒 

相互唤醒的意思就是,假如你手机里装了支付宝、淘宝、天猫、UC等阿里系的app,那么你打开任意一个阿里系的app后,有可能就顺便把其余阿里系的app给唤醒了。这个彻底有可能的。此外,开机,网络切换、拍照、拍视频时候,利用系统产生的广播也能唤醒app,不过Android N已经将这三种广播取消了。


粘性服务&与系统服务捆绑

这个是系统自带的,onStartCommand方法必须具备一个整形的返回值,这个整形的返回值用来告诉系统在服务启动完毕后,若是被Kill,系统将如何操做,这种方案虽然能够,可是在某些状况or某些定制ROM上可能失效

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    return START_REDELIVER_INTENT;
}
复制代码
  • START_STICKY
    若是系统在onStartCommand返回后被销毁,系统将会从新建立服务并依次调用onCreate和onStartCommand(注意:根据测试Android2.3.3如下版本只会调用onCreate根本不会调用onStartCommand,Android4.0能够办到),这种至关于服务又从新启动恢复到以前的状态了)。

  • START_NOT_STICKY
    若是系统在onStartCommand返回后被销毁,若是返回该值,则在执行完onStartCommand方法后若是Service被杀掉系统将不会重启该服务。

  • START_REDELIVER_INTENT
    START_STICKY的兼容版本,不一样的是其不保证服务被杀后必定能重启。               


  • JobSheduler

    是做为进程死后复活的一种手段,native进程方式最大缺点是费电, Native 进程费电的缘由是感知主进程是否存活有两种实现方式,在 Native 进程中经过死循环或定时器,轮训判断主进程是否存活,当主进程不存活时进行拉活。其次5.0以上系统不支持。 可是JobSheduler能够替代在Android5.0以上native进程方式,这种方式即便用户强制关闭,也能被拉起来,代码以下:

    JobSheduler@TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public class MyJobService extends JobService {
        @Override
        public void onCreate() {
            super.onCreate();
            startJobSheduler();
        }
    
        public void startJobSheduler() {
            try {
                JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(getPackageName(), MyJobService.class.getName()));
                builder.setPeriodic(5);
                builder.setPersisted(true);
                JobScheduler jobScheduler = (JobScheduler) this.getSystemService(Context.JOB_SCHEDULER_SERVICE);
                jobScheduler.schedule(builder.build());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    
        @Override
        public boolean onStartJob(JobParameters jobParameters) {
            return false;
        }
    
        @Override
        public boolean onStopJob(JobParameters jobParameters) {
            return false;
        }
    }复制代码

    开启一个像素的Activity

    听说这个是手Q的进程保活方案,基本思想,系统通常是不会杀死前台进程的。因此要使得进程常驻,咱们只须要在锁屏的时候在本进程开启一个Activity,为了欺骗用户,让这个Activity的大小是1像素,而且透明无切换动画,在开屏幕的时候,把这个Activity关闭掉,因此这个就须要监听系统锁屏广播. 

    相关文章
    相关标签/搜索