StrictMode类是Android 2.3 (API 9)引入的一个工具类,可以用来帮助开发者发现代码中的一些不规范的问题,以达到提升应用响应能力的目的。举个例子来说,如果开发者在UI线程中进行了网络操作或者文件系统的操作,而这些缓慢的操作会严重影响应用的响应能力,甚至出现ANR对话框。为了在开发中发现这些容易忽略的问题,我们使用StrictMode,系统检测出主线程违例的情况并做出相应的反应,最终帮助开发者优化和改善代码逻辑。
官网文档:http://developer.android.com/reference/android/os/StrictMode.html
StrictMode具体能检测什么
严苛模式主要检测两大问题,一个是线程策略,即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);
}
常见用法
严格模式的开启可以放在Application或者Activity以及其他组件的onCreate方法。为了更好地分析应用中的问题,建议放在Application的onCreate方法中。
其中,我们只需要在app的开发版本下使用 StrictMode,线上版本避免使用 StrictMode,这里定义了一个布尔值变量DEV_MODE来进行控制。
private boolean DEV_MODE = true;
public void onCreate() {
if (DEV_MODE) {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectCustomSlowCalls() //API等级11,使用StrictMode.noteSlowCode
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyDialog() //弹出违规提示对话框
.penaltyLog() //在Logcat 中打印违规异常信息
.penaltyFlashScreen() //API等级11
.build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects() //API等级11
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate();
}
其中Android3.0引入的方法包括detectCustomSlowCalls()和noteSlowCode(),它们都是用来检测应用中执行缓慢代码的或者潜在的缓慢代码。
查看报告结果
严格模式有很多种报告违例的形式,但是想要分析具体违例情况,还是需要查看日志,终端下过滤StrictMode就能得到违例的具体stacktrace信息。
adb logcat | grep StrictMode
当然也可以选择弹窗形式来简明提醒开发者
ThreadPolicy 详解
StrictMode.ThreadPolicy.Builder 主要方法如下
detectNetwork() 用于检查UI线程中是否有网络请求操作
检测UI线程中网络请求案例:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
Button btnTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectNetwork()
.penaltyLog()
.build());
btnTest = (Button) findViewById(R.id.btn_test);
btnTest.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int id = v.getId();
switch (id) {
case R.id.btn_test:
postNetwork();
break;
}
}
/**
* 网络连接的操作
*/
private void postNetwork() {
try {
URL url = new URL("http://www.wooyun.org");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.connect();
BufferedReader reader = new BufferedReader(new InputStreamReader(
conn.getInputStream()));
String lines = null;
StringBuffer sb = new StringBuffer();
while ((lines = reader.readLine()) != null) {
sb.append(lines);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行后,触发的警告如下
detectDiskReads() 和 detectDiskWrites() 是磁盘读写检查
磁盘读写检查案例:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
Button btnTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskWrites()
.detectDiskReads()
.penaltyLog()
.build());
btnTest = (Button) findViewById(R.id.btn_test);
btnTest.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int id = v.getId();
switch (id) {
case R.id.btn_test:
writeToExternalStorage();
break;
}
}
/**
* 文件系统的操作
*/
public void writeToExternalStorage() {
File externalStorage = Environment.getExternalStorageDirectory();
File mbFile = new File(externalStorage, "castiel.txt");
try {
OutputStream output = new FileOutputStream(mbFile, true);
output.write("www.wooyun.org".getBytes());
output.flush();
output.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行后,触发的警告如下
-
noteSlowCall针对执行比较耗时的检查
StrictMode从 API 11开始允许开发者自定义一些耗时调用违例,这种自定义适用于自定义的任务执行类中,比如我们有一个进行任务处理的类,为TaskExecutor。
public class TaskExecutor {
public void execute(Runnable task) {
task.run();
}
}
先需要跟踪每个任务的耗时情况,如果大于500毫秒需要提示给开发者,noteSlowCall就可以实现这个功能,如下修改代码
public class TaskExecutor {
private static long SLOW_CALL_THRESHOLD = 500;
public void executeTask(Runnable task) {
long startTime = SystemClock.uptimeMillis();
task.run();
long cost = SystemClock.uptimeMillis() - startTime;
if (cost > SLOW_CALL_THRESHOLD) {
StrictMode.noteSlowCall("slowCall cost=" + cost);
}
}
}
执行一个耗时2000毫秒的任务
TaskExecutor executor = new TaskExecutor();
executor.executeTask(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
得到的违例日志,注意其中~duration=20 ms并非耗时任务的执行时间,而我们的自定义信息msg=slowCall cost=2000才包含了真正的耗时。
penaltyDeath(),当触发违规条件时,直接Crash掉当前应用程序。
penaltyDeathOnNetwork(),当触发网络违规时,Crash掉当前应用程序。
penaltyDialog(),触发违规时,显示对违规信息对话框。
penaltyFlashScreen(),会造成屏幕闪烁,不过一般的设备可能没有这个功能。
penaltyDropBox(),将违规信息记录到 dropbox 系统日志目录中(/data/system/dropbox),你可以通过如下命令进行插件:
adb shell dumpsys dropbox dataappstrictmode --print
permitCustomSlowCalls()、permitDiskReads ()、permitDiskWrites()、permitNetwork: 如果你想关闭某一项检测,可以使用对应的permit*方法。
VMPolicy 详解
StrictMode.VmPolicy.Builder 主要方法如下
detectActivityLeaks() 用户检查 Activity 的内存泄露情况
内存泄露检查案例:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectActivityLeaks()
.penaltyLog()
.build()
);
new Thread() {
@Override
public void run() {
while (true) {
SystemClock.sleep(1000);
}
}
}.start();
}
}
我们反复旋转屏幕就会输出提示信息(重点在 instances=2; limit=1 这一行)
这时因为,我们在Activity中创建了一个Thread匿名内部类,而匿名内部类隐式持有外部类的引用。而每次旋转屏幕是,Android会新创建一个Activity,而原来的Activity实例又被我们启动的匿名内部类线程持有,所以不会释放,从日志上看,当先系统中该Activty有4个实例,而限制是只能创建1各实例。我们不断翻转屏幕,instances 的个数还会持续增加。
detectLeakedClosableObjects()用于资源没有正确关闭时提醒
// 资源引用没有关闭检查案例
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedClosableObjects()
.penaltyLog()
.build()
);
File newxmlfile = new File(Environment.getExternalStorageDirectory(), "castiel.txt");
try {
newxmlfile.createNewFile();
FileWriter fw = new FileWriter(newxmlfile);
fw.write("猴子搬来的救兵WooYun");
//fw.close(); 我们在这里特意没有关闭 fw
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行后触发警告如下
detectLeakedSqlLiteObjects() 和
detectLeakedClosableObjects()的用法类似,只不过是用来检查 SQLiteCursor 或者 其他 SQLite
对象是否被正确关闭detectLeakedRegistrationObjects() 用来检查 BroadcastReceiver 或者
ServiceConnection 注册类对象是否被正确释放setClassInstanceLimit(),设置某个类的同时处于内存中的实例上限,可以协助检查内存泄露
检测内存泄露案例
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static class CastielClass{}
private static List<CastielClass> classList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
classList = new ArrayList<CastielClass>();
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.setClassInstanceLimit(CastielClass.class, 2)
.penaltyLog()
.build());
classList.add(new CastielClass());
classList.add(new CastielClass());
classList.add(new CastielClass());
classList.add(new CastielClass());
classList.add(new CastielClass());
classList.add(new CastielClass());
classList.add(new CastielClass());
classList.add(new CastielClass());
}
}
运行后触发警告如下
其他操作
除了通过日志查看之外,我们也可以在开发者选项中开启严格模式,开启之后,如果主线程中有执行时间长的操作,屏幕则会闪烁,这是一个更加直接的方法。
注意事项
- 只在开发阶段启用StrictMode,发布应用或者release版本一定要禁用它。
- 严格模式无法监控JNI中的磁盘IO和网络请求。
- 应用中并非需要解决全部的违例情况,比如有些IO操作必须在主线程中进行。
参考链接:http://droidyue.com/blog/2015/09/26/android-tuning-tool-strictmode/
安卓开发高级技术交流QQ群:108721298 欢迎入群
微信公众号:mobilesafehome
(本公众号支持投票)
Android严苛模式StrictMode使用详解的更多相关文章
-
StrictMode使用详解
http://hb.qq.com/a/20110914/000054.htm http://www.android100.org/html/201204/25/1097.html http://www ...
-
Android Design Support Library使用详解
Android Design Support Library使用详解 Google在2015的IO大会上,给我们带来了更加详细的Material Design设计规范,同时,也给我们带来了全新的And ...
-
[Android新手区] SQLite 操作详解--SQL语法
该文章完全摘自转自:北大青鸟[Android新手区] SQLite 操作详解--SQL语法 :http://home.bdqn.cn/thread-49363-1-1.html SQLite库可以解 ...
-
Android中的windowSoftInputMode属性详解
这篇文章主要介绍了Android中的windowSoftInputMode属性详解,本文对windowSoftInputMode的9个属性做了详细总结,需要的朋友可以参考下 在前面的一篇文章中 ...
-
Android Animations 视图动画使用详解!!!
转自:http://www.open-open.com/lib/view/open1335777066015.html Android Animations 视图动画使用详解 一.动画类型 Andro ...
-
Android中Canvas绘图基础详解(附源码下载) (转)
Android中Canvas绘图基础详解(附源码下载) 原文链接 http://blog.csdn.net/iispring/article/details/49770651 AndroidCa ...
-
Android高效率编码-第三方SDK详解系列(三)——JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送
Android高效率编码-第三方SDK详解系列(三)--JPush推送牵扯出来的江湖恩怨,XMPP实现推送,自定义客户端推送 很久没有更新第三方SDK这个系列了,所以更新一下这几天工作中使用到的推送, ...
-
Android高效率编码-第三方SDK详解系列(一)——百度地图,绘制,覆盖物,导航,定位,细腻分解!
Android高效率编码-第三方SDK详解系列(一)--百度地图,绘制,覆盖物,导航,定位,细腻分解! 这是一个系列,但是我也不确定具体会更新多少期,最近很忙,主要还是效率的问题,所以一些有效的东西还 ...
-
Android项目刮刮奖详解(二)
Android项目刮刮奖详解(一) 前言 上期我们简单地实现了一个画板的功能,用户可以在上面乱写乱画,其实,刮刮奖也是如此,用户刮奖的时候也是乱写乱画的. 刮刮奖原理 一共有两层画布,底层画布存放中奖 ...
随机推荐
-
echart饼状图使用,打发时间。
新公司,刚来几天,闲着没事,领导让我做些无关痛痒的活,优化报表统计!!!之前是用flash做的,现在要改成echart实现.好吧,之前没用过,抱着学习态度,研究了下.写点东西打发下时间,能帮到需要帮助 ...
-
(转)ASP.Net 学习路线
入门篇 1.学习面向对象(OOP)的编程思想 许多高级语言都是面向对象的编程,.NET也不例外.如果您第一次接触面向对象的编程,就必须理解类.对象.字段.属性.方法和事件.封装.继承和多态性.重载.重 ...
-
INNO SETUP 获得命令行参数
原文 http://www.cnblogs.com/ahuo/archive/2009/07/30/1534998.html ); Result := CmdLine; ...
-
利用php函数mkdir递归创建层级目录
项目开发中免不了要在服务器上创建文件夹,比如上传图片时的目录,模板解析时的目录等.这不当前手下的项目就用到了这个,于是总结了几个循环创建层级目录的方法. php默认的mkdir一次只能创建一层目录,而 ...
-
Spring Cloud之——Config(配置中心)
Spring Cloud Config(配置中心) 大家好,有一段时间没有写技术博客了.由于工作上的事情,这方面很难分配时间.近几年随着服务化的兴起,一批服务化的框架应运而生,像dubbo,thrif ...
-
Django Rest framework 框架之解析器
解析器 序列化***** 请求数据进行校验 对queryset进行序列化处理 分页 路由 视图 渲染器
-
epoll测试实例
C++ Code 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 ...
-
Hibernate错误:Could not bind factory to JNDI
使用hibernate时,将hibernate.cfg.xml中 <session-factory name="SessionFactory">的那么属性去掉即可.因为 ...
-
go语言中的json
结构体类型转化为json格式 package main import ( "encoding/json" "fmt" ) //如果要转化成json格式,那么成员 ...
-
Tomcat的bin目录下startup.bat、Tomcat6.exe、Tomcat6w.exe区别
从官方下载了apache-tomcat-6.0.37-windows-x64.zip安装包,解压后bin目录下的startup.bat.Tomcat6.exe.Tomcat6w.exe 3个程序有何区 ...