StrictMode ——Android性能调优的利器

举报
yd_221104950 发表于 2020/12/04 00:47:57 2020/12/04
【摘要】 性能无外乎就是CPU密集型或I/O密集型两种。 StrictMode是一个开发者工具,常用于捕获在应用主线程中发生的磁盘I/O、网络访问违例等问题。 StrictMode具体能检测什么呢 StrictMode主要检测两大问题:线程策略(TreadPolicy)和VM策略(VmPolicy)。 ThreadPolicy线程策略: 自定义的耗时调用,使用detec...

性能无外乎就是CPU密集型或I/O密集型两种。

StrictMode是一个开发者工具,常用于捕获在应用主线程中发生的磁盘I/O、网络访问违例等问题。

StrictMode具体能检测什么呢

StrictMode主要检测两大问题:线程策略(TreadPolicy)和VM策略(VmPolicy)。

ThreadPolicy线程策略:

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

VmPolicy虚拟机策略:

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

如何使用呢?

可以在应用的Application、Activity或者其他应用组件的onCreate方法中加入检测代码,如:

  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();
  }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

如果观测结果呢?

StrictMode有多种报告违例的形式,但要分析具体违例情况,还是需要查看日志。我们在此介绍两种方式,一种是在android studio IDE的logcat里查看:
在这里插入图片描述
另一种是在终端下,过滤StrictMode得到违例的具体stacktrace信息(手机要打开调试用的app),然后打开命令终端,使用adb命令来查看:

~$ adb logcat | grep StrictMode

  
 
  • 1

在这里插入图片描述

如果发现有违例的行为,可以通过使用线程(threads)、Handler、AsyncTask、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());


  
 
  • 1
  • 2
  • 3
  • 4

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(); } }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

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(); }

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

demo示例

检测内存泄漏

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

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

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

  
 
  • 1

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); } }
}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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(); }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

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)); } }); }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

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)

  
 
  • 1
  • 2
  • 3

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

检测内存泄漏demo

自定义检测类的实例泄漏

我们还可以通过StrictMode自定义检测类的实例泄漏。从API 11 开始,系统提供的这个方法setClassInstanceLimit可以实现我们的需求。比如说有个类叫SingleAction.class,我们认为在运行时,它应该只有一个实例,如果多一个,我们就可以认为发生了内存泄漏:
1.开启违例检测,如下:

StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().setClassInstanceLimit(SingleAction.class,1).penaltyLog().build());

  
 
  • 1

上面代码就是说,当SingleAction类出现多于一个实例时,就报告内存泄漏。

耗时调用违例:noteSlowCall

StrictMode从API 11开始允许开发者自定义一些耗时调用违例,这种自定义适用于自定义的任务执行类中,比如自定义任务处理类MyTaskExecutor:

public class MyTaskExecutor { public void execute(Runnable task){ task.run(); }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5

但是如果我们想跟踪每个任务执行的耗时情况,如果耗时大于500毫秒就通知我们,我们该怎么办呢?StrictMode的noteSlowCall方法可以实现这个功能,修改MyTaskExecutor,如下所示:

public class MyTaskExecutor { public static long CAN_BEAR_TIME = 500; public void execute(Runnable task){ long sTime = SystemClock.uptimeMillis(); task.run(); long cTime = SystemClock.uptimeMillis() - sTime; if(cTime > CAN_BEAR_TIME){ StrictMode.noteSlowCall("slow call cost:"+cTime); } }


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

执行一个耗时1000毫秒的任务,测试一下:

package com.wong.timeconsumingviolation;

import android.os.StrictMode;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build()); super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); MyTaskExecutor taskExecutor = new MyTaskExecutor(); taskExecutor.execute(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); }
}


  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

在Logcat中过滤StrictMode就会得到这样的日志:

2019-04-04 20:27:27.020 6147-6147/com.wong.timeconsumingviolation D/StrictMode: StrictMode policy violation; ~duration=29 ms: android.os.StrictMode$StrictModeCustomViolation: policy=65599 violation=8 msg=slow call cost:1001 at android.os.StrictMode$AndroidBlockGuardPolicy.onCustomSlowCall(StrictMode.java:1397) at android.os.StrictMode.noteSlowCall(StrictMode.java:2340) at com.wong.timeconsumingviolation.MyTaskExecutor.execute(MyTaskExecutor.java:19) at com.wong.timeconsumingviolation.MainActivity.onCreate(MainActivity.java:17) at android.app.Activity.performCreate(Activity.java:7040) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1214) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2809) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2931) at android.app.ActivityThread.-wrap11(Unknown Source:0) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1620) at android.os.Handler.dispatchMessage(Handler.java:105) at android.os.Looper.loop(Looper.java:173) at android.app.ActivityThread.main(ActivityThread.java:6698) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:782)

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

从日志分析来看,我们已成功跟踪到耗时超过500ms的任务了。
注意上面的日志结果中的:duration=29 ms,并非耗时任务的执行时间,而我们自定义信息msg=slow call cost:1001包包含的时间才是真正的耗时。

自定义耗时违例demo

注意

  1. StrictMode无法监控JNI中的磁盘IO和网络请求。
  2. 应用中并非需要解决全部的违例情况,比如有些IO操作必须在主线程中进行。
  3. 通常情况下StrictMode给出的耗时相对实际情况偏高,并不是真正的耗时数据。

文章来源: blog.csdn.net,作者:WongKyunban,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/weixin_40763897/article/details/89018306

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。