Android性能优化工具-严苛模式StrictMode使用指南

2,891 阅读6分钟

StrictMode简介

性能无外乎就是CPU密集型或I/O密集型两种。 StrictMode是一个开发者工具,常用于捕获在应用主线程中发生的磁盘I/O、网络访问违例等问题。 Android 2.3 (API 9)引入的一个工具类,可以用来帮助开发者发现代码中的一些不规范的问题,以达到提升应用响应能力的目的。举个例子来说,如果开发者在UI线程中进行了网络操作或者文件系统的操作,而这些缓慢的操作会严重影响应用的响应能力,甚至出现ANR对话框。为了在开发中发现这些容易忽略的问题,我们使用StrictMode,系统检测出主线程违例的情况并做出相应的反应,最终帮助开发者优化和改善代码逻辑。

   官网文档:http://developer.android.com/reference/android/os/StrictMode.html

StrictMode具体能检测什么

严苛模式主要检测两大问题:

一个是线程策略,即TreadPolicy :不应该在应用主线程中完成的工作,包括磁盘读写、网络访问等。 一个是VM策略,即VmPolicy。 内存泄露,包括Activity泄露、SQLite泄露、未正确释放的对象等。

ThreadPolicy线程策略:

  1. 自定义的耗时调用,使用detectCustomSlowCalls()开启;
  2. 磁盘读取操作,使用detectDiskReads()开启;
  3. 磁盘写入操作,使用detectDiskWrites()开启;
  4. 网络操作,使用detectNetwork()开启。

VmPolicy虚拟机策略:

  1. Activity泄漏,使用detectActivityLeaks()开启;
  2. 未关闭的Closable对象泄漏,使用detectLeakedClosableObjects()开启;
  3. 泄漏的Sqlite对象,使用detectLeakedSqlLiteObjects()开启;
  4. 检测实例数量,使用setClassInstanceLimit()开启。

工作原理

BlockGuard和CloseGuard

StrictMode针对单个线程和虚拟机的所有对象都定义了检查策略,用来发现一些违规操作,譬如:主线程中的磁盘读/写、网络访问、未关闭cursor,这些操作都能够被StrictMode检查出来。 怎么做到的呢?在做这些操作时,植入StrictMode的检查代码就可以了。有一部分植入代码是建立在BlockGuard和CloseGuard之上的,可以说,StrictMode是建立在BlockGuard和CloseGuard之上的机制。

Guard有“守卫”的意思

Block是阻塞的意思,在进行一些耗时操作时,譬如磁盘读写、网络操作,有一个守卫在监测着,它就是BlockGuard,如果这些耗时的操作导致主线程阻塞,BlockGuard就会发出通知;

Close对应到可打开的文件,在文件被打开后,也有一个守卫在监测着,它就是CloseGuard,如果没有关闭文件,则CloseGuard就会发出通知。

常见用法

为了更好地分析应用中的问题,严苛模式的开启一般放在Application的onCreate方法。我们只需要在app的开发版本下使用 StrictMode,线上版本避免使用 StrictMode,这里定义了一个布尔值变量DEV_MODE来进行控制。

