Android的OOM_ADJ

OOM_ADJ (Out of Memory Adjustment)是android系统在内存不足情况下进行内存调整的重要参数,我先看一下OOM_ADJ 都有哪些取值:

OOM_ADJOOM_SCORE_ADJ分类透过何种方式达到
-17 (native)-1000Native 进程
-12 (persistent)-900框架或常驻应用在AndroidManifest.xml设persistent = true
0 (foreground)0前台应用Case 1: 目前正在前台的应用。Case2: 应用正在运行onRecieve()、或是Service.onCreate()、onStartCommand()。
1 (visible)100Case 1:被常驻应用bind住的service或provider。 Case 2:虽非前台,但看得到界面的应用。
2 (perceptible)200Foreground service、IMEstartService()且呼叫startForeground()
5 (A service)500服务startService()
6 (home)600桌面在后台时
7 (previous)700前一个按home键离开的应用
8 (B service)800服务startService()
9 ~15 (cached)900~906Case 1:单纯只有activity的应用。Case2:已执行receiver、service、provider。Case 3:没有stopService()且内含activity

一般常造成问题的状况
1.OOM_ADJ < 0

  • 砍不到
  • Foreground scheduling

2.OOM_ADJ = 1, 2

  • 砍不到
  • Foreground scheduling

3.OOM_ADJ = 5,或>=8

  • 砍了又马上重启(功耗、效能)
  • 重启时callback以foreground scheduling运行

共通影响:应用切换的性能(Multitasking performance)

查询应用的OOM_ADJ

《Android的OOM_ADJ》 image.png

OOM_ADJ与编程

例如是否有人考虑过这么解决broadcast timeout ANR问题

//AndroidManifest.xml
<receiver android:name=".MyReceiver">
    <action android:name=“android.intent.action.BOOT_COMPLETED”/>
</receiver>

//MyReceiver.java
public class MyReceiver extends BroadcastReceiver{
    public void onReceive(Context context, Intent intent) {
        new Thread() {
            public void run() {
            //做超过10秒的事
            }
        }.start();
    }
}

当应用的执行离开了onReceive(),就等同告诉框架,此应用已经执行完receiver 了,不需要再保持高优先级的OOM_ADJ。当系统进行大量广播、或内存较紧时,进程可能很快就被砍掉。此问题在开机或FOTA后特别容易发生。ANR触发原理:https://developer.android.com/training/articles/perf-anr#java

线程无法影响OOM_ADJ,只有进程的行为能影响

  • Broadcast Receiver
  • 在执行onReceive() 时,应用的OOM_ADJ 为0
  • 离开onReceive() 后,视应用其他的状态决定OOM_ADJ
  • Started Service

在执行onCreate()、onStartCommand() 时,应用的OOM_ADJ 为0。之后依序经历几种变化:

  • Service A:Service 刚运行时
  • Service B:若系统同时运行的Service 过多,较早运行的Service 会放到Service B
  • Cached:运行超过30分钟、或曾经启动过activity

Foreground service 能保持OOM_ADJ 处于2,且跟前台应用抢占CPU 时有相同优先级。但滥用foreground service,会导致内存问题

  • Bind Service
  • 若进程A bind 住进程B 的service,ActivityManager会将B的重要性提升至与A 相同。如果A使用完服务后,忘了调用unbindService(),B的重要性就降不下来,极端情况是,若A是常驻的,会导致B也变成常驻了,引发系统性内存问题
  • 若进程A bind住的service位在同一进程,则OOM_ADJ 不会因此有任何改变
  • Provider
  • 若进程A查询进程B的provider得到Cursor,在进程A关闭Cursor之前,ActivityManager会保持provider connection,此时B的OOM_ADJ将提升至与A相同。若进程A属于常驻应用,则B也跟着变成常驻了,就形成严重的内存问题
  • 若应用B的UID为system UID,不可开放content provider给其他应用使用。在安卓的特殊机制中,只要进程A存取(insert/update/delete/pquery) 了B的provider,provider connection 就不再结束

OOM_ADJ分析案例

  • 案例一:
    笔记本应用,如何在用户按home键时存下草稿?
public class MyActivityextends Activity {
    public void onPause() {
        //储存草稿
    }
}

方案一可能导致ANR.

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

方案二,储存草稿可能做不完…
综上,在手机内存不足的情况,应用退到后台后,可能很快被砍掉,但若启动service,则因为此进程启动过activity,依然无法提高OOM_ADJ。故,此种情况适合使用foreground service ,但时间不可太长。

  • 案例二:
    发送短信的Service,若跟Activity运行在同一个进程,有何潜藏危机?
    当用户送出短信时,若马上按下back 键离开应用,此时虽然SendSmsService还在运行,但由于这个进程执行过Activity,故OOM_ADJ会掉到>= 9。若手机内存不足,可能进程会很快被砍掉,导致功能出错

常驻应用

  • 狭义

在AndroidManifest.xml 设定persistent =true 的应用

  • 广义

应用透过某种方式,直接、或间接持续运行在内存都算,例如:

  • 不停止的started service
  • 常驻应用在后台透过bind service 绑住另一进程、且不释放
  • 常驻应用在后台透过provider 绑住另一进程、且不释放
  • 应用透过AlarmManager或类似方式,不断启动,导致被砍了也频繁重启运行在后台
    原文作者:jimjayce
    原文地址: https://www.jianshu.com/p/8897b7e47466
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