你了解Android LMK机制么?

前言

上文主要介绍了Andorid内存的管理机制,本文对其中的LMK机制进行深入扩展总结。

我们知道出现Crash应用闪退和崩溃一般有三个原因:ANR(程序无响应)、Exception(异常)、LMK(低内存杀死机制)。本文重点介绍LMK机制。

目的:了解LMK原理,探究进程保活的方案和程序异常处理的方法。

一、原理篇

1. 什么是Android LMK

KMK,(Low Memory Killer )低内存杀死机制。由于Android应用的沙箱机制,每个应用程序都运行在一个独立的进程中,各自拥有独立的Dalvik虚拟机实例,系统默认分配给虚拟机的内存是有限度的,当系统内存太低依然会触发LMK机制,即出现闪退、崩溃现象。

不同厂商不同,如:华为mate7,192M ;小米4,128M ;红米,128M 。而在,Android4.0以后,可以通过在application节点中设置属性android:largeHeap=”true”来设置最大可分配多少内存空间就可以突破一定限制。

2. OOM

OOM(OutOfMemoryError)内存溢出错误 。导致OOM的两个主要原因:

1、内存泄漏,大量无用对象未及时回收,导致后续申请内存失败。

2、BitMap大对象,几个大图同时加载很容易触发OOM。

通过阅读本文,可以了解Android LMK机制,从而避免App被频繁的杀死,以及一些开发中会遇到的问题。

为了空出足够的内存供前台进程使用,Android会定时进行CHECK进程树,然后杀死优先级别不高的进程。而进程的优先级别是按照属性 oom_adj 来判断的。oom_adj数值越低,越不会被杀死。

3. oom_adj的值是如何赋予的

oom_adj的数字大致为这几种:

FOREGROUND_APP_ADJ 0 前台进程,正在活动的Activity或者使用startForeground的Service
VISIBLE_APP_ADJ 1 可见进程,不可操作的Activity,但是可见
SECONDARY_SERVER_ADJ 2 拥有后台服务器的进程
HIDDEN_APP_MIN_ADJ 7 Activity没有完全退出,直接采用 moveTaskToBack 到HOME的进程
CONTENT_PROVIDER_ADJ 14 内容提供进程
EMPTY_APP_ADJ 15 空程序,既不提供服务,也不提供内容
CORE_SERVER_ADJ -12 系统进程
SYSTEM_ADJ -16 系统核心服务(进程永远不会被杀掉)

当系统的内存不足的时候,那么就会杀死发送KILL SIGNAL, 杀死一些优先级别低的进程,用来提供足够的内存给前台进程使用。

用命令可以查看进程的优先级的值:

adb shell dumpsys activity|grep oom_adj 

在这里插入图片描述

查看进程命令:

adb shell dumpsys activity processes

在这里插入图片描述

注意:

  • Low memory killer 则是定时进行检查。

  • Low memory killer 主要是通过进程的oom_adj 来判定进程的重要程度。这个值越小,程序越重要,被杀的可能性越低。

  • oom_adj的大小和进程的类型以及进程被调度的次序有关。

4. LMK的工作机制

LMK开始工作时,首先根据阈值表确定当前的警戒级数,则高于警戒级数的进程是待杀的范围。

然后遍历所有进程的oom_adj值,找到大于min_adj的进程,若找到多个,则把占用进程最大的进程存放在selected中。最关键的一步就是,发送SIGKILL信息,杀掉该进程。

5.Android进程优先级

5.1 Android进程的优先级

一般情况下,Android会尽可能的保持应用进程,但在特定的场景会对进程进行Kill,例如为了清除旧进程来回收内存等。为了区分哪些进程最先被回收清理,而哪些不会,有一个优先级别,这就是Android的进程优先级,具体包括以下5种(优先级从高到低)。

  • Foreground/Activate process 前台进程。用户当前操作的进程,包括用户正在交互的Activity,绑定用户正在交互Activity的Service,使用startForeground的Service,正在执行onReceive的BroadcastReceiver等。
  • Visible process 可见进程。会影响用户所见内容的进程,如onPause状态的Activity等。
  • Service process 服务进程。后台服务,如正在运行startService启动的Service。
  • Background process 后台进程。对用户交互无影响,如onStop状态的Activity等。
  • Empty process 空进程。一般用作缓存以缩短下次启动时间,系统往往会终止这些空进程。

5.2. Android进程的回收策略

Android主要通过LMK(Low Memory Killer)来对进程进行回收管理,LMK是在Android系统内存不足而选择kill部分进程释放空间,生死大权的决定者,其基于Linux的OOM机制,LMK通过oom_adj与占用内存的大小决定要杀死的进程,oom_adj值越小,越不容易被杀死。上面的几种进程形态对于的不同的oom_adj值。前台进程的优先级为0,普通service的进程优先级是8。

5.3 保活的方法

一方面提高进程优先级,降低被系统kill的概率。另一方面,在App被杀死以后进行拉活。知道了原理我们就可以采用一定的策略来提高应用的存活几率。

二、方法篇

