开篇
在日常开发中,我们或多或少会接触到一些性能问题,比如运行缓慢,ANR等。工欲善其事必先利其器。本文介绍一下StrictMode在Android开发中的作用。
StrictMode的定义
StrictMode(严格模式),一个可以检测 工程师意外产生的错误 的开发工具,并让你通过UI改变让你注意到有错误。StrictMode一般被用来捕获在应用main thread中的意外的硬盘io或网络IO。让硬盘读写和网络操作从main thread中脱离会让程序更流畅,响应更灵敏,同时摆脱了ANR的困扰。
注意:Android设备的硬盘一般都是flash存储器,许多的设备很少并发地将文件系统运行在存储器的顶端。一般来说硬盘IO都是很快的,少部分情况下当IO运行在后台进程中会变得特别的慢。我们应该假定一般都是慢的。这样更合适。
具体能检测什么
严格模式主要检测两大问题,一个是线程策略,即TreadPolicy,另一个是VM策略,即VmPolicy。
ThreadPolicy
线程策略检测的内容有
自定义的耗时调用 使用detectCustomSlowCalls()开启
磁盘读取操作 使用detectDiskReads()开启
磁盘写入操作 使用detectDiskWrites()开启
网络操作 使用detectNetwork()开启
VmPolicy
虚拟机策略检测的内容有
Activity泄露 使用detectActivityLeaks()开启
未关闭的Closable对象泄露 使用detectLeakedClosableObjects()开启
泄露的Sqlite对象 使用detectLeakedSqlLiteObjects()开启
检测实例数量 使用setClassInstanceLimit()开启
工作原理
其实StrictMode实现原理也比较简单,以IO操作为例,主要是通过在open,read,write,close时进行监控。libcore.io.BlockGuardOs文件就是监控的地方。以open为例,如下进行监控。
@Override
public FileDescriptor open(String path, int flags, int mode)
throws ErrnoException {
BlockGuard.getThreadPolicy().onReadFromDisk();
if ((mode & O_ACCMODE) != O_RDONLY) {
BlockGuard.getThreadPolicy().onWriteToDisk();
}
return os.open(path, flags, mode);
}
其中onReadFromDisk()方法的实现,代码位于StrictMode.Java中。
public void onReadFromDisk() {
if ((mPolicyMask & DETECT_DISK_READ) == 0) {
return;
}
if (tooManyViolationsThisLoop()) {
return;
}
BlockGuard.BlockGuardPolicyException e = new StrictModeDiskReadViolation(
mPolicyMask);
e.fillInStackTrace();
startHandlingViolationException(e);
}
使用范例:
public void onCreate() {
if (DEVELOPER_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate();
}
异常捕获、查看结果
严格模式有很多种报告违例的形式,但是想要分析具体违例情况,还是需要查看日志,终端下过滤StrictMode就能得到违例的具体stacktrace信息。
adb logcat | grep StrictMode
日志的时间靠谱么
在下面的过滤日志中,我们看到下面的一个IO操作要消耗31毫秒,这是真的么
D/StrictMode( 2921): StrictMode policy violation; ~duration=31 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=31 violation=2
D/StrictMode( 2921): at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1176)
D/StrictMode( 2921): at libcore.io.BlockGuardOs.read(BlockGuardOs.java:148)
D/StrictMode( 2921): at libcore.io.IoBridge.read(IoBridge.java:422)
D/StrictMode( 2921): at java.io.FileInputStream.read(FileInputStream.java:179)
D/StrictMode( 2921): at java.io.InputStreamReader.read(InputStreamReader.java:244)
D/StrictMode( 2921): at java.io.BufferedReader.fillBuf(BufferedReader.java:130)
D/StrictMode( 2921): at java.io.BufferedReader.readLine(BufferedReader.java:354)
D/StrictMode( 2921): at com.example.strictmodedemo.MainActivity.testReadContentOfFile(MainActivity.java:65)
D/StrictMode( 2921): at com.example.strictmodedemo.MainActivity.onCreate(MainActivity.java:28)
D/StrictMode( 2921): at android.app.Activity.performCreate(Activity.java:4543)
从上面的stacktrace可以看出testReadContentOfFile方法中包含了文件读取IO操作,至于是否为31毫秒,我们可以利用秒表的原理计算一下,即在方法调用的地方如下记录
long startTime = System.currentTimeMillis();
testReadContentOfFile();
long cost = System.currentTimeMillis() - startTime;
Log.d(LOGTAG, "cost = " + cost);
得到的日志中上述操作耗时9毫秒,非31毫秒。
D/MainActivity(20996): cost = 9
---注:通常情况下StrictMode给出的耗时相对实际情况偏高,并不是真正的耗时数据---
特别需要注意的理念
当你发现一个异常的时候. 比如你通过adb logcat看到了penaltvLog()打出来的log.
你可以用: threads, Handler, AsyncTask, IntentService等尝试修复它. 特别注意别惦记着要修复所有StrictMode找到的点. 在正常活动生命周期中,许多情况下磁盘访问通常是必要的.不过利用StrictMode找到的在UI线程上的网络请求几乎都的确是一个问题。
StrictMode是不是一个安全机制并不能保证发现所有的磁盘或网络访问。
在Bindercalls时跨进程通信的状态下,它还是很有效的一种机制。
从磁盘或网络访问调用JNI不会触发。
1. http://droidyue.com/blog/2015/09/26/android-tuning-tool-strictmode/
2. http://www.bubuko.com/infodetail-1819517.html
3. http://tech.it168.com/a2011/0908/1243/000001243936_all.shtml