作为AndroID开发,日常的开发工作中或多或少要接触到性能问题,比如我的AndroID程序运行缓慢卡顿,并且常常出现ANR对话框等等问题。既然有性能问题,就需要进行性能优化。正所谓工欲善其事,必先利其器。一个好的工具,可以帮助我们发现并定位问题,进而有的放矢进行解决。本文主要介绍StrictMode 在AndroID 应用开发中的应用和一些问题。
什么是StrictMode
StrictMode意思为严格模式,是用来检测程序中违例情况的开发者工具。最常用的场景就是检测主线程中本地磁盘和网络读写等耗时的 *** 作。
严在哪里
既然叫做严格模式,那么又严格在哪些地方呢?
在AndroID中,主线程,也就是UI线程,除了负责处理UI相关的 *** 作外,还可以执行文件读取或者数据库读写 *** 作(从AndroID 4.0 开始,网络 *** 作禁止在主线程中执行,否则会抛出networkonmainthreadException)。使用严格模式,系统检测出主线程违例的情况会做出相应的反应,如日志打印,d出对话框亦或者崩溃等。换言之,严格模式会将应用的违例细节暴露给开发者方便优化与改善。
具体能检测什么
严格模式主要检测两大问题,一个是线程策略,即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为例,如下进行监控。
@OverrIDepublic 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);}
如何使用
关于StrictMode如何使用,最重要的就是如何启用严格模式。
放在哪里
严格模式的开启可以放在Application或者Activity以及其他组件的onCreate方法。为了更好地分析应用中的问题,建议放在Application的onCreate方法中。
简单启用
以下的代码启用全部的ThreadPolicy和VmPolicy违例检测
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信息。
adb logcat | grep StrictMode
解决违例
如果是主线程中出现文件读写违例,建议使用工作线程(必要时结合Handler)完成。 如果是对SharedPreferences写入 *** 作,在API 9 以上 建议优先调用apply而非commit。 如果是存在未关闭的Closable对象,根据对应的stacktrace进行关闭。 如果是sqlite对象泄露,根据对应的stacktrace进行释放。举个例子
以主线程中的文件写入为例,引起违例警告的代码
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(); }}
引起的警告为
D/StrictMode( 9730): StrictMode policy violation; ~duration=20 ms: androID.os.StrictMode$StrictModediskReadViolation: policy=31 violation=2D/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违例,解决方法就是讲写入 *** 作放入工作线程。
public voID writetoExternalStorage() { new Thread() { @OverrIDe public voID run() { super.run(); file externalStorage = Environment.getExternalStorageDirectory(); file destfile = new file(externalStorage,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对象未关闭的违例。改进如下
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泄露的代码。
public class LeakyActivity extends Activity{ @OverrIDe protected voID onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyApplication.sLeakyActivitIEs.add(this); }}
MyApplication中关于sLeakyActivitIEs的部分实现
public class MyApplication extends Application { public static final boolean IS_DEBUG = true; public static ArrayList<Activity> sLeakyActivitIEs = new ArrayList<Activity>();}
当我们反复进入LeakyActivity再退出,过滤StrictMode就会得到这样的日志
E/StrictMode( 2622): class com.example.strictmodedemo.LeakyActivity; instances=2; limit=1E/StrictMode( 2622): androID.os.StrictMode$InstanceCountViolation: class com.example.strictmodedemo.LeakyActivity; instances=2; limit=1E/StrictMode( 2622): at androID.os.StrictMode.setClassInstancelimit(StrictMode.java:1)
分析日志,LeakyActivity本应该是只存在一份实例,但现在出现了2个,说明LeakyActivity发生了内存泄露。
严格模式除了可以检测Activity的内存泄露之外,还能自定义检测类的实例泄露。从API 11 开始,系统提供的这个方法可以实现我们的需求。
public StrictMode.VmPolicy.Builder setClassInstancelimit (Class klass,int instancelimit)
举个栗子,比如一个浏览器中只允许存在一个SearchBox实例,我们就可以这样设置已检测SearchBox实例的泄露
StrictMode.setVmPolicy(new VmPolicy.Builder().setClassInstancelimit(SearchBox.class,1).penaltyLog().build());
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才包含了真正的耗时。
D/StrictMode(23890): StrictMode policy violation; ~duration=20 ms: androID.os.StrictMode$StrictModeCustomViolation: policy=31 violation=8 msg=slowCall cost=2000D/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.access0(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毫秒,这是真的么
D/StrictMode( 2921): StrictMode policy violation; ~duration=31 ms: androID.os.StrictMode$StrictModediskReadViolation: policy=31 violation=2D/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毫秒,我们可以利用秒表的原理计算一下,即在方法调用的地方如下记录
long startTime = System.currentTimeMillis();testReadContentOffile();long cost = System.currentTimeMillis() - startTime;Log.d(LOGTAG,"cost = " + cost);
得到的日志中上述 *** 作耗时9毫秒,非31毫秒。
D/MainActivity(20996): cost = 9
注:通常情况下StrictMode给出的耗时相对实际情况偏高,并不是真正的耗时数据。
注意
在线上环境即Release版本不建议开启严格模式。 严格模式无法监控JNI中的磁盘IO和网络请求。 应用中并非需要解决全部的违例情况,比如有些IO *** 作必须在主线程中进行。总结
以上所述是小编给大家介绍的AndroID性能调优利器StrictMode应用分析,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对编程小技巧网站的支持!
您可能感兴趣的文章:详解Android StrictMode严格模式的使用方法Android StrictMode运行流程(推荐)Android严苛模式StrictMode使用详解 总结以上是内存溢出为你收集整理的Android性能调优利器StrictMode应用分析全部内容,希望文章能够帮你解决Android性能调优利器StrictMode应用分析所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)