class BaseApplication : Application() {
    companion object {
        val instance: BaseApplication? = null
            get() = field ?: BaseApplication()
    }
    override fun onCreate() {
        super.onCreate()
        if (BuildConfig.DEBUG) {
            StrictMode.setThreadPolicy(
                StrictMode.ThreadPolicy.Builder()
                    .detectCustomSlowCalls() //配合StrictMode.noteSlowCall使用
                    .detectDiskReads()//是否在主线程中进行磁盘读取
                    .detectDiskWrites()//是否在主线程中进行磁盘写入
                    .detectNetwork() // 是否在主线程中进行网络请求
                    .penaltyDialog() //弹出违规提示对话框
                    .penaltyLog() //在Logcat 中打印违规异常信息
                    .penaltyFlashScreen() //会造成屏幕闪烁
                    .penaltyDropBox()//将违规信息记录到 dropbox 系统日志目录中
                    .build()
            )
            StrictMode.setVmPolicy(
                StrictMode.VmPolicy.Builder()
                    .detectActivityLeaks()//Activity是否内存泄漏
                    .detectLeakedSqlLiteObjects()//数据库是否未关闭
                    .detectLeakedClosableObjects()//文件是否未关闭
                    .setClassInstanceLimit(MainActivity.class, 1) //某个类在内存中实例上限
                    .detectLeakedRegistrationObjects()//对象是否被正确关闭
                    .penaltyLog()//打印日志
                    .penaltyDeath()//直接Crash掉当前应用程序
                    .build()
            )
        }
    }
}
  • penaltyDropBox(),将违规信息记录到 dropbox 系统日志目录中(/data/system/dropbox),你可以通过如下命令进行插件: adb shell dumpsys dropbox dataappstrictmode --print

  • permitCustomSlowCalls()

  • permitDiskReads ()

  • permitDiskWrites()

  • permitNetwork() 如果你想关闭某一项检测,可以使用对应的permit*方法。

扩充StrictMode


1.用getThreadPolicy() 或getVmPolicy()获得当前策略。
2.用setThreadPolicy() or setVmPolicy()来扩充它。

StrictMode.ThreadPolicy oldThreadPolicy = StrictMode.getThreadPolicy();
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(oldThreadPolicy)
    .permitDiskWrites()  // 在原有策略的规则基础上,不监测读写磁盘
    .build());
 
StrictMode.VmPolicy oldVmPolicy = StrictMode.getVmPolicy();
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder(oldVmPolicy)
    .detectFileUriExposure()   // 在原有策略的规则基础上,监测文件URI暴露
    .build());

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

adb logcat | grep StrictMode

当然也可以选择弹窗形式来简明提醒开发者设置如下代码:penaltyDialog()

ThreadPolicy 详解

StrictMode.ThreadPolicy.Builder 主要方法如下:

detectNetwork() 用于检查UI线程中是否有网络请求操作

检测UI线程中网络请求案例:

class MainActivity : AppCompatActivity() {
    @RequiresApi(Build.VERSION_CODES.N)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        try {
            val url = URL("http://www.baidi.com")
            val conn = url.openConnection() as HttpURLConnection
            conn.connect()
            val reader = BufferedReader(
                InputStreamReader(
                    conn.inputStream
                )
            )
            var lines: String? = null
            val sb = StringBuffer()
            while (reader.readLine().also { lines = it } != null) {
                sb.append(lines)
            }
        } catch (e: java.lang.Exception) {
            e.printStackTrace()
        }
    }
}

运行后,触发的警告如下: image.png 改进后:

class MainActivity : AppCompatActivity() {
    @RequiresApi(Build.VERSION_CODES.N)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Thread {
            try {
                val url = URL("http://www.baidu.com")
                val conn = url.openConnection() as HttpURLConnection
                conn.connect()
                val reader = BufferedReader(
                    InputStreamReader(
                        conn.inputStream
                    )
                )
                var lines: String? = null
                val sb = StringBuffer()
                while (reader.readLine().also { lines = it } != null) {
                    sb.append(lines)
                }
            } catch (e: java.lang.Exception) {
                e.printStackTrace()
            }
        }
    }
}

detectDiskReads() 和 detectDiskWrites() 是磁盘读写检查

磁盘读写检查案例:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val externalStorage = Environment.getExternalStorageDirectory()
        val mbFile = File(externalStorage, "test.txt")
        try {
            val output: OutputStream = FileOutputStream(mbFile, true)
            output.write("这是一个StrictMode".toByteArray())
            output.flush()
            output.close()
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }
}

运行后,触发的警告如下: image.png

image.png

改进方式:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //子线程中进行磁盘的读写操作
        Thread {
            val externalStorage = filesDir
            val mbFile = File(externalStorage, "test.txt")
            if (!mbFile.exists()) {
                mbFile.createNewFile()
            }
            var output: OutputStream? = null;
            try {
                output = FileOutputStream(mbFile, true)
                output.write("这是一个StrictMode".toByteArray())
                output.flush()
                output.close()
            } catch (e: FileNotFoundException) {
                e.printStackTrace()
            } catch (e: IOException) {
                e.printStackTrace()
            } finally {
                output?.close();
            }
        }.start()
    }
}

