Android内存管理篇 - adj的概念与进程adj级别控制

本文主要介绍Android的lowmemorykiller的oom_adj的相关概念,以及根据一些案例来阐述了解oom_adj对于作Android应用开发的重要意义。java

1、lowmeorykiller中进程的分类以及各种进程的adj值android

        在Android的lowmemroykiller机制中,会对于全部进程进行分类,对于每一类别的进程会有其oom_adj值的取值范围,oom_adj值越高则表明进程越不重要,在系统执行低杀操做时,会从oom_adj值越高的开始杀。系统lowmemeorykiller机制下对于进程的级别的以变量的形式定义在framework/base/core/java/com/android/server/am/ProcessList.java类中,可总结成下表:web

 

 

再补充介绍一下:数据库

1.AMS角度对于进程的分级       app

上表带分级只是从lowmemroykiller角度来分的,时用于lowmemeorykiller执行杀进程操做,可是从android的系统管理角度看,便是从AMS执行相关逻辑时,又有一套本身的分级机制,固然这两套机制也有着不少互通的点。AMS角度的级别划分以变量的形式定义在framework/base/core/java/android/app/ActivityManager.java类中,以PROCESS_STATE开头的变量。框架

2.没有stopService其内含activity的后台进程ide

        这类进程从lowmemorykiller角度是划分为cached,由于若是这类进程每每占有较大的内存,这类含有activity的后台进程每每占有较大内存,因此即便这类进程包含了Service,lowmemorykiller的机制也会更加倾向于优先杀死这类进程。ui

        可是通常启动了服务的进程每每是但愿服务在后台可以执行某些任务,这样看是不但愿这些服务由于进程被杀而过早的被终止的,那如何调和这种矛盾呢?正确的作法是,对于指望较长时间留在后台的服务,应该将服务运行在单独的进程里,便是UI进程与Servie进程分离,这样指望长时间留在后台的Serivce会存在与一个被lmk分类为Service 进程的服务而得到较小的Adj值,而占有大量内存的UI进程则会分类为Cached进程,可以在须要的时候更快地被回收。this

        还有一点,这类进程虽然被lmk划分为cached进程,可是从ams角度是被划分为PROCESS_STATE_SERVICE这个类别的,即视为服务进程,在ams相关流程中也是以服务进程来执行相关逻辑的,此外在使用dumpsys meminfo查看全部进程时,这类进程也是被列在B service这个类别的。spa

3.A-Service与B-Service的划分

        全部启动了服务的进程,且该服务所在的进程没有显示过UI,且该服务未执行startForeground(执行后会变为perveptible服务)动做,那该进程则为A-Service与B-Service中的一种。而后根据这类服务进程所处于Lru进程表中的位置,前1/3点服务为A-Service,其他的则为B-Service。

4.perceptible的标准

        perceptible名为可感知的进程,但并非说可以感知到进程就必定表示该进程属于perveptible进程,好比播放音乐的进程活着状态栏上有通知的进程,虽然可以感知到进程的存在,可是不表明进程必定时perceptible类别的进程。决定该进程是否属于perceptible进程并未进程的可感知性,而是该进程的服务是否执行了startForeground动做。 

 

2、如何查询应用的adj级别

1.dumpsys meminfo

        使用dumpsys meminfo命令时,会列出当前系统的全部进程,不一样进程放入不一样的分类,对应的分类名基本与lmk的分类一致。有一点不一样的就是,退到后台启动了服务且显示过UI的进程,在dumpsys meminfo命令中会归为b service一类,但从lmk角度分配的oom_adj值为9~16的范围,属于cached一类

2.cat /proc/[PID]/oom_adj:  使用该命令会直接显示出对应进程号的adj值

 

3、未控制好oom_adj的案例

1.ui进程启动service的隐患

案例a:备份进程启动一个服务开始执行备份,备份服务运行在ui进程(服务未调用startForeground())

隐患:备份服务通常须要较长时间,在用户按Home键退出后台后,备份进程会处于previous状态,继续使用手机其余应用,会是使得备份进程处于cch-started-ui-services的状态,便是启动了服务而且包含ui的进程退到后台状态,此时进程的adj值处于9~16,随着时间推移逐渐增大。若是在较长的备份过程当中,触发了lowmemorykiller,很容易致使备份进程被杀掉,从而致使备份的失败。

