}
注意:我们只需要在app的开发版本下使用 StrictMode,线上版本避免使用 StrictMode,随意需要通过 诸如 DEVELOPER_MODE 这样的配置变量来进行控制。
下面我们举几个例子来说明 StrictMode 是如何发挥作用的。
代码1:
public class ActivitySimple extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
StrictMode.setThreadPolicy(new ThreadPolicy.Builder()
.detectAll()
.penaltyDialog() //弹出违规提示对话框
.penaltyLog() //在Logcat 中打印违规异常信息
.build());
this.testNetwork();
}
private void testNetwork() {
try {
URL url = new URL(“http://www.baidu.com”);
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();
}
}
}
在这里例子中,我们在主线程(UI线程)中执行了网络请求,ThreadPolicy 策略中的 detectAll()
方法 包含而来对这类违规操作的检查,同时我们通过penaltyDialog()
和 penaltyLog()
两个方法将违规信息提示给开发者。
在运行这段代码是,我们会看到下图中的对话框提示:
在LogCat 中我们会看到这样的日志信息:
1.2 StrictMode 详解
StrictMode 通过策略方式来让你自定义需要检查哪方面的问题。 主要有两中策略,一个时线程方策略([ThreadPolicy](()),一个是VM方面的策略([VmPolicy](())。
-
ThreadPolicy 主要用于发现在UI线程中是否有读写磁盘的操作,是否有网络操作,以及检查UI线程中调用的自定义代码是否执行得比较慢。
-
VmPolicy,主要用于发现内存问题,比如 Activity内存泄露, SQL 对象内存泄露, 资源未释放,能够限定某个类的最大对象数。
1.3 ThreadPolicy 详解
[StrictMode.ThreadPolicy.Builder](() 主要方法如下:
-
detectNetwork() 用于检查UI线程中是否有网络请求操作,上面的代码的就是网络请求违规的问题。
-
detectDiskReads() 和 detectDiskReads() 是磁盘读写检查,触发时会打印出如下日志(以 detectDiskReads() 为例):
- detectCustomSlowCalls() 主要用于帮助开发者发现UI线程调用的那些方法执行得比较慢,要和
StrictMode.noteSlowCall
配合使用,StrictMode.noteSlowCall
只有通过StrictMode.noteSlowCall
用来标记“可能会”执行比较慢的方法,只有标记过的方法才能被检测到,日志中会记录方法的执行时间(比如 ~duration=2019 ms)。看下面的例子:
代码2:
public class ActivityTestDetectCustomSlowCalls extends Activity {
private TextView textView = null;
private static boolean isStrictMode = false;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activitymain); this.textView = (TextView) findViewById(R.id.textview);this.textView.setText(“In ActivityTestDetectCustomSlowCalls”); if(! isStrictMode){StrictMode.setThreadPolicy(new ThreadPolicy.Builder() .detectCustomSlowCalls() .penaltyLog() .build());isStrictMode = true; } this.slowCall1(); this.slowCall2(); }
/* * 没有标记的方法 */ private void slowCall_1(){ //用来标记潜在执行比较慢的方法 SystemClock.sleep(1000 2); }
/ * 标记过的方法 / private void slowCall_2(){ //用来标记潜在执行比较慢的方法 StrictMode.noteSlowCall("slowCall 2"); SystemClock.sleep(1000 2); } }
在logcat 中我们只能看到和方法 slowCall_2()
(因为通过StrictMode.noteSlowCall()
标记过)相关的日志:
当然你也可以在其他线程中使用 detectCustomSlowCalls(),但是没有什么实际意义,也看不到方法执行时间,比如:
public class ActivityTestDetectCustomSlowCalls extends Activity {
private TextView textView = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.textView = (TextView) findViewById(R.id.text_view);
this.textView.setText(“In ActivityTestDetectCustomSlowCalls”);
new Thread(){
public void run() {
StrictMode.setThreadPolicy(new ThreadPolicy.Builder()
.detectCustomSlowCalls()
.penaltyLog()
.build());
this.slowCallInCustomThread();
};
private void slowCallInCustomThread(){
//用来标记潜在执行比较慢的方法
StrictMode.noteSlowCall(“slowCallInCustomThread”);
SystemClock.sleep(1000 * 2);
}
}.start();;
}
}
日志输出如下:
-
penaltyDeath(),当触发违规条件时,直接Crash掉当前应用程序。
-
penaltyDeathOnNetwork(),当触发网络违规时,Crash掉当前应用程序。
-
penaltyDialog(),触发违规时,显示对违规信息对话框。
-
penaltyFlashScreen(),会造成屏幕闪烁,不过一般的设备可能没有这个功能。
-
penaltyDropBox(),将违规信息记录到 dropbox 系统日志目录中(/data/system/dropbox),你可以通过如下命令进行插件:
会得到如下的信息:
- permitCustomSlowCalls()、permitDiskReads ()、permitDiskWrites()、permitNetwork: 如果你想关闭某一项检测,可以使用对应的permit*方法。
##1.4 VMPolicy 详解
- detectActivityLeaks() 用户检查 Activity 的内存泄露情况,比如下面的代码:
public class ActivityTestActivityLeaks extends Activity {
private static boolean isStrictMode = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(! isStrictMode){
StrictMode.setVmPolicy(new VmPolicy.Builder()
.detectActivityLeaks()
.penaltyLog()
.build());
isStrictMode = true;
}
new Thread() {
@Override
public void run() {
while (true) {
SystemClock.sleep(1000);
}
}
}.start();
}
}
我们反复旋转屏幕就会输出如下信息(重点在 instances=4; limit=1 这一行):
这时因为,我们在Activity中创建了一个Thread匿名内部类,而匿名内部类隐式持有外部类的引用。而每次旋转屏幕是,Android会新创建一个Activity,而原来的Activity实例又被我们启动的匿名内部类线程持有,所以不会释放,从日志上看,当先系统中该Activty有4个实例,而限制是只能创建1各实例。我们不断翻转屏幕,instances 的个数还会持续增加。
- detectLeakedClosableObjects() 和 detectLeakedSqlLiteObjects(),资源没有正确关闭时回触发,比如下面的代码:
public class MainActivityTestDetectLeakedClosableObjects extends Activity {
private static boolean isStrictMode = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(! isStrictMode){
StrictMode.setVmPolicy(new VmPolicy.Builder()
.detectLeakedClosableObjects()
.penaltyLog()
.build());
isStrictMode = true;
}
File newxmlfile = new File(Environment.getExternalStorageDirectory(), “aaa.txt”);
try {
newxmlfile.createNewFile();
FileWriter fw = new FileWriter(newxmlfile);
fw.write(“aaaaaaaaaaa”);
//fw.close(); 我们在这里故意没有关闭 fw
} catch (IOException e) {
e.printStackTrace();
}
}
}
会产生如下异常信息:
detectLeakedSqlLiteObjects() 和 detectLeakedClosableObjects()的用法类似,只不过是用来检查 SQLiteCursor 或者 其他 SQLite 对象是否被正确关闭。
- detectLeakedRegistrationObjects() 用来检查 BroadcastReceiver 或者 ServiceConnection 注册类对象是否被正确释放,看下面的代码
public class ActivityTestLeakedRegistrationObjects extends Activity {
private TextView textView = null;
private static boolean isStrictMode = false;
private MyReceiver receiver = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.textView = (TextView) findViewById(R.id.text_view);
this.textView.setText(“In ActivityTestLeakedRegistrationObjects”);
if(! isStrictMode){
StrictMode.setVmPolicy(new VmPolicy.Builder()
.detectLeakedRegistrationObjects()
.penaltyLog()
.build());
isStrictMode = true;
}
this.receiver = new MyReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(“android.intent.action.MY_BROADCAST”);
registerReceiver(this.receiver, filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
输入信息如下:
正确做法应该是在 onDestroy() 方法中将 receiver 释放掉:
@Override
protected void onDestroy() {
unregisterReceiver(this.receiver);
super.onDestroy();
}
- setClassInstanceLimit(),设置某个类的同时处于内存中的实例上限,可以协助检查内存泄露。比如下面的代码:
public class ActivityTestObjectLimit extends Activity {
private static class MyClass{}
private static boolean isStrictMode = false;
private static List classList = new ArrayList<ActivityTestObjectLimit.MyClass>();
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); 《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》无偿开源 徽信搜索公众号【编程进阶路】
List<ActivityTestObjectLimit.MyClass>();
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); 《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》无偿开源 徽信搜索公众号【编程进阶路】