Android嚴(yán)苛模式StrictMode使用詳解
StrictMode類是Android 2.3 (API 9)引入的一個(gè)工具類,可以用來幫助開發(fā)者發(fā)現(xiàn)代碼中的一些不規(guī)范的問題,以達(dá)到提升應(yīng)用響應(yīng)能力的目的。舉個(gè)例子來說,如果開發(fā)者在UI線程中進(jìn)行了網(wǎng)絡(luò)操作或者文件系統(tǒng)的操作,而這些緩慢的操作會(huì)嚴(yán)重影響應(yīng)用的響應(yīng)能力,甚至出現(xiàn)ANR對(duì)話框。為了在開發(fā)中發(fā)現(xiàn)這些容易忽略的問題,我們使用StrictMode,系統(tǒng)檢測(cè)出主線程違例的情況并做出相應(yīng)的反應(yīng),最終幫助開發(fā)者優(yōu)化和改善代碼邏輯。
官網(wǎng)文檔:http://developer.android.com/reference/android/os/StrictMode.html
StrictMode具體能檢測(cè)什么
嚴(yán)苛模式主要檢測(cè)兩大問題,一個(gè)是線程策略,即TreadPolicy,另一個(gè)是VM策略,即VmPolicy。
ThreadPolicy線程策略檢測(cè)
- 線程策略檢測(cè)的內(nèi)容有
- 自定義的耗時(shí)調(diào)用 使用detectCustomSlowCalls()開啟
- 磁盤讀取操作 使用detectDiskReads()開啟
- 磁盤寫入操作 使用detectDiskWrites()開啟
- 網(wǎng)絡(luò)操作 使用detectNetwork()開啟
VmPolicy虛擬機(jī)策略檢測(cè)
- Activity泄露 使用detectActivityLeaks()開啟
- 未關(guān)閉的Closable對(duì)象泄露 使用detectLeakedClosableObjects()開啟
- 泄露的Sqlite對(duì)象 使用detectLeakedSqlLiteObjects()開啟
- 檢測(cè)實(shí)例數(shù)量 使用setClassInstanceLimit()開啟
工作原理
其實(shí)StrictMode實(shí)現(xiàn)原理也比較簡(jiǎn)單,以IO操作為例,主要是通過在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); }
常見用法
嚴(yán)格模式的開啟可以放在Application或者Activity以及其他組件的onCreate方法。為了更好地分析應(yīng)用中的問題,建議放在Application的onCreate方法中。
其中,我們只需要在app的開發(fā)版本下使用 StrictMode,線上版本避免使用 StrictMode,這里定義了一個(gè)布爾值變量DEV_MODE來進(jìn)行控制。
private boolean DEV_MODE = true; public void onCreate() { if (DEV_MODE) { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectCustomSlowCalls() //API等級(jí)11,使用StrictMode.noteSlowCode .detectDiskReads() .detectDiskWrites() .detectNetwork() // or .detectAll() for all detectable problems .penaltyDialog() //彈出違規(guī)提示對(duì)話框 .penaltyLog() //在Logcat 中打印違規(guī)異常信息 .penaltyFlashScreen() //API等級(jí)11 .build()); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedSqlLiteObjects() .detectLeakedClosableObjects() //API等級(jí)11 .penaltyLog() .penaltyDeath() .build()); } super.onCreate(); }
其中Android3.0引入的方法包括detectCustomSlowCalls()和noteSlowCode(),它們都是用來檢測(cè)應(yīng)用中執(zhí)行緩慢代碼的或者潛在的緩慢代碼。
查看報(bào)告結(jié)果
嚴(yán)格模式有很多種報(bào)告違例的形式,但是想要分析具體違例情況,還是需要查看日志,終端下過濾StrictMode就能得到違例的具體stacktrace信息。
adb logcat | grep StrictMode
當(dāng)然也可以選擇彈窗形式來簡(jiǎn)明提醒開發(fā)者
彈窗警告
ThreadPolicy 詳解
StrictMode.ThreadPolicy.Builder 主要方法如下
detectNetwork() 用于檢查UI線程中是否有網(wǎng)絡(luò)請(qǐng)求操作
檢測(cè)UI線程中網(wǎng)絡(luò)請(qǐng)求案例:
public class MainActivity extends AppCompatActivity implements View.OnClickListener { Button btnTest; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectNetwork() .penaltyLog() .build()); btnTest = (Button) findViewById(R.id.btn_test); btnTest.setOnClickListener(this); } @Override public void onClick(View v) { int id = v.getId(); switch (id) { case R.id.btn_test: postNetwork(); break; } } /** * 網(wǎng)絡(luò)連接的操作 */ private void postNetwork() { try { URL url = new URL("http://www.wooyun.org"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.connect(); BufferedReader reader = new BufferedReader(new InputStreamReader( conn.getInputStream())); String lines = null; StringBuffer sb = new StringBuffer(); while ((lines = reader.readLine()) != null) { sb.append(lines); } } catch (Exception e) { e.printStackTrace(); } } }
運(yùn)行后,觸發(fā)的警告如下
detectDiskReads() 和 detectDiskWrites() 是磁盤讀寫檢查
磁盤讀寫檢查案例:
public class MainActivity extends AppCompatActivity implements View.OnClickListener { Button btnTest; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectDiskWrites() .detectDiskReads() .penaltyLog() .build()); btnTest = (Button) findViewById(R.id.btn_test); btnTest.setOnClickListener(this); } @Override public void onClick(View v) { int id = v.getId(); switch (id) { case R.id.btn_test: writeToExternalStorage(); break; } } /** * 文件系統(tǒng)的操作 */ public void writeToExternalStorage() { File externalStorage = Environment.getExternalStorageDirectory(); File mbFile = new File(externalStorage, "castiel.txt"); try { OutputStream output = new FileOutputStream(mbFile, true); output.write("www.wooyun.org".getBytes()); output.flush(); output.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
運(yùn)行后,觸發(fā)的警告如下
noteSlowCall針對(duì)執(zhí)行比較耗時(shí)的檢查
StrictMode從 API 11開始允許開發(fā)者自定義一些耗時(shí)調(diào)用違例,這種自定義適用于自定義的任務(wù)執(zhí)行類中,比如我們有一個(gè)進(jìn)行任務(wù)處理的類,為TaskExecutor。
public class TaskExecutor { public void execute(Runnable task) { task.run(); } }
先需要跟蹤每個(gè)任務(wù)的耗時(shí)情況,如果大于500毫秒需要提示給開發(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í)。
penaltyDeath(),當(dāng)觸發(fā)違規(guī)條件時(shí),直接Crash掉當(dāng)前應(yīng)用程序。
penaltyDeathOnNetwork(),當(dāng)觸發(fā)網(wǎng)絡(luò)違規(guī)時(shí),Crash掉當(dāng)前應(yīng)用程序。
penaltyDialog(),觸發(fā)違規(guī)時(shí),顯示對(duì)違規(guī)信息對(duì)話框。
penaltyFlashScreen(),會(huì)造成屏幕閃爍,不過一般的設(shè)備可能沒有這個(gè)功能。
penaltyDropBox(),將違規(guī)信息記錄到 dropbox 系統(tǒng)日志目錄中(/data/system/dropbox),你可以通過如下命令進(jìn)行插件:
adb shell dumpsys dropbox dataappstrictmode --print
permitCustomSlowCalls()、permitDiskReads ()、permitDiskWrites()、permitNetwork: 如果你想關(guān)閉某一項(xiàng)檢測(cè),可以使用對(duì)應(yīng)的permit*方法。
VMPolicy 詳解
StrictMode.VmPolicy.Builder 主要方法如下
detectActivityLeaks() 用戶檢查 Activity 的內(nèi)存泄露情況
內(nèi)存泄露檢查案例:
public class MainActivity extends AppCompatActivity implements View.OnClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectActivityLeaks() .penaltyLog() .build() ); new Thread() { @Override public void run() { while (true) { SystemClock.sleep(1000); } } }.start(); } }
我們反復(fù)旋轉(zhuǎn)屏幕就會(huì)輸出提示信息(重點(diǎn)在 instances=2; limit=1 這一行)
這時(shí)因?yàn)?,我們?cè)贏ctivity中創(chuàng)建了一個(gè)Thread匿名內(nèi)部類,而匿名內(nèi)部類隱式持有外部類的引用。而每次旋轉(zhuǎn)屏幕是,Android會(huì)新創(chuàng)建一個(gè)Activity,而原來的Activity實(shí)例又被我們啟動(dòng)的匿名內(nèi)部類線程持有,所以不會(huì)釋放,從日志上看,當(dāng)先系統(tǒng)中該Activty有4個(gè)實(shí)例,而限制是只能創(chuàng)建1各實(shí)例。我們不斷翻轉(zhuǎn)屏幕,instances 的個(gè)數(shù)還會(huì)持續(xù)增加。
detectLeakedClosableObjects()用于資源沒有正確關(guān)閉時(shí)提醒
// 資源引用沒有關(guān)閉檢查案例 public class MainActivity extends AppCompatActivity implements View.OnClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .detectLeakedClosableObjects() .penaltyLog() .build() ); File newxmlfile = new File(Environment.getExternalStorageDirectory(), "castiel.txt"); try { newxmlfile.createNewFile(); FileWriter fw = new FileWriter(newxmlfile); fw.write("猴子搬來的救兵WooYun"); //fw.close(); 我們?cè)谶@里特意沒有關(guān)閉 fw } catch (IOException e) { e.printStackTrace(); } } }
運(yùn)行后觸發(fā)警告如下
- detectLeakedSqlLiteObjects() 和
- detectLeakedClosableObjects()的用法類似,只不過是用來檢查 SQLiteCursor 或者 其他 SQLite
- 對(duì)象是否被正確關(guān)閉
- detectLeakedRegistrationObjects() 用來檢查 BroadcastReceiver 或者
- ServiceConnection 注冊(cè)類對(duì)象是否被正確釋放
- setClassInstanceLimit(),設(shè)置某個(gè)類的同時(shí)處于內(nèi)存中的實(shí)例上限,可以協(xié)助檢查內(nèi)存泄露
檢測(cè)內(nèi)存泄露案例
public class MainActivity extends AppCompatActivity implements View.OnClickListener { private static class CastielClass{} private static List<CastielClass> classList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); classList = new ArrayList<CastielClass>(); StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() .setClassInstanceLimit(CastielClass.class, 2) .penaltyLog() .build()); classList.add(new CastielClass()); classList.add(new CastielClass()); classList.add(new CastielClass()); classList.add(new CastielClass()); classList.add(new CastielClass()); classList.add(new CastielClass()); classList.add(new CastielClass()); classList.add(new CastielClass()); } }
運(yùn)行后觸發(fā)警告如下
其他操作
除了通過日志查看之外,我們也可以在開發(fā)者選項(xiàng)中開啟嚴(yán)格模式,開啟之后,如果主線程中有執(zhí)行時(shí)間長(zhǎng)的操作,屏幕則會(huì)閃爍,這是一個(gè)更加直接的方法。
注意事項(xiàng)
- 只在開發(fā)階段啟用StrictMode,發(fā)布應(yīng)用或者release版本一定要禁用它。
- 嚴(yán)格模式無法監(jiān)控JNI中的磁盤IO和網(wǎng)絡(luò)請(qǐng)求。
- 應(yīng)用中并非需要解決全部的違例情況,比如有些IO操作必須在主線程中進(jìn)行。
總結(jié)
以上所述是小編給大家介紹的Android嚴(yán)苛模式StrictMode使用詳解,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Android Activity中使用Intent實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)與參數(shù)傳遞的方法
這篇文章主要介紹了Android Activity中使用Intent實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)與參數(shù)傳遞的方法,結(jié)合實(shí)例形式簡(jiǎn)單分析了Android中的Activity交互操作相關(guān)技巧,需要的朋友可以參考下2016-07-07Android強(qiáng)制設(shè)定橫屏?xí)r,SurfaceView一直黑屏
本文主要介紹了Android強(qiáng)制設(shè)定橫屏?xí)r,SurfaceView一直黑屏的方法。具有一定的參考作用,下面跟著小編一起來看下吧2017-01-01Android ViewPager實(shí)現(xiàn)Banner循環(huán)播放
這篇文章主要為大家詳細(xì)介紹了Android ViewPager實(shí)現(xiàn)Banner循環(huán)播放,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09Android 判斷當(dāng)前網(wǎng)絡(luò)是否可用簡(jiǎn)單實(shí)例
這篇文章主要介紹了Android 判斷當(dāng)前網(wǎng)絡(luò)是否可用簡(jiǎn)單實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-06-06android實(shí)現(xiàn)自動(dòng)滾動(dòng)的Gallary控件效果
這篇文章主要介紹了android實(shí)現(xiàn)自動(dòng)滾動(dòng)的Gallary控件效果,涉及Android中Gallary控件的相關(guān)使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-10-10