进程保活说白了就是保证自己App进程不死,App被杀死有以下几种可能:

  • 被系统杀死
  • 被用户杀死
  • 被竞争对手杀死

了解LMK机制后,就可以对系统杀死的情况做相对应的优化。

  1. 通过在androidmanifest.xml中的application标签中加入android:persistent="true"属性后的确就能够达到保证该应用程序所在进程不会被LMK杀死。

但有个前提就是应用程序必须是系统应用,也就是说应用程序不能采用通常的安装方式。必须将应用程序的apk包直接放到/system/app目录下。而且必须重启系统后才能生效。

应用场景:定制TV端、平板端和手机端预装系统软件。

关键词:加入白名单、成为系统软件。

有些手机厂商把这些知名的app放入了自己的白名单中,保证了进程不死来提高用户体验(如微信、QQ、百度全家桶等在小米的白名单中)。如果从白名单中移除,他们终究还是和普通app一样躲避不了被杀的命运。

  1. 在程序中考虑异常处理,如恢复数据状态
static final String STATE_SCORE = "playerScore";
static final String STATE_LEVEL = "playerLevel";
...

@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
    // 保存用户自定义的状态
    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
    savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);
    
    // 调用父类交给系统处理,这样系统能保存视图层次结构状态
    super.onSaveInstanceState(savedInstanceState);
}
  1. 程序退出时 做类似Home处理。将程序退到后台而不是kill程序。

在根Activity中重写后退按钮响应事件,当按后退按钮的时候把Activity退置到后台

     @Override
     public boolean onKeyUp(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_BACK) {
                moveTaskToBack(true);        
             return true;
         }
         return super.onKeyDown(keyCode, event);
     }

其中的moveTaskToBack

   /**
     * Move the task containing this activity to the back of the activity
     * stack.  The activity's order within the task is unchanged.
     *
     * @param nonRoot If false then this only works if the activity is the root
     *                of a task; if true it will work for any activity in
     *                a task.
     *
     * @return If the task was moved (or it was already at the
     *         back) true is returned, else false.
     */ 
public boolean moveTaskToBack(boolean nonRoot) {
        try {
            return ActivityManager.getService().moveActivityTaskToBack(
                    mToken, nonRoot);
        } catch (RemoteException e) {
            // Empty
        }
        return false;
    }

说明:当nonRoot 为 false 时,当前activity必须为栈底,也就是最底层的activity,如果其他activity没有及时finish掉,就会出现异常,导致崩溃等情况的发生;nonRoot 为 true 时,不需要考虑当前activity是否在栈底。

  1. 程序性能优化,回归问题的本质。

系统统内存不足的时候肯定优先杀死这些占用内存高的进程来腾出资源。所以,为了尽量避免后台UI进程被杀,需要尽可能的释放一些不用的资源,尤其是图片、音视频之类的。对此,我们要想程序不被杀死,必须当前程序的内存占空间有比其他程序小才有竞争力不被优化kill掉。

最后在进一步总结LMK的机制和实际运用的意义时,看看这个博主是如何总结的:

关于了解LMK之后的内存管理建议:

  1. 我个人是长期在手机厂商从事APP和framework开发的。从外部来看,如果一个应用的内存总是不够用而又经常被回收,那么是可以从各个层面来缓解这种情况的——设置应用进程的oom_adj为更低(这种修改不仅可在底层也可在app层进行),修改各等级阀值等等,这是在不涉及应用本身修改的情况下,能做出的有限修改。可惜对于日常的应用开发者来说,这绝对不是康庄大道。
  2. 对于一般的应用开发者而言,安卓设备的底层已经封闭,甚至ROOT权限也无法获得,他们能做的更多是从应用自身进行优化,来使得应用更不容易被杀死回收。要做到这一点,就要深刻理解LMK的权重系统。比如运行那些需要后台运行的任务,用service而不要用后台进程;又比如如果你在开发输入法等应用,你自然能通过某些设置使得它变成高优先级的可视进程而不是后台进程。
  3. 当然,既然有所谓阀值,最治本的方法当然是减少进程的内存占用。作为普通应用开发者,你不能默认自己的应用能始终在前台,更不可能无限制地重启自己的进程(最好别这么干),提高应用各层级的阀值,锲而不舍地减少应用各个部分所占用的内存,并更顺畅地帮助内存的释放(是的。。。JAVA),才是开发内存占用小,运行稳定顺畅的好应用的优秀开发者素质。

三、总结

了解Android LMK机制后,有助于在开发程序时 对系统的内存回收机制采取一定的措施来提高程序的体验性,同时最核心的问题还是在程序的开发内功上做指导意义,毕竟,如何管理好内存,是我们一直需要探讨和实践的问题。未完待续……

参考资料:

1.onSaveInstanceState()和onRestoreInstanceState()使用详解

2.Android之进程回收机制LMK(Low Memory Killer)

3.Android APP开发的内存管理与优化之一 ——LowMemory Killer

发布了53 篇原创文章 · 获赞 217 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/jun5753/article/details/105670683