StrictMode最常用来捕捉应用程序的主线程,报告与线程及虚拟机相关的策略违例。一旦检测到策略违例(policy violation),开发者将获得输出警告:包含了一个栈trace显示你的应用在何处发生违例。除了主线程,我们还可以在IntentService、AsyncQueryHandler、IntentService、AsyncTask、Handler、等API中使用StrictMode。  举个例子来说,如果开发者在UI线程中进行了网络操作或者文件系统的操作,而这些缓慢的操作会严重影响应用的响应,就会被捕捉报告。

StrictMode具体能检测什么

严苛模式主要检测两大问题,一个是线程策略,即TreadPolicy,另一个是VM策略,即VmPolicy。

检查策略

StrictMode的线程策略主要用于检测磁盘IO和网络访问,而虚拟机策略主要用于检测内存泄漏现象。Android已经在磁盘IO访问和网络访问的代码中加入了StrictMode。当监视的线程发生策略的违例时,就可以获得警告,例如写入LogCat,显示一个对话框,闪下屏幕,写入DropBox日志文件,或让应用崩溃。最通常的做法是写入LogCat或让应用崩溃。

ThreadPolicy线程策略:

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

VmPolicy虚拟机策略:

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

使用方法

如果不指定检测函数,也可以用detectAll()来替代。penaltyLog()表示将警告输出到LogCat,你也可以使用其他或增加新的惩罚(penalty)函数,例如使用penaltyDeath()的话,一旦StrictMode消息被写到LogCat后应用就会崩溃。具体支持的监视方法见:

https://developer.android.com/reference/android/os/StrictMode.ThreadPolicy.Builder.html与https://developer.android.com/reference/android/os/StrictMode.VmPolicy.Builder.html

在正式版本中,我们并不希望使用StrictMode来让用户的应用因为一个警告而崩溃,所以在应用正式发布时,需要移出这些监视。你可以通过删除代码来实现,不过这里提供一个更好的方式来解决这个问题,即使用AndroidMainifest文件中的debuggable属性来实现,代码如下所示:

android:debuggable="true"

在代码中,使用方法如下所示:

// Return if this application is not in debug mode 
ApplicationInfo appInfo = context.getApplicationInfo(); 
int appFlags = appInfo.flags; 

if ((appFlags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {     
    // Do StrictMode setup here     
    StrictMode.setVmPolicy(
        new StrictMode.VmPolicy.Builder()         
        .detectLeakedSqlLiteObjects()         
        .penaltyLog()         
        .penaltyDeath()         
        .build()); 
}

严格模式的开启可以放在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();
 }

查看报告结果

严格模式有很多种报告违例的形式,但是想要分析具体违例情况,还是需要查看日志,我们在此介绍两种方式,一种是在android studio IDE的logcat里查看:

另一种是在终端下,过滤StrictMode得到违例的具体stacktrace信息(手机要打开调试用的app),然后打开命令终端,使用adb命令来查看:

~$ adb logcat | grep StrictMode

如果发现有违例的行为,可以通过使用线程、AsyncTask、Handler、IntentService等帮助解决。比如以下一些常用的措施:

  • 假如在主线程中进行文件读写出现了违例,可用工作线程(另外开辟子线程)来解决,必要时还可以结合Handler一起来解决。
  • SharedPreferences的写入操作,在API 9以上应该优先使用apply而非commit。
  • 如果是存在未关闭的Closable对象(如有些流OutputStream,在出现异常时,未来得及关闭),根据对应的stacktrace进行关闭。
  • 如果是SQLite对象泄漏,根据对应的stacktrace进行释放。

检测主线程

接下来我们来举个在主线程中的文件写入,引起违例警告的例子:

1.首先Activity的onCreate方法中加上检测代码:

注:以下的代码启用全部的ThreadPolicy和VmPolicy违例检测

StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());

2.这是引起违例的代码:

    public void writeToExternalStorageInMainThread() {
        File externalStorage = Environment.getExternalStorageDirectory();
        File destFile = new File(externalStorage, "hello.txt");
        try {
            OutputStream output = new FileOutputStream(destFile, true);
            output.write("I am testing io".getBytes());
            output.flush();
            output.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

3.运行app,观察logcat的输出,下面是部分截图:

logcat已告诉我们出现了违例和出现的位置。

4.解决这个违例

修改一下writeToExternalStorageInMainThread方法,将引起违例的代码都放在一个工作线程中去执行,如下所示:

    public void writeToExternalStorageInMainThread() {

        new Thread(new Runnable() {
            @Override
            public void run() {
                File externalStorage = Environment.getExternalStorageDirectory();
                File destFile = new File(externalStorage, "hello.txt");
                OutputStream output = null;
                try {
                    output = new FileOutputStream(destFile, true);
                    output.write("I am testing io".getBytes());
                    output.flush();
                    output.close();
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }finally {
                    if(output != null){
                        try {
                            output.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }).start();

    }

检测内存泄漏

通常情况下,检测内存泄漏,我们会使用MAT(Eclipse Memory Analyzer)工具对heap dump 文件进行分析。但是使用StrictMode,只需要过滤日志就能发现内存泄漏,更快捷方便。

1.首先,需要开启对检测Activity泄漏的违例检测,可以使用detectAll或者detectActivityLeaks():

StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectActivityLeaks().penaltyLog().build());

2.写一段能够产生Activity泄漏的代码

public class LeakActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak);
        if(MyApplication.IS_DEBUG){
            MyApplication.sLeakyActivities.add(this);
        }
    }
}

3.MyApplication中关于sLeakyActivities的部分实现

public class MyApplication extends Application {


    public static final boolean IS_DEBUG = true;
    public static ArrayList<Activity> sLeakyActivities = new ArrayList<Activity>();

    @Override
    public void onCreate() {
        StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectActivityLeaks().penaltyLog().build());
        super.onCreate();
    }
}

4.引发内存泄漏的操作:

通过不断从MainActivity打开LeakActivity,再返回,再打开,如此反复操作,引发内存泄漏,下面是MainActivity的代码:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView textView = (TextView)findViewById(R.id.tv);
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(MainActivity.this,LeakActivity.class));
            }
        });
    }
}

5.当我们反复进入LeakyActivity再退出,在Logcat中过滤StrictMode就会得到这样的日志:

2019-04-04 19:49:43.502 32708-32708/com.wong.appmemoryleakydemo E/StrictMode: class com.wong.appmemoryleakydemo.LeakActivity; instances=7; limit=1
   android.os.StrictMode$InstanceCountViolation: class com.wong.appmemoryleakydemo.LeakActivity; instances=7; limit=1
       at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1)

分析日志:LeakyActivity本应该只存在一个实例的,但现在存在了7个,说明LeakyActivity发生了内存泄漏。

除此之外,还可以自定义检测类型。具体网上资料较多,也就不多分析(copy)了。

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