Android性能調(diào)優(yōu)利器StrictMode應(yīng)用分析
作為Android開(kāi)發(fā),日常的開(kāi)發(fā)工作中或多或少要接觸到性能問(wèn)題,比如我的Android程序運(yùn)行緩慢卡頓,并且常常出現(xiàn)ANR對(duì)話框等等問(wèn)題。既然有性能問(wèn)題,就需要進(jìn)行性能優(yōu)化。正所謂工欲善其事,必先利其器。一個(gè)好的工具,可以幫助我們發(fā)現(xiàn)并定位問(wèn)題,進(jìn)而有的放矢進(jìn)行解決。本文主要介紹StrictMode 在Android 應(yīng)用開(kāi)發(fā)中的應(yīng)用和一些問(wèn)題。
什么是StrictMode
StrictMode意思為嚴(yán)格模式,是用來(lái)檢測(cè)程序中違例情況的開(kāi)發(fā)者工具。最常用的場(chǎng)景就是檢測(cè)主線程中本地磁盤(pán)和網(wǎng)絡(luò)讀寫(xiě)等耗時(shí)的操作。
嚴(yán)在哪里
既然叫做嚴(yán)格模式,那么又嚴(yán)格在哪些地方呢?
在Android中,主線程,也就是UI線程,除了負(fù)責(zé)處理UI相關(guān)的操作外,還可以執(zhí)行文件讀取或者數(shù)據(jù)庫(kù)讀寫(xiě)操作(從Android 4.0 開(kāi)始,網(wǎng)絡(luò)操作禁止在主線程中執(zhí)行,否則會(huì)拋出NetworkOnMainThreadException)。使用嚴(yán)格模式,系統(tǒng)檢測(cè)出主線程違例的情況會(huì)做出相應(yīng)的反應(yīng),如日志打印,彈出對(duì)話框亦或者崩潰等。換言之,嚴(yán)格模式會(huì)將應(yīng)用的違例細(xì)節(jié)暴露給開(kāi)發(fā)者方便優(yōu)化與改善。
具體能檢測(cè)什么
嚴(yán)格模式主要檢測(cè)兩大問(wèn)題,一個(gè)是線程策略,即TreadPolicy,另一個(gè)是VM策略,即VmPolicy。
ThreadPolicy
線程策略檢測(cè)的內(nèi)容有
- 自定義的耗時(shí)調(diào)用 使用detectCustomSlowCalls()開(kāi)啟
- 磁盤(pán)讀取操作 使用detectDiskReads()開(kāi)啟
- 磁盤(pán)寫(xiě)入操作 使用detectDiskWrites()開(kāi)啟
- 網(wǎng)絡(luò)操作 使用detectNetwork()開(kāi)啟
VmPolicy
虛擬機(jī)策略檢測(cè)的內(nèi)容有
Activity泄露 使用detectActivityLeaks()開(kāi)啟
未關(guān)閉的Closable對(duì)象泄露 使用detectLeakedClosableObjects()開(kāi)啟
泄露的Sqlite對(duì)象 使用detectLeakedSqlLiteObjects()開(kāi)啟
檢測(cè)實(shí)例數(shù)量 使用setClassInstanceLimit()開(kāi)啟
工作原理
其實(shí)StrictMode實(shí)現(xiàn)原理也比較簡(jiǎn)單,以IO操作為例,主要是通過(guò)在open,read,write,close時(shí)進(jìn)行監(jiān)控。libcore.io.BlockGuardOs文件就是監(jiān)控的地方。以open為例,如下進(jìn)行監(jiān)控。
@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()方法的實(shí)現(xiàn),代碼位于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); }
如何使用
關(guān)于StrictMode如何使用,最重要的就是如何啟用嚴(yán)格模式。
放在哪里
嚴(yán)格模式的開(kāi)啟可以放在Application或者Activity以及其他組件的onCreate方法。為了更好地分析應(yīng)用中的問(wèn)題,建議放在Application的onCreate方法中。
簡(jiǎn)單啟用
以下的代碼啟用全部的ThreadPolicy和VmPolicy違例檢測(cè)
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()); }
嚴(yán)格模式需要在debug模式開(kāi)啟,不要在release版本中啟用。
同時(shí),嚴(yán)格模式自API 9 開(kāi)始引入,某些API方法也從 API 11 引入。使用時(shí)應(yīng)該注意 API 級(jí)別。
如有需要,也可以開(kāi)啟部分的嚴(yán)格模式。
查看結(jié)果
嚴(yán)格模式有很多種報(bào)告違例的形式,但是想要分析具體違例情況,還是需要查看日志,終端下過(guò)濾StrictMode就能得到違例的具體stacktrace信息。
adb logcat | grep StrictMode
解決違例
- 如果是主線程中出現(xiàn)文件讀寫(xiě)違例,建議使用工作線程(必要時(shí)結(jié)合Handler)完成。
- 如果是對(duì)SharedPreferences寫(xiě)入操作,在API 9 以上 建議優(yōu)先調(diào)用apply而非commit。
- 如果是存在未關(guān)閉的Closable對(duì)象,根據(jù)對(duì)應(yīng)的stacktrace進(jìn)行關(guān)閉。
- 如果是SQLite對(duì)象泄露,根據(jù)對(duì)應(yīng)的stacktrace進(jìn)行釋放。
舉個(gè)例子
以主線程中的文件寫(xiě)入為例,引起違例警告的代碼
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=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)
因?yàn)樯鲜鰧儆谥骶€程中的IO違例,解決方法就是講寫(xiě)入操作放入工作線程。
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(); }
然而這并非完善,因?yàn)镺utputStream.write方法可能拋出IOException,導(dǎo)致存在OutputStream對(duì)象未關(guān)閉的情況,仍然需要改進(jìn)避免出現(xiàn)Closable對(duì)象未關(guān)閉的違例。改進(jìn)如下
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(); }
檢測(cè)內(nèi)存泄露
通常情況下,檢測(cè)內(nèi)存泄露,我們需要使用MAT對(duì)heap dump 文件進(jìn)行分析,這種操作不困難,但也不容易。使用嚴(yán)格模式,只需要過(guò)濾日志就能發(fā)現(xiàn)內(nèi)存泄露。
這里以Activity為例說(shuō)明,首先我們需要開(kāi)啟對(duì)檢測(cè)Activity泄露的違例檢測(cè)。使用上面的detectAll或者detectActivityLeaks()均可。其次寫(xiě)一段能夠產(chǎn)生Activity泄露的代碼。
public class LeakyActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); MyApplication.sLeakyActivities.add(this); } }
MyApplication中關(guān)于sLeakyActivities的部分實(shí)現(xiàn)
public class MyApplication extends Application { public static final boolean IS_DEBUG = true; public static ArrayList<Activity> sLeakyActivities = new ArrayList<Activity>(); }
當(dāng)我們反復(fù)進(jìn)入LeakyActivity再退出,過(guò)濾StrictMode就會(huì)得到這樣的日志
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本應(yīng)該是只存在一份實(shí)例,但現(xiàn)在出現(xiàn)了2個(gè),說(shuō)明LeakyActivity發(fā)生了內(nèi)存泄露。
嚴(yán)格模式除了可以檢測(cè)Activity的內(nèi)存泄露之外,還能自定義檢測(cè)類(lèi)的實(shí)例泄露。從API 11 開(kāi)始,系統(tǒng)提供的這個(gè)方法可以實(shí)現(xiàn)我們的需求。
public StrictMode.VmPolicy.Builder setClassInstanceLimit (Class klass, int instanceLimit)
舉個(gè)栗子,比如一個(gè)瀏覽器中只允許存在一個(gè)SearchBox實(shí)例,我們就可以這樣設(shè)置已檢測(cè)SearchBox實(shí)例的泄露
StrictMode.setVmPolicy(new VmPolicy.Builder().setClassInstanceLimit(SearchBox.class, 1).penaltyLog().build());
noteSlowCall
StrictMode從 API 11開(kāi)始允許開(kāi)發(fā)者自定義一些耗時(shí)調(diào)用違例,這種自定義適用于自定義的任務(wù)執(zhí)行類(lèi)中,比如我們有一個(gè)進(jìn)行任務(wù)處理的類(lèi),為T(mén)askExecutor。
public class TaskExecutor { public void execute(Runnable task) { task.run(); } }
先需要跟蹤每個(gè)任務(wù)的耗時(shí)情況,如果大于500毫秒需要提示給開(kāi)發(fā)者,noteSlowCall就可以實(shí)現(xiàn)這個(gè)功能,如下修改代碼
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); } } }
執(zhí)行一個(gè)耗時(shí)2000毫秒的任務(wù)
TaskExecutor executor = new TaskExecutor(); executor.executeTask(new Runnable() { @Override public void run() { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } });
得到的違例日志,注意其中~duration=20 ms并非耗時(shí)任務(wù)的執(zhí)行時(shí)間,而我們的自定義信息msg=slowCall cost=2000才包含了真正的耗時(shí)。
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)
其他技巧
除了通過(guò)日志查看之外,我們也可以在開(kāi)發(fā)者選項(xiàng)中開(kāi)啟嚴(yán)格模式,開(kāi)啟之后,如果主線程中有執(zhí)行時(shí)間長(zhǎng)的操作,屏幕則會(huì)閃爍,這是一個(gè)更加直接的方法。
問(wèn)題來(lái)了
日志的時(shí)間靠譜么
在下面的過(guò)濾日志中,我們看到下面的一個(gè)IO操作要消耗31毫秒,這是真的么
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毫秒,我們可以利用秒表的原理計(jì)算一下,即在方法調(diào)用的地方如下記錄
long startTime = System.currentTimeMillis(); testReadContentOfFile(); long cost = System.currentTimeMillis() - startTime; Log.d(LOGTAG, "cost = " + cost);
得到的日志中上述操作耗時(shí)9毫秒,非31毫秒。
D/MainActivity(20996): cost = 9
注:通常情況下StrictMode給出的耗時(shí)相對(duì)實(shí)際情況偏高,并不是真正的耗時(shí)數(shù)據(jù)。
注意
- 在線上環(huán)境即Release版本不建議開(kāi)啟嚴(yán)格模式。
- 嚴(yán)格模式無(wú)法監(jiān)控JNI中的磁盤(pán)IO和網(wǎng)絡(luò)請(qǐng)求。
- 應(yīng)用中并非需要解決全部的違例情況,比如有些IO操作必須在主線程中進(jìn)行。
總結(jié)
以上所述是小編給大家介紹的Android性能調(diào)優(yōu)利器StrictMode應(yīng)用分析,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- Android性能優(yōu)化以及數(shù)據(jù)優(yōu)化方法
- 簡(jiǎn)單了解Android性能優(yōu)化方向及相關(guān)工具
- Android性能之冷啟動(dòng)優(yōu)化詳析
- Android性能測(cè)試關(guān)注的指標(biāo)整理
- Android高性能日志寫(xiě)入方案的實(shí)現(xiàn)
- Android圖片性能優(yōu)化詳解
- Android端TCP長(zhǎng)連接的性能優(yōu)化教程分享
- Android APP性能優(yōu)化分析
- 淺談android性能優(yōu)化之啟動(dòng)過(guò)程(冷啟動(dòng)和熱啟動(dòng))
- 獲取Android界面性能數(shù)據(jù)的快捷方法
相關(guān)文章
Android使用TabLayou+fragment+viewpager實(shí)現(xiàn)滑動(dòng)切換頁(yè)面效果
這篇文章主要介紹了Android使用TabLayou+fragment+viewpager實(shí)現(xiàn)滑動(dòng)切換頁(yè)面效果,需要的朋友可以參考下2017-05-05Android開(kāi)發(fā)之基于DialogFragment創(chuàng)建對(duì)話框的方法示例
這篇文章主要介紹了Android開(kāi)發(fā)之基于DialogFragment創(chuàng)建對(duì)話框的方法,結(jié)合實(shí)例形式分析了DialogFragment創(chuàng)建對(duì)話框的具體功能與布局相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-08-08Android實(shí)現(xiàn)WebView刪除緩存的方法
這篇文章主要介紹了Android實(shí)現(xiàn)WebView刪除緩存的方法,實(shí)例分析了Android針對(duì)WebView操作緩存的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-07-07Android實(shí)現(xiàn)高德地圖首頁(yè)效果(下)
這篇文章主要為大家詳細(xì)介紹了基于Android實(shí)現(xiàn)高德地圖首頁(yè)效果下篇,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2023-08-08詳解Android項(xiàng)目多服務(wù)端接口適配(超簡(jiǎn)單)
這篇文章主要介紹了Android項(xiàng)目多服務(wù)端接口適配(超簡(jiǎn)單),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08Android實(shí)現(xiàn)標(biāo)題顯示隱藏功能
這篇文章主要介紹了Android實(shí)現(xiàn)標(biāo)題顯示隱藏功能2016-02-02Android顏色處理SweepGradient掃描及梯度渲染示例
這篇文章主要為大家介紹了Android顏色處理SweepGradient掃描渲染及梯度渲染示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Android使用viewpager實(shí)現(xiàn)自動(dòng)無(wú)限輪播圖
這篇文章主要介紹了Android使用viewpager實(shí)現(xiàn)自動(dòng)無(wú)限輪播圖效果,實(shí)現(xiàn)方法大概有兩種,一種是viewpager+作為游標(biāo)的點(diǎn) 。另外一種是重寫(xiě)viewpager,具體實(shí)現(xiàn)過(guò)程大家參考下本文2018-06-06