StrictMode ——Android性能調優的利器
性能無外乎就是CPU密集型或I/O密集型兩種。
StrictMode是一個開發者工具,常用于捕獲在應用主線程中發生的磁盤I/O、網絡訪問違例等問題。
StrictMode具體能檢測什么呢
StrictMode主要檢測兩大問題:線程策略(TreadPolicy)和VM策略(VmPolicy)。
ThreadPolicy線程策略:
自定義的耗時調用,使用detectCustomSlowCalls()開啟;
磁盤讀取操作,使用detectDiskReads()開啟;
磁盤寫入操作,使用detectDiskWrites()開啟;
網絡操作,使用detectNetwork()開啟。
VmPolicy虛擬機策略:
Activity泄漏,使用detectActivityLeaks()開啟;
未關閉的Closable對象泄漏,使用detectLeakedClosableObjects()開啟;
泄漏的Sqlite對象,使用detectLeakedSqlLiteObjects()開啟;
檢測實例數量,使用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
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
注意
StrictMode無法監控JNI中的磁盤IO和網絡請求。
應用中并非需要解決全部的違例情況,比如有些IO操作必須在主線程中進行。
通常情況下StrictMode給出的耗時相對實際情況偏高,并不是真正的耗時數據。
Android 應用性能調優
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。