Android内存优化- 严苛模式StrictMode使用详解

来源:互联网 发布:淘宝文玩店推荐 编辑:程序博客网 时间:2024/06/04 17:42

StrictMode具体能检测什么

严苛模式主要检测两大问题,一个是线程策略,即TreadPolicy,另一个是VM策略,即VmPolicy。

ThreadPolicy线程策略检测

  • 线程策略检测的内容有
  • 自定义的耗时调用 使用detectCustomSlowCalls()开启
  • 磁盘读取操作 使用detectDiskReads()开启
  • 磁盘写入操作 使用detectDiskWrites()开启
  • 网络操作 使用detectNetwork()开启

VmPolicy虚拟机策略检测(检测内存泄露)

  • Activity泄露 使用detectActivityLeaks()开启
  • 未关闭的Closable对象泄露 使用detectLeakedClosableObjects()开启
  • 泄露的Sqlite对象 使用detectLeakedSqlLiteObjects()开启
  • 检测实例数量 使用setClassInstanceLimit()开启

工作原理

       其实StrictMode实现原理也比较简单,以IO操作为例,主要是通过在open,read,write,close时进行监控。libcore.io.BlockGuardOs文件就是监控的地方。以open为例,如下进行监控。

@Overridepublic FileDescriptor open(String path, int flags, int mode) throws ErrnoException {  BlockGuard.getThreadPolicy().onReadFromDisk();    if ((mode & O_ACCMODE) != O_RDONLY) {      BlockGuard.getThreadPolicy().onWriteToDisk();    }    return os.open(path, flags, mode);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

其中onReadFromDisk()方法的实现,代码位于StrictMode.java中。

public void onReadFromDisk() {    if ((mPolicyMask & DETECT_DISK_READ) == 0) {      return;    }    if (tooManyViolationsThisLoop()) {      return;    }    BlockGuard.BlockGuardPolicyException e = new StrictModeDiskReadViolation(mPolicyMask);    e.fillInStackTrace();    startHandlingViolationException(e);}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

常见用法

       严格模式的开启可以放在Application或者Activity以及其他组件的onCreate方法。为了更好地分析应用中的问题,建议放在Application的onCreate方法中。 
       其中,我们只需要在app的开发版本下使用 StrictMode,线上版本避免使用 StrictMode,这里定义了一个布尔值变量DEV_MODE来进行控制。

private boolean DEV_MODE = true; public void onCreate() {     if (DEV_MODE) {         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()                 .detectCustomSlowCalls() //API等级11,使用StrictMode.noteSlowCode                 .detectDiskReads()                 .detectDiskWrites()                 .detectNetwork()   // or .detectAll() for all detectable problems                 .penaltyDialog() //弹出违规提示对话框                 .penaltyLog() //在Logcat 中打印违规异常信息                 .penaltyFlashScreen() //API等级11                 .build());         StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()                 .detectLeakedSqlLiteObjects()                 .detectLeakedClosableObjects() //API等级11                 .penaltyLog()                 .penaltyDeath()                 .build());     }     super.onCreate(); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

       其中Android3.0引入的方法包括detectCustomSlowCalls()和noteSlowCode(),它们都是用来检测应用中执行缓慢代码的或者潜在的缓慢代码。

查看报告结果

       严格模式有很多种报告违例的形式,但是想要分析具体违例情况,还是需要查看日志,终端下过滤StrictMode就能得到违例的具体stacktrace信息。

adb logcat | grep StrictMode
  • 1
  • 1



  •   StrictMode从 API 11开始允许开发者自定义一些耗时调用违例,这种自定义适用于自定义的任务执行类中,比如我们有一个进行任务处理的类,为TaskExecutor。
public class TaskExecutor {    public void execute(Runnable task) {        task.run();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

       先需要跟踪每个任务的耗时情况,如果大于500毫秒需要提示给开发者,noteSlowCall就可以实现这个功能,如下修改代码

public class TaskExecutor {    private static long SLOW_CALL_THRESHOLD = 500;    public void executeTask(Runnable task) {        long startTime = SystemClock.uptimeMillis();        task.run();        long cost = SystemClock.uptimeMillis() - startTime;        if (cost > SLOW_CALL_THRESHOLD) {            StrictMode.noteSlowCall("slowCall cost=" + cost);        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

执行一个耗时2000毫秒的任务

TaskExecutor executor = new TaskExecutor();executor.executeTask(new Runnable() {  @Override    public void run() {        try {          Thread.sleep(2000);        } catch (InterruptedException e) {            e.printStackTrace();        }    }});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

       得到的违例日志,注意其中~duration=20 ms并非耗时任务的执行时间,而我们的自定义信息msg=slowCall cost=2000才包含了真正的耗时。

  • penaltyDeath(),当触发违规条件时,直接Crash掉当前应用程序。

  • penaltyDeathOnNetwork(),当触发网络违规时,Crash掉当前应用程序。

  • penaltyDialog(),触发违规时,显示对违规信息对话框。

  • penaltyFlashScreen(),会造成屏幕闪烁,不过一般的设备可能没有这个功能。

  • penaltyDropBox(),将违规信息记录到 dropbox 系统日志目录中(/data/system/dropbox),你可以通过如下命令进行插件:

adb shell dumpsys dropbox dataappstrictmode  --print
  • 1
  • 1
  • permitCustomSlowCalls()、permitDiskReads ()、permitDiskWrites()、permitNetwork: 如果你想关闭某一项检测,可以使用对应的permit*方法。



VMPolicy 详解

StrictMode.VmPolicy.Builder 主要方法如下

  • detectActivityLeaks() 用户检查 Activity 的内存泄露情况

内存泄露检查案例:public class MainActivity extends AppCompatActivity implements View.OnClickListener {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()                .detectActivityLeaks()                .penaltyLog()                .build()        );        new Thread() {            @Override            public void run() {                while (true) {                    SystemClock.sleep(1000);                }            }        }.start();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

我们反复旋转屏幕就会输出提示信息(重点在 instances=2; limit=1 这一行) 
这里写图片描述 



  这时因为,我们在Activity中创建了一个Thread匿名内部类,而匿名内部类隐式持有外部类的引用。而每次旋转屏幕是,Android会新创建一个Activity,而原来的Activity实例又被我们启动的匿名内部类线程持有,所以不会释放,从日志上看,当先系统中该Activty有4个实例,而限制是只能创建1各实例。我们不断翻转屏幕,instances 的个数还会持续增加。

  • detectLeakedSqlLiteObjects() 和 
    detectLeakedClosableObjects()的用法类似,只不过是用来检查 SQLiteCursor 或者 其他 SQLite 
    对象是否被正确关闭

  • detectLeakedRegistrationObjects() 用来检查 BroadcastReceiver 或者 
    ServiceConnection 注册类对象是否被正确释放

  • setClassInstanceLimit(),设置某个类的同时处于内存中的实例上限,可以协助检查内存泄露

检测内存泄露案例public class MainActivity extends AppCompatActivity implements View.OnClickListener {    private static class CastielClass{}    private static List<CastielClass> classList;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        classList = new ArrayList<CastielClass>();        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()                .setClassInstanceLimit(CastielClass.class, 2)                .penaltyLog()                .build());        classList.add(new CastielClass());        classList.add(new CastielClass());        classList.add(new CastielClass());        classList.add(new CastielClass());        classList.add(new CastielClass());        classList.add(new CastielClass());        classList.add(new CastielClass());        classList.add(new CastielClass());    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

运行后触发警告如下

这里写图片描述

其他操作

       除了通过日志查看之外,我们也可以在开发者选项中开启严格模式,开启之后,如果主线程中有执行时间长的操作,屏幕则会闪烁,这是一个更加直接的方法。 


注意事项

  • 只在开发阶段启用StrictMode,发布应用或者release版本一定要禁用它。
  • 严格模式无法监控JNI中的磁盘IO和网络请求。
  • 应用中并非需要解决全部的违例情况,比如有些IO操作必须在主线程中进行。



0 0
原创粉丝点击