strictmode是android提供的一种调试环境的动态检测机制,主要的作用有两类:
检测主线程读写卡顿
detectDiskReads();
detectDiskWrites();
检测是否主线程使用网络
detectNetwork();
检测自定义的慢检测
detectCustomSlowCalls()
资源不匹配检测
detectResourceMismatches()
没有buffer的IO操作
detectUnbufferedIo()
具体的代码位置:
StrictMode#ThreadPolicy#Builder.detectAll
public @NonNull Builder detectAll() {
detectDiskReads();
detectDiskWrites();
detectNetwork();
final int targetSdk = VMRuntime.getRuntime().getTargetSdkVersion();
if (targetSdk >= Build.VERSION_CODES.HONEYCOMB) {
detectCustomSlowCalls();
}
if (targetSdk >= Build.VERSION_CODES.M) {
detectResourceMismatches();
}
if (targetSdk >= Build.VERSION_CODES.O) {
detectUnbufferedIo();
}
return this;
}
检测sqlite泄漏
detectLeakedSqlLiteObjects();
检测activity泄漏:注意这里是引用计数,实际无法检测出真的泄漏,后续会进行详细讲解
detectActivityLeaks()
检测是否需要关闭的没有关闭,比如IO流
detectLeakedClosableObjects
检测是否有组件忘记反注册(BroadCastReceiver,Service)
detectLeakedRegistrationObjects()
检测File Uri是否暴露
detectFileUriExposure()
检测是否明文网络请求
detectCleartextNetwork()
检测不正确的conext使用
detectIncorrectContextUse()
检测不安全的intent launch
detectUnsafeIntentLaunch()
源代码:
public @NonNull Builder detectAll() {
detectLeakedSqlLiteObjects();
final int targetSdk = VMRuntime.getRuntime().getTargetSdkVersion();
if (targetSdk >= Build.VERSION_CODES.HONEYCOMB) {
detectActivityLeaks();
detectLeakedClosableObjects();
}
if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN) {
detectLeakedRegistrationObjects();
}
if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
detectFileUriExposure();
}
if (targetSdk >= Build.VERSION_CODES.M) {
// TODO: always add DETECT_VM_CLEARTEXT_NETWORK once we have
// facility for apps to mark sockets that should be ignored
if (SystemProperties.getBoolean(CLEARTEXT_PROPERTY, false)) {
detectCleartextNetwork();
}
}
if (targetSdk >= Build.VERSION_CODES.O) {
detectContentUriWithoutPermission();
detectUntaggedSockets();
}
if (targetSdk >= Build.VERSION_CODES.Q) {
detectCredentialProtectedWhileLocked();
}
if (targetSdk >= Build.VERSION_CODES.R) {
detectIncorrectContextUse();
}
if (targetSdk >= Build.VERSION_CODES.S) {
detectUnsafeIntentLaunch();
}
// TODO: Decide whether to detect non SDK API usage beyond a certain API level.
// TODO: enable detectImplicitDirectBoot() once system is less noisy
return this;
}
private fun strictModeOnDebug() {
if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= 28) {
StrictMode.setThreadPolicy(
ThreadPolicy.Builder()
.detectAll() //检测Thread所有选项
.penaltyLog() //打印日志
.build()
)
StrictMode.setVmPolicy(
VmPolicy.Builder()
.detectAll() //检测VM所有选项
.penaltyLog() //打印日志
.build()
)
TrafficStats.setThreadStatsTag(0xF00D)
}
}
findViewById<Button>(R.id.io_read_btn).setOnClickListener {
val outputStream = FileOutputStream(File(getExternalFilesDir("")?.path + "hello.json"))
outputStream.write("hello world".toByteArray())
outputStream.flush()
outputStream.close()
}
logcat日志:
2022-06-20 07:57:35.043 13012-13012/com.ifreedomer.strictmode D/StrictMode: StrictMode policy violation; ~duration=1 ms: android.os.strictmode.DiskWriteViolation
at android.os.StrictMode$AndroidBlockGuardPolicy.onWriteToDisk(StrictMode.java:1460)
at libcore.io.BlockGuardOs.write(BlockGuardOs.java:347)
at libcore.io.IoBridge.write(IoBridge.java:526)
at java.io.FileOutputStream.write(FileOutputStream.java:381)
at java.io.FileOutputStream.write(FileOutputStream.java:359)
at com.ifreedomer.strictmode.MainActivity.onCreate$lambda-0(MainActivity.kt:26)
at com.ifreedomer.strictmode.MainActivity.$r8$lambda$J_2M7j2bq49wREm_StCdiMLvSuc(Unknown Source:0)
at com.ifreedomer.strictmode.MainActivity$$ExternalSyntheticLambda0.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:6597)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1194)
at android.view.View.performClickInternal(View.java:6574)
at android.view.View.access$3100(View.java:778)
at android.view.View$PerformClick.run(View.java:25885)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
读日志也同理。需要注意的是,IO我们确实需要经常性的在主线程进行,为避免过多的日志对开发造成干扰,推荐使用时可以屏蔽此选项
IO未关闭检测
示例代码:
findViewById<Button>(R.id.io_not_close_btn).setOnClickListener {
var outputStream = FileOutputStream(File(getExternalFilesDir("")?.path + "hello.json"))
outputStream.write("hello world".toByteArray())
outputStream = FileOutputStream(File(getExternalFilesDir("")?.path + "hello.json"))
Runtime.getRuntime().gc()
Runtime.getRuntime().gc()
}
logcat:
2022-06-20 08:11:36.071 13677-13687/com.ifreedomer.strictmode 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:1786)
at dalvik.system.CloseGuard.warnIfOpen(CloseGuard.java:264)
at java.io.FileOutputStream.finalize(FileOutputStream.java:475)
at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:250)
at java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:237)
at java.lang.Daemons$Daemon.run(Daemons.java:103)
at java.lang.Thread.run(Thread.java:764)
Caused by: java.lang.Throwable: Explicit termination method 'close' not called
at dalvik.system.CloseGuard.open(CloseGuard.java:221)
at java.io.FileOutputStream.<init>(FileOutputStream.java:241)
at java.io.FileOutputStream.<init>(FileOutputStream.java:180)
at com.ifreedomer.strictmode.MainActivity.onCreate$lambda-4(MainActivity.kt:57)
at com.ifreedomer.strictmode.MainActivity.$r8$lambda$sFdQJzxyBqsXzAQCFxp0PCpUtgg(Unknown Source:0)
at com.ifreedomer.strictmode.MainActivity$$ExternalSyntheticLambda3.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:6597)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1194)
at android.view.View.performClickInternal(View.java:6574)
at android.view.View.access$3100(View.java:778)
at android.view.View$PerformClick.run(View.java:25885)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
数据库泄漏检测
cursor未关闭
findViewById<Button>(R.id.database_open_btn).setOnClickListener {
val readableDatabase =
dbHelper.readableDatabase
var rawQuery = readableDatabase.rawQuery("select * from user", null)
val sqLiteCursor = rawQuery as SQLiteCursor
sqLiteCursor.moveToFirst()
Log.d(TAG,"sql window = ${sqLiteCursor.window}")
rawQuery = null
readableDatabase.close()
Runtime.getRuntime().gc()
}
logcat:
2022-06-20 08:00:19.687 13012-13022/com.ifreedomer.strictmode D/StrictMode: StrictMode policy violation: android.os.strictmode.SqliteObjectLeakedViolation: Finalizing a Cursor that has not been deactivated or closed. database = /data/user/0/com.ifreedomer.strictmode/databases/haha.db, table = null, query = select * from user
at android.os.StrictMode.onSqliteObjectLeaked(StrictMode.java:1956)
at android.database.sqlite.SQLiteCursor.finalize(SQLiteCursor.java:285)
at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:250)
at java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:237)
at java.lang.Daemons$Daemon.run(Daemons.java:103)
at java.lang.Thread.run(Thread.java:764)
Caused by: android.database.sqlite.DatabaseObjectNotClosedException: Application did not close the cursor or database object that was opened here
at android.database.sqlite.SQLiteCursor.<init>(SQLiteCursor.java:103)
at android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:52)
at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1408)
at android.database.sqlite.SQLiteDatabase.rawQuery(SQLiteDatabase.java:1347)
at com.ifreedomer.strictmode.MainActivity.onCreate$lambda-2(MainActivity.kt:43)
at com.ifreedomer.strictmode.MainActivity.$r8$lambda$KlEPG7__y4DedDU3EV-gqJ4mzpo(Unknown Source:0)
at com.ifreedomer.strictmode.MainActivity$$ExternalSyntheticLambda3.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:6597)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1194)
at android.view.View.performClickInternal(View.java:6574)
at android.view.View.access$3100(View.java:778)
at android.view.View$PerformClick.run(View.java:25885)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6669)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
如果你想获得这些泄漏的信息,你可以自己实现StrictMode的Listener
if (BuildConfig.DEBUG && Build.VERSION.SDK_INT >= 28) {
StrictMode.setThreadPolicy(
ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.penaltyListener(Executors.newSingleThreadExecutor(),{
})
.build()
)
StrictMode.setVmPolicy(
VmPolicy.Builder()
.detectAll()
.penaltyLog()
.penaltyListener(Executors.newSingleThreadExecutor(),{
})
.build()
)
}