使用StrictMode-StrictMode原理(1)

1. strictmode入门使用:

strictmode是android提供的一种调试环境的动态检测机制,主要的作用有两类:

1.1 线程策略

  • 检测主线程读写卡顿

    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;
}

1.2 虚拟机VM策略

  • 检测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;
}

1.3 入门使用


    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)
        }
    }

1.3.1 Thread检测操作

  • 监控写操作
        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我们确实需要经常性的在主线程进行,为避免过多的日志对开发造成干扰,推荐使用时可以屏蔽此选项

1.3.2 VM检测操作

  • 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)

1.4 设置监听

如果你想获得这些泄漏的信息,你可以自己实现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()
            )
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值