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.<init>(FileInputStream.java:176)
at java.io.FileInputStream.<init>(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<Class, Integer>计数。
(3)StrictMode是建立在BlockGuard和CloseGuard之上的机制,Guard表示守卫,Block表示阻塞,在进行一些耗时操作时,譬如磁盘读写、网络操作,有一个守卫在监测着,它就是BlockGuard,如果这些耗时的操作导致主线程阻塞,BlockGuard就会发出通知,Close对应可打开的文件,在文件被打开后,也有一个守卫在监测着,它就是CloseGuard,如果没有关闭文件,则CloseGuard就会发出通知。
参考
严苛模式StrictMode使用指南
未关闭的文件流会引起内存泄露么
Java9改进的try-with-resources
try-with-resource语句使用
网友评论