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线程策略:
- 自定义的耗时调用,使用detectCustomSlowCalls()开启;
- 磁盘读取操作,使用detectDiskReads()开启;
- 磁盘写入操作,使用detectDiskWrites()开启;
- 网络操作,使用detectNetwork()开启。
VmPolicy虚拟机策略:
- Activity泄漏,使用detectActivityLeaks()开启;
- 未关闭的Closable对象泄漏,使用detectLeakedClosableObjects()开启;
- 泄漏的Sqlite对象,使用detectLeakedSqlLiteObjects()开启;
- 检测实例数量,使用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()
}
}
}
运行后,触发的警告如下:
改进后:
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()
}
}
}
运行后,触发的警告如下:
改进方式:
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()
}
}
}
}
运行后,触发的警告如下:
改进方式
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就会得到这样的日志:
改进方式:
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()
}
}
}
运行后触发警告如下
改进方式:
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(),设置某个类的同时处于内存中的实例上限,可以协助检查内存泄露
其他操作
除了通过日志查看之外,我们也可以在开发者选项中开启严格模式,开启之后,如果主线程中有执行时间长的操作,屏幕则会闪烁,这是一个更加直接的方法。