案例b:备份进程启动一个服务开始执行备份,备份服务运行在ui进程(服务调用了startForeground())

隐患:这种状况下备份进程会被划分为perceptible进程,基本上是不会被lowmemorykiller杀掉的,可是这也致使内存占用较大的备份常驻了,从内存管理角度来将,备份进程的UI部分是并不指望他常驻的,而大量内存的常驻也容易致使lowmemorykiller的出现,从而致使系统进入内存较低的等级,而当系统处于内存较低等级时,会触发系统回调全部进程进行进程回收动做,容易致使系统卡顿场景的出现。此外,调用了startForeground()会致使进程被系统断定为前景进程,这样备份进程便会抢占用户操做手机时前台应用的cpu资源,增长了卡顿场景出现的概率。

解决方法:将Service运行在独立的进程,这样应用退到后台后,备份服务进程会处于A-Service中(逐渐掉落到B-Service),而B-Service进程通常也是很难被lowmemorykiller砍。该独立是否要startForeground()?若是指望保证备份尽快到完成,即可以牺牲一些用户在操做其余应用时到用户体验,将服务推为前景应用;对于不少须要保证功能的流畅运行的服务进程,例如音乐播放,录音等,则须要将这类服务进程经过startForeground()设置为前景进程,但前提仍是须要作到ui与Service分离。

 

2.使用线程解决耗时操做形成anr问题的隐患

案例:短信、邮件、或笔记本应用,在用户按BACK键时存下草稿

public class MyActivity extents Activity {
    public void onPause(){
        //存储草稿
    }
}

问题(1):因为存储草稿定操做通常时保存到数据库,某些状况下可能会占用较长时间,这里就有可能致使anr的隐患

解决方案1:

public class MyActivity extents Activity {
    public void onPause(){
        new Thread() {
            public void run() {
                //存储草稿
            }
        }.start()
    }
}            

问题(2):当用户以back键离开应用时(以home键离开会处于previous状态),应用退到后台处于empty-cached状态,内存不足时,可能会马上杀。

解决方案2:若是线程有这问题,是否能够用服务来完成存储草稿的动做呢?

问题(3):若是用服务来存储草稿,即将存储草稿动做写在onStartCommand中,因为onStartCommmand操做依旧是执行在主线程的,因此在其中执行耗时操做时,依旧可能会致使ANR

最终解决方案:使用IntentService来执行保存草稿的动做

public class MyActivity extents Activity {
    public void onPause(){
        ...  
        startService(new Intent(this, MyIntentService.class));
        ...
    }
}

public class MyIntentService extends IntentService {
    protected void onHandleIntent(Intent intent) {
        //保存草稿
    }
}

 

3.provider被binder致使的隐患

案例:systemui进程获取手机中的手机管家应用提供的content provider,用于获取当前应用相关信息

问题:管家应用的UID为System,在Android机制中,System UID进程提供的provider一旦被访问,即便访问完成关掉provider后,链接依旧存在,全部管家应用因为其provider持续被persistent进程咬住,因此管家应用便会长时间处于foreground级别的应用中,oom_adj为0,致使管家应用占用的大量内存很难被回收

解决方案:单独使用一个进程提供provider,提供provider进程因为占用内存较小,因此即便没法被回收也影响较小,这样管家应用的UI进程可以按照系统正常的回收流程在须要时被回收

 

 

4、总结一些经验

1.进程在启动服务后,在事情作完后,必须呼叫stopService或stopSelf通知框架,避免事情作完了,服务进程依旧常驻内存

2.对于须要长时间停留在后台的服务,且服务设置为具备重启特性时,须要作到ui与service分离,一避免占用内存较大的ui进程的常驻

3.对于须要长时间停留在后台的服务,且服务设置为具备重启特性时,不可长时间bind住其余进程的service或provider,避免其余进程常驻;

4.常驻性质的进程(oom_adj<=6),不可一直bind住其余进程的服务或provider,用完必须立刻放掉

5.不可以使用SystemUID进程内的provider,在Android设计中,若针对System UID的进程使用provider,即便已关掉provider,但框架仍会保持provider connection

6.利用onStartCommand方法回传值(START_STICKY,START_NOT_STICKY等)控制好服务的重启属性,在设计时充分考虑进程被lmk杀死的状况

7.IntentService继承自Service,针对CPU scheduling、工做排程等都有完整实现,建议多采用IntentnService进行功能实现

相关文章
相关标签/搜索