Android严格模式

1.背景

为防止出现资源泄漏或者主线程发生的意外耗时网络操作或IO操作导致卡顿,Android2.3(API9)开始提供了监测接口StrictMode类,针对单个线程和虚拟机的所有对象定义了检查策略。监测到异常后日志能打印到具体的代码堆栈,便于立刻排查解决。

2.使用方式

应用启动处设置严苛模式,可在Application.onCreate()或Activity中启用监测,包含线程级别和进程级别两类监测接口

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        if (BuildConfig.DEBUG) {
            enableStrictMode();
        }
    }

    private void enableStrictMode() {
        // 监测当前线程(UI线程)上的网络、磁盘读写等耗时操作
        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
             .detectDiskReads()  // 监测读磁盘
             .detectDiskWrites()  // 监测写磁盘
             .detectNetwork()      // 监测网络操作
             .detectCustomSlowCalls()  // 监测哪些方法执行慢
             .detectResourceMismatches()  // 监测资源不匹配
             .penaltyLog()   // 打印日志,也可设置为弹窗提示penaltyDialog()或者直接使进程死亡penaltyDeath()
             .penaltyDropBox()  //监测到将信息存到Dropbox文件夹 data/system/dropbox
             .build());

      // 监测VM虚拟机进程级别的Activity泄漏或者其它资源泄漏
     StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
             .detectActivityLeaks()  // 监测内存泄露情况
             .detectLeakedSqlLiteObjects()  // SqlLite资源未关闭,如cursor
             .detectLeakedClosableObjects()  // Closable资源未关闭,如文件流
             .detectCleartextNetwork()  // 监测明文网络
             .setClassInstanceLimit(MyClass.class, 1)  // 设置某个类的实例上限,可用于内存泄露提示
             .detectLeakedRegistrationObjects()  // 监测广播或者ServiceConnection是否有解注册
             .penaltyLog()
             .build());
    }
}

可按需建造需要监测的场景,也可使用Builder().detectAll()监测所有情况。
比如文件流未关闭,打印的日志提示如下,可根据堆栈找到导致问题的地方

2022-01-11 11:11:53.096 20010-20031/com.hello.myApp D/StrictMode: StrictMode policy violation: android.os.strictmode.LeakedClosableViolation: 
A resource was acquired at attached stack trace but never released. 
See java.io.Closeable for information on avoiding resource leaks.
        at android.os.StrictMode$AndroidCloseGuardReporter.report(StrictMode.java:1987)
        at dalvik.system.CloseGuard.warnIfOpen(CloseGuard.java:345)
        at java.io.FileInputStream.finalize(FileInputStream.java:503)
        at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:291)
        at java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:278)
        at java.lang.Daemons$Daemon.run(Daemons.java:139)
        at java.lang.Thread.run(Thread.java:930)
     Caused by: java.lang.Throwable: Explicit termination method 'close' not called
        at dalvik.system.CloseGuard.openWithCallSite(CloseGuard.java:295)
        at dalvik.system.CloseGuard.open(CloseGuard.java:263)
        at java.io.FileInputStream.(FileInputStream.java:176)
        at java.io.FileInputStream.(FileInputStream.java:115)
        ... 自己代码调用栈

ps: 开发人员选项里的严格模式跟这个有差别
开发者选项中开启严格模式,已提示应用在主线程上执行长时间操作时闪烁屏幕


开发人员选项-严格模式

3.try-with-resources语句关闭资源

一种自动关闭资源的方式能更优雅处理资源关闭,JDK7中一个新的异常处理机制,在处理必须关闭的资源时,使用try-with-resources语句替代try-finally语句。
try-finally语句使用

InputStream in = null;
try {
    in = new FileInputStream(file);
    int temp;
    while ((temp = in.read()) != -1) {
        System.out.write(temp);
    }
} catch (IOException e) {
    // todo
} finally {
    in.close();
}

替换为try-with-resources语法,即try后面加括号引入资源

try (InputStream in = new FileInputStream(file)) {
    in = new FileInputStream(file);
    int temp;
    while ((temp = in.read()) != -1) {
        System.out.write(temp);
    }
} catch (IOException e) {
    // todo
}

实现AutoCloseable或Closeable接口均可使用此操作,可将实现此接口的类定义为资源,自己定义的资源也可以实现此接口。
ps: try-with-resources只能在JDK7及以上使用,必选在括号内有变量声明,JDK9之后括号内直接使用外部定义的变量即可

4.原理

(1)通过在需要监控的代码中植入代码实现此功能,比如IO中通过在open,read,write,close时进行监控,从上面监听到异常打印的日志堆栈即可看出植入的代码

android.os.StrictMode$AndroidCloseGuardReporter.report(StrictMode.java:1987)
        at dalvik.system.CloseGuard.warnIfOpen(CloseGuard.java:345)
        at java.io.FileInputStream.finalize(FileInputStream.java:503)
image.png

Object finalize()方法当GC确定不存在对该对象的有更多引用时,对象的垃圾回收器就会调用这个方法,FileInputStream将被回收时判断资源还未关闭随即提示。
(2)再如监测Acitivity泄露时,performLaunchActivity、performDestroyActivity方法中启动和回收Activity时用HashMap计数。
(3)StrictMode是建立在BlockGuard和CloseGuard之上的机制,Guard表示守卫,Block表示阻塞,在进行一些耗时操作时,譬如磁盘读写、网络操作,有一个守卫在监测着,它就是BlockGuard,如果这些耗时的操作导致主线程阻塞,BlockGuard就会发出通知,Close对应可打开的文件,在文件被打开后,也有一个守卫在监测着,它就是CloseGuard,如果没有关闭文件,则CloseGuard就会发出通知。

参考

严苛模式StrictMode使用指南
未关闭的文件流会引起内存泄露么
Java9改进的try-with-resources
try-with-resource语句使用

你可能感兴趣的:(Android严格模式)