Android性能调优利器StrictMode
2019独角兽企业重金招聘Python工程师标准>>>
作为Android开发,日常的开发工作中或多或少要接触到性能问题,比如我的Android程序运行缓慢卡顿,并且常常出现ANR对话框等等问题。既然有性能问题,就需要进行性能优化。正所谓工欲善其事,必先利其器。一个好的工具,可以帮助我们发现并定位问题,进而有的放矢进行解决。本文主要介绍StrictMode 在Android 应用开发中的应用和一些问题。
什么是StrictMode
StrictMode意思为严格模式,是用来检测程序中违例情况的开发者工具。最常用的场景就是检测主线程中本地磁盘和网络读写等耗时的操作。
严在哪里
既然叫做严格模式,那么又严格在哪些地方呢?
在Android中,主线程,也就是UI线程,除了负责处理UI相关的操作外,还可以执行文件读取或者数据库读写操作(从Android 4.0 开始,网络操作禁止在主线程中执行,否则会抛出NetworkOnMainThreadException)。使用严格模式,系统检测出主线程违例的情况会做出相应的反应,如日志打印,弹出对话框亦或者崩溃等。换言之,严格模式会将应用的违例细节暴露给开发者方便优化与改善。
具体能检测什么
严格模式主要检测两大问题,一个是线程策略,即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为例,如下进行监控。
1 2 3 4 5 6 7 8 |
@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中。
1 2 3 4 5 6 7 8 9 10 11 |
public void onReadFromDisk() { if ((mPolicyMask & DETECT_DISK_READ) == 0) { return; } if (tooManyViolationsThisLoop()) { return; } BlockGuard.BlockGuardPolicyException e = new StrictModeDiskReadViolation(mPolicyMask); e.fillInStackTrace(); startHandlingViolationException(e); } |
如何使用
关于StrictMode如何使用,最重要的就是如何启用严格模式。
放在哪里
严格模式的开启可以放在Application或者Activity以及其他组件的onCreate方法。为了更好地分析应用中的问题,建议放在Application的onCreate方法中。
简单启用
以下的代码启用全部的ThreadPolicy和VmPolicy违例检测
1 2 3 4 |
if (IS_DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().build()); StrictMode.setVmPolicy(new VmPolicy.Builder().detectAll().penaltyLog().build()); } |
严格模式需要在debug模式开启,不要在release版本中启用。
同时,严格模式自API 9 开始引入,某些API方法也从 API 11 引入。使用时应该注意 API 级别。
如有需要,也可以开启部分的严格模式。
查看结果
严格模式有很多种报告违例的形式,但是想要分析具体违例情况,还是需要查看日志,终端下过滤StrictMode就能得到违例的具体stacktrace信息。
1 |
adb logcat | grep StrictMode |
解决违例
- 如果是主线程中出现文件读写违例,建议使用工作线程(必要时结合Handler)完成。
- 如果是对SharedPreferences写入操作,在API 9 以上 建议优先调用apply而非commit。
- 如果是存在未关闭的Closable对象,根据对应的stacktrace进行关闭。
- 如果是SQLite对象泄露,根据对应的stacktrace进行释放。
举个例子
以主线程中的文件写入为例,引起违例警告的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public void writeToExternalStorage() { File externalStorage = Environment.getExternalStorageDirectory(); File destFile = new File(externalStorage, "dest.txt"); try { OutputStream output = new FileOutputStream(destFile, true); output.write("droidyue.com".getBytes()); output.flush(); output.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } |
引起的警告为
1 2 3 4 5 6 7 8 |
D/StrictMode( 9730): StrictMode policy violation; ~duration=20 ms: android.os.StrictMode$StrictModeDiskReadViolation: policy=31 violation=2 D/StrictMode( 9730): at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1176) D/StrictMode( 9730): at libcore.io.BlockGuardOs.open(BlockGuardOs.java:106) D/StrictMode( 9730): at libcore.io.IoBridge.open(IoBridge.java:390) D/StrictMode( 9730): at java.io.FileOutputStream.<init>(FileOutputStream.java:88) D/StrictMode( 9730): at com.example.strictmodedemo.MainActivity.writeToExternalStorage(MainActivity.java:56) D/StrictMode( 9730): at com.example.strictmodedemo.MainActivity.onCreate(MainActivity.java:30) D/StrictMode( 9730): at android.app.Activity.performCreate(Activity.java:4543) |
因为上述属于主线程中的IO违例,解决方法就是讲写入操作放入工作线程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public void writeToExternalStorage() { new Thread() { @Override public void run() { super.run(); File externalStorage = Environment.getExternalStorageDirectory(); File destFile = new File(externalStorage, "dest.txt"); try { OutputStream output = new FileOutputStream(destFile, true); output.write("droidyue.com".getBytes()); output.flush(); output.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }.start(); } |
然而这并非完善,因为OutputStream.write方法可能抛出IOException,导致存在OutputStream对象未关闭的情况,仍然需要改进避免出现Closable对象未关闭的违例。改进如下
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 |
public void writeToExternalStorage() { new Thread() { @Override public void run() { super.run(); File externalStorage = Environment.getExternalStorageDirectory(); File destFile = new File(externalStorage, "dest.txt"); OutputStream output = null; try { output = new FileOutputStream(destFile, true); output.write("droidyue.com".getBytes()); output.flush(); output.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (null != output) { try { output.close(); } catch (IOException e) { e.printStackTrace(); } } } } }.start(); } |
检测内存泄露
通常情况下,检测内存泄露,我们需要使用MAT对heap dump 文件进行分析,这种操作不困难,但也不容易。使用严格模式,只需要过滤日志就能发现内存泄露。
这里以Activity为例说明,首先我们需要开启对检测Activity泄露的违例检测。使用上面的detectAll或者detectActivityLeaks()均可。其次写一段能够产生Activity泄露的代码。
1 2 3 4 5 6 7 |
public class LeakyActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyApplication.sLeakyActivities.add(this); } } |
MyApplication中关于sLeakyActivities的部分实现
1 2 3 4 5 |
public class MyApplication extends Application { public static final boolean IS_DEBUG = true; public static ArrayList<Activity> sLeakyActivities = new ArrayList<Activity>(); } |
当我们反复进入LeakyActivity再退出,过滤StrictMode就会得到这样的日志
1 2 3 |
E/StrictMode( 2622): class com.example.strictmodedemo.LeakyActivity; instances=2; limit=1 E/StrictMode( 2622): android.os.StrictMode$InstanceCountViolation: class com.example.strictmodedemo.LeakyActivity; instances=2; limit=1 E/StrictMode( 2622): at android.os.StrictMode.setClassInstanceLimit(StrictMode.java:1) |
分析日志,LeakyActivity本应该是只存在一份实例,但现在出现了2个,说明LeakyActivity发生了内存泄露。
严格模式除了可以检测Activity的内存泄露之外,还能自定义检测类的实例泄露。从API 11 开始,系统提供的这个方法可以实现我们的需求。
1 |
public StrictMode.VmPolicy.Builder setClassInstanceLimit (Class klass, int instanceLimit) |
举个栗子,比如一个浏览器中只允许存在一个SearchBox实例,我们就可以这样设置已检测SearchBox实例的泄露
1 |
StrictMode.setVmPolicy(new VmPolicy.Builder().setClassInstanceLimit(SearchBox.class, 1).penaltyLog().build()); |
noteSlowCall
StrictMode从 API 11开始允许开发者自定义一些耗时调用违例,这种自定义适用于自定义的任务执行类中,比如我们有一个进行任务处理的类,为TaskExecutor。
1 2 3 4 5 |
public class TaskExecutor { public void execute(Runnable task) { task.run(); } } |
先需要跟踪每个任务的耗时情况,如果大于500毫秒需要提示给开发者,noteSlowCall就可以实现这个功能,如下修改代码
1 2 3 4 5 6 7 8 9 10 11 12 |
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毫秒的任务
1 2 3 4 5 6 7 8 9 10 11 |
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才包含了真正的耗时。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
D/StrictMode(23890): StrictMode policy violation; ~duration=20 ms: android.os.StrictMode$StrictModeCustomViolation: policy=31 violation=8 msg=slowCall cost=2000 D/StrictMode(23890): at android.os.StrictMode$AndroidBlockGuardPolicy.onCustomSlowCall(StrictMode.java:1163) D/StrictMode(23890): at android.os.StrictMode.noteSlowCall(StrictMode.java:1974) D/StrictMode(23890): at com.example.strictmodedemo.TaskExecutor.executeTask(TaskExecutor.java:17) D/StrictMode(23890): at com.example.strictmodedemo.MainActivity.onCreate(MainActivity.java:36) D/StrictMode(23890): at android.app.Activity.performCreate(Activity.java:4543) D/StrictMode(23890): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1071) D/StrictMode(23890): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2158) D/StrictMode(23890): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2237) D/StrictMode(23890): at android.app.ActivityThread.access$600(ActivityThread.java:139) D/StrictMode(23890): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1262) D/StrictMode(23890): at android.os.Handler.dispatchMessage(Handler.java:99) D/StrictMode(23890): at android.os.Looper.loop(Looper.java:156) D/StrictMode(23890): at android.app.ActivityThread.main(ActivityThread.java:5005) D/StrictMode(23890): at java.lang.reflect.Method.invokeNative(Native Method) D/StrictMode(23890): at java.lang.reflect.Method.invoke(Method.java:511) D/StrictMode(23890): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784) D/StrictMode(23890): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551) D/StrictMode(23890): at dalvik.system.NativeStart.main(Native Method) |
其他技巧
除了通过日志查看之外,我们也可以在开发者选项中开启严格模式,开启之后,如果主线程中有执行时间长的操作,屏幕则会闪烁,这是一个更加直接的方法。
问题来了
日志的时间靠谱么
在下面的过滤日志中,我们看到下面的一个IO操作要消耗31毫秒,这是真的么
1 2 3 4 5 6 7 8 9 10 11 |
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毫秒,我们可以利用秒表的原理计算一下,即在方法调用的地方如下记录
1 2 3 4 |
long startTime = System.currentTimeMillis(); testReadContentOfFile(); long cost = System.currentTimeMillis() - startTime; Log.d(LOGTAG, "cost = " + cost); |
得到的日志中上述操作耗时9毫秒,非31毫秒。
1 |
D/MainActivity(20996): cost = 9 |
注:通常情况下StrictMode给出的耗时相对实际情况偏高,并不是真正的耗时数据。
注意
- 在线上环境即Release版本不建议开启严格模式。
- 严格模式无法监控JNI中的磁盘IO和网络请求。
- 应用中并非需要解决全部的违例情况,比如有些IO操作必须在主线程中进行。
转载于:https://my.oschina.net/u/3026396/blog/831710
Android性能调优利器StrictMode相关推荐
- Android性能调优篇之探索垃圾回收机制
开篇废话 如果我们想要进行内存优化的工作,还是需要了解一下,但这一块的知识属于纯理论的,有可能看起来会有点枯燥,我尽量把这一篇的内容按照一定的逻辑来走一遍.首先,我们为什么要学习垃圾回收的机制,我大概 ...
- android性能调优的工具,神兵利器-Android 性能调优工具 Hugo
在进行Android性能调优.减少应用卡顿时,寻找可优化的code是一个必要的过程.如何发现应用中的耗时任务甚至是耗时函数呢,如果可以在log中打印每个方法的执行时间,甚至把执行方法时的输入输出同时打 ...
- 程序员精进之路:性能调优利器--火焰图
作者:厉辉,腾讯 CSIG 后台开发工程师 本文主要分享火焰图使用技巧,介绍 systemtap 的原理机制,如何使用火焰图快速定位性能问题原因,同时加深对 systemtap 的理解. 让我们回想一 ...
- 参数调优为什么要采样_程序员精进之路:性能调优利器--火焰图
本文主要分享火焰图使用技巧,介绍 systemtap 的原理机制,如何使用火焰图快速定位性能问题原因,同时加深对 systemtap 的理解. 让我们回想一下,曾经作为编程新手的我们是如何调优程序的? ...
- 编程新手该如何调优程序?程序员必备性能调优利器——火焰图
本文主要分享火焰图使用技巧,介绍 systemtap 的原理机制,如何使用火焰图快速定位性能问题原因,同时加深对 systemtap 的理解. 让我们回想一下,曾经作为编程新手的我们是如何调优程序的? ...
- Android性能调优实例
本文主要分享自己在appstore项目中的性能调优点,包括 同步改异步.缓存.Layout优化.数据库优化.算法优化.延迟执行等. 一.性能瓶颈点 整个页面主要由6个Page的ViewPager,每个 ...
- StrictMode ——Android性能调优的利器
性能无外乎就是CPU密集型或I/O密集型两种. StrictMode是一个开发者工具,常用于捕获在应用主线程中发生的磁盘I/O.网络访问违例等问题. StrictMode具体能检测什么呢 Strict ...
- Android性能调优--StrictMode
转载自:https://blog.csdn.net/weixin_40763897/article/details/89018306 性能无外乎就是CPU密集型或I/O密集型两种. StrictMod ...
- Android性能调优篇之探索JVM内存分配
开篇废话 今天我们一起来学习JVM的内存分配,主要目的是为我们Android内存优化打下基础. 一直在想以什么样的方式来呈现这个知识点才能让我们易于理解,最终决定使用方法为:图解+源代码分析. 欢迎访 ...
最新文章
- Spark2.1.0之内置RPC框架
- Mac终端(Terminal)自定义颜色,字体,背景
- python opencv imshow()显示窗口中文字符出现乱码 解决方案:将utf-8转换为gbk(未能完全解决)
- SAP CRM WebClient UI,点击Master Data工作中心后执行的JavaScript代码
- cookie和session(1)
- Pro ASP.NET 4 CMS
- rpc协议微服务器,RPC协议及实现方式(分布式微服务治理的核心)
- php上js实现ajax请求,原生JS如何实现Ajax通过POST方式与PHP进行交互的方法
- Python 第三方扩展库
- java jquery怎么取值_jquery 取值
- Ubuntu18.04安装和卸载teamviewer
- bp神经网络算法原理 Levenberg Marquardt
- Sobel边缘检测算子OpenCV实现
- 用excel制作项目管理甘特图
- 基于auto.js的安卓抢购软件---淘宝抢购小助手(不完善,仅供参考)
- SAP系统中的几种系统级消息(用户登录后消息弹窗SM02,TH_POPUP,以及系统邮件)
- SVN报错:Cannot checkout from svn: svn: E155000: 'F:\SVN-Flx\。。。。' is alrea
- SQL Server使用SUM(求和)函数
- 如何利用python解方程_如何用python解方程
- 怎么将heic转为jpg格式,哪个图片转换器好用
热门文章
- Linux云服务器安装Redis并设置远程连接设置开机自启
- QT: 使用qtchooser修改ubuntu默认的qmake版本
- CNN结构:SPP-Net为CNNs添加空间尺度卷积-神经元层
- 专访杨开振:程序员除了敲代码还能做什么?
- [Spark][Python]groupByKey例子
- 《计算机科学与工程导论:基于IoT和机器人的可视化编程实践方法第2版》一1.2.2 团队组建...
- 按钮随复选框选中与取消变换样式
- 【乐畅】工作积累 ---- 调节音量大小 (滑动条调节音量大小并保存起来 )
- 321. Create Maximum Number 解题方法详解
- display:inline-block