detectCustomSlowCalls()针对执行比较耗时的检查

配合StrictMode.noteSlowCall使用

实现一个自定义任务类,跟踪每个任务的耗时情况,如果大于500毫秒需要提示给开发者,noteSlowCall就可以监测到该任务超时

class TaskExecutor {
    companion object {
        private const val SLOW_CALL_THRESHOLD: Long = 500
    }

    fun executeTask(task: Runnable) {
        val startTime = SystemClock.uptimeMillis()
        task.run()
        val cost = SystemClock.uptimeMillis() - startTime
        if (cost > SLOW_CALL_THRESHOLD) {
            StrictMode.noteSlowCall("slowCall cost=$cost")
        }
    }
}

执行如下代码:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val executor = TaskExecutor()
        executor.executeTask {
            try {
                Thread.sleep(2000)
            } catch (e: InterruptedException) {
                e.printStackTrace()
            }
        }
    }
}

运行后,触发的警告如下: image.png

改进方式

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val executor = TaskExecutor()
        executor.executeTask {
            try {
                Thread.sleep(40)
            } catch (e: InterruptedException) {
                e.printStackTrace()
            }
        }
    }
}

VMPolicy 详解

StrictMode.VmPolicy.Builder 主要方法如下

VMPolicy 详解 StrictMode.VmPolicy.Builder 主要方法如下:

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

Singleton单例模式 入参Context:

class Singleton private constructor(private val context: Context) {
    companion object {
        @Volatile
        private var instance: Singleton? = null
        fun getInstance(context: Context) =
            instance ?: synchronized(this) {
                instance ?: Singleton(context).also { instance = it }
            }
    }
}

内存泄漏的Activity

class SecondActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        BaseApplication.sLeakyActivities.add(this)
    }
}

当我们反复进入SecondActivity再退出,过滤StrictMode就会得到这样的日志:

image.png

改进方式:

class LeakyActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_second)
        Singleton.getInstance(BaseApplication.instance!!.baseContext);
    }
}

detectLeakedClosableObjects()用于资源没有正确关闭时提醒

// 资源引用没有关闭检查案例

class MainActivity : AppCompatActivity() {
    @RequiresApi(Build.VERSION_CODES.N)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val externalStorage = Environment.getExternalStorageDirectory()
        val mbFile = File(externalStorage, "castiel.txt")
        if (!mbFile.exists()) {
            mbFile.createNewFile()
        }
        try {
            val output: OutputStream = FileOutputStream(mbFile, true)
            output.write("xianicai".toByteArray())
            output.flush()
            //测出没有关闭文件
            //output.close()
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }
}

运行后触发警告如下

image.png 改进方式:

class MainActivity : AppCompatActivity() {
    @RequiresApi(Build.VERSION_CODES.N)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val externalStorage = Environment.getExternalStorageDirectory()
        val mbFile = File(externalStorage, "castiel.txt")
        if (!mbFile.exists()) {
            mbFile.createNewFile()
        }
        var output: FileOutputStream? = null;
        try {
            output = FileOutputStream(mbFile, true)
            output.write("xianicai".toByteArray())
            output.flush()
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        } finally {
            output?.close()
        }
    }
}

detectLeakedSqlLiteObjects()用来检查 SQLiteCursor 或者 其他 SQLite

使用方法和 detectLeakedClosableObjects()相似,测出不在举例

detectLeakedRegistrationObjects()对象是否被正确关闭

detectLeakedRegistrationObjects() 用来检查 BroadcastReceiver 或者 ServiceConnection 注册类对象是否被正确释放 setClassInstanceLimit(),设置某个类的同时处于内存中的实例上限,可以协助检查内存泄露

其他操作

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

WeChat24624e50ad48d8fa0ed677487a4b1def.png