Android 工具篇 02 --- Android嚴苛模式StrictMode使用詳解

StrictMode類是Android 2.3 (API 9)引入的一個工具類,可以用來幫助開發(fā)者發(fā)現(xiàn)代碼中的一些不規(guī)范的問題希俩,以達到提升應(yīng)用響應(yīng)能力的目的琳骡。舉個例子來說煌往,如果開發(fā)者在UI線程中進行了網(wǎng)絡(luò)操作或者文件系統(tǒng)的操作,而這些緩慢的操作會嚴重影響應(yīng)用的響應(yīng)能力糠爬,甚至出現(xiàn)ANR對話框寇荧。為了在開發(fā)中發(fā)現(xiàn)這些容易忽略的問題,我們使用StrictMode执隧,系統(tǒng)檢測出主線程違例的情況并做出相應(yīng)的反應(yīng)揩抡,最終幫助開發(fā)者優(yōu)化和改善代碼邏輯户侥。

官網(wǎng)文檔:http://developer.android.com/reference/android/os/StrictMode.html

StrictMode具體能檢測什么

嚴苛模式主要檢測兩大問題

  • 一個是線程策略,即TreadPolicy
  • 另一個是VM策略捅膘,即VmPolicy添祸。

ThreadPolicy線程策略檢測

線程策略檢測的內(nèi)容有:

  • 自定義的耗時調(diào)用 使用detectCustomSlowCalls()開啟
  • 磁盤讀取操作 使用detectDiskReads()開啟
  • 磁盤寫入操作 使用detectDiskWrites()開啟
  • 網(wǎng)絡(luò)操作 使用detectNetwork()開啟

VmPolicy虛擬機策略檢測

  • Activity泄露 使用detectActivityLeaks()開啟
  • 未關(guān)閉的Closable對象泄露 使用detectLeakedClosableObjects()開啟
  • 泄露的Sqlite對象 使用detectLeakedSqlLiteObjects()開啟
  • 檢測實例數(shù)量 使用setClassInstanceLimit()開啟

工作原理

其實StrictMode實現(xiàn)原理也比較簡單,以IO操作為例寻仗,主要是通過在open刃泌,read,write署尤,close時進行監(jiān)控耙替。libcore.io.BlockGuardOs文件就是監(jiān)控的地方。以open為例曹体,如下進行監(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()方法的實現(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);
}

常見用法

嚴格模式的開啟可以放在Application或者Activity以及其他組件的onCreate方法铜幽。為了更好地分析應(yīng)用中的問題,建議放在Application的onCreate方法中串稀。

其中除抛,我們只需要在app的開發(fā)版本下使用 StrictMode,線上版本避免使用 StrictMode母截,這里定義了一個布爾值變量DEV_MODE來進行控制到忽。

private boolean DEV_MODE = true;
public void onCreate() {
if (DEV_MODE) {
 StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
   .detectCustomSlowCalls() //API等級11,使用StrictMode.noteSlowCode
   .detectDiskReads()
   .detectDiskWrites()
   .detectNetwork() // or .detectAll() for all detectable problems
   .penaltyDialog() //彈出違規(guī)提示對話框
   .penaltyLog() //在Logcat 中打印違規(guī)異常信息
   .penaltyFlashScreen() //API等級11
   .build());
 StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
   .detectLeakedSqlLiteObjects()
   .detectLeakedClosableObjects() //API等級11
   .penaltyLog()
   .penaltyDeath()
   .build());
}
super.onCreate();
}

其中Android3.0引入的方法包括detectCustomSlowCalls()和noteSlowCode()清寇,它們都是用來檢測應(yīng)用中執(zhí)行緩慢代碼的或者潛在的緩慢代碼喘漏。

查看報告結(jié)果

嚴格模式有很多種報告違例的形式,但是想要分析具體違例情況华烟,還是需要查看日志翩迈,終端下過濾StrictMode就能得到違例的具體stacktrace信息。

adb logcat | grep StrictMode
image.png

當然也可以選擇彈窗形式來簡明提醒開發(fā)者


image.png

彈窗警告

ThreadPolicy 詳解

StrictMode.ThreadPolicy.Builder 主要方法如下

detectNetwork() 用于檢查UI線程中是否有網(wǎng)絡(luò)請求操作

檢測UI線程中網(wǎng)絡(luò)請求案例:

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();
}
}
}

運行后盔夜,觸發(fā)的警告如下


image.png

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();
}
}
}

運行后帽馋,觸發(fā)的警告如下

image.png

noteSlowCall針對執(zhí)行比較耗時的檢查

StrictMode從 API 11開始允許開發(fā)者自定義一些耗時調(diào)用違例,這種自定義適用于自定義的任務(wù)執(zhí)行類中比吭,比如我們有一個進行任務(wù)處理的類绽族,為TaskExecutor。

public class TaskExecutor {
public void execute(Runnable task) {
task.run();
}
}

先需要跟蹤每個任務(wù)的耗時情況衩藤,如果大于500毫秒需要提示給開發(fā)者吧慢,noteSlowCall就可以實現(xiàn)這個功能,如下修改代碼

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í)行一個耗時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并非耗時任務(wù)的執(zhí)行時間检诗,而我們的自定義信息msg=slowCall cost=2000才包含了真正的耗時匈仗。

penaltyDeath(),當觸發(fā)違規(guī)條件時逢慌,直接Crash掉當前應(yīng)用程序悠轩。

penaltyDeathOnNetwork(),當觸發(fā)網(wǎng)絡(luò)違規(guī)時攻泼,Crash掉當前應(yīng)用程序火架。

penaltyDialog(),觸發(fā)違規(guī)時忙菠,顯示對違規(guī)信息對話框何鸡。

penaltyFlashScreen(),會造成屏幕閃爍牛欢,不過一般的設(shè)備可能沒有這個功能骡男。

penaltyDropBox(),將違規(guī)信息記錄到 dropbox 系統(tǒng)日志目錄中(/data/system/dropbox)傍睹,你可以通過如下命令進行插件:

adb shell dumpsys dropbox dataappstrictmode --print

permitCustomSlowCalls()隔盛、permitDiskReads ()、permitDiskWrites()拾稳、permitNetwork: 如果你想關(guān)閉某一項檢測吮炕,可以使用對應(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)屏幕就會輸出提示信息(重點在 instances=2; limit=1 這一行)
image.png

這時因為熊赖,我們在Activity中創(chuàng)建了一個Thread匿名內(nèi)部類,而匿名內(nèi)部類隱式持有外部類的引用虑椎。而每次旋轉(zhuǎn)屏幕是震鹉,Android會新創(chuàng)建一個Activity,而原來的Activity實例又被我們啟動的匿名內(nèi)部類線程持有捆姜,所以不會釋放传趾,從日志上看,當先系統(tǒng)中該Activty有4個實例泥技,而限制是只能創(chuàng)建1各實例浆兰。我們不斷翻轉(zhuǎn)屏幕,instances 的個數(shù)還會持續(xù)增加珊豹。

detectLeakedClosableObjects()用于資源沒有正確關(guān)閉時提醒

// 資源引用沒有關(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(); 我們在這里特意沒有關(guān)閉 fw
} catch (IOException e) {
 e.printStackTrace();
}
}
}

運行后觸發(fā)警告如下
image.png
  • detectLeakedSqlLiteObjects() 和
  • detectLeakedClosableObjects()的用法類似簸呈,只不過是用來檢查 SQLiteCursor 或者 其他 SQLite
  • 對象是否被正確關(guān)閉
  • detectLeakedRegistrationObjects() 用來檢查 BroadcastReceiver 或者
  • ServiceConnection 注冊類對象是否被正確釋放
  • setClassInstanceLimit(),設(shè)置某個類的同時處于內(nèi)存中的實例上限店茶,可以協(xié)助檢查內(nèi)存泄露

檢測內(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());
}
}

運行后觸發(fā)警告如下
image.png

其他操作

除了通過日志查看之外蜕便,我們也可以在開發(fā)者選項中開啟嚴格模式,開啟之后贩幻,如果主線程中有執(zhí)行時間長的操作轿腺,屏幕則會閃爍两嘴,這是一個更加直接的方法。
image.png

注意事項

  • 只在開發(fā)階段啟用StrictMode族壳,發(fā)布應(yīng)用或者release版本一定要禁用它憔辫。
  • 嚴格模式無法監(jiān)控JNI中的磁盤IO和網(wǎng)絡(luò)請求。
  • 應(yīng)用中并非需要解決全部的違例情況仿荆,比如有些IO操作必須在主線程中進行贰您。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市赖歌,隨后出現(xiàn)的幾起案子枉圃,更是在濱河造成了極大的恐慌,老刑警劉巖庐冯,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件孽亲,死亡現(xiàn)場離奇詭異,居然都是意外死亡展父,警方通過查閱死者的電腦和手機返劲,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來栖茉,“玉大人篮绿,你說我怎么就攤上這事÷榔” “怎么了亲配?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長惶凝。 經(jīng)常有香客問我吼虎,道長,這世上最難降的妖魔是什么苍鲜? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任思灰,我火速辦了婚禮,結(jié)果婚禮上混滔,老公的妹妹穿的比我還像新娘洒疚。我一直安慰自己,他們只是感情好坯屿,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布油湖。 她就那樣靜靜地躺著,像睡著了一般领跛。 火紅的嫁衣襯著肌膚如雪肺魁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天隔节,我揣著相機與錄音鹅经,去河邊找鬼寂呛。 笑死,一個胖子當著我的面吹牛瘾晃,可吹牛的內(nèi)容都是我干的贷痪。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼蹦误,長吁一口氣:“原來是場噩夢啊……” “哼劫拢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起强胰,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤舱沧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后偶洋,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體熟吏,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年玄窝,在試婚紗的時候發(fā)現(xiàn)自己被綠了牵寺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡恩脂,死狀恐怖帽氓,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情俩块,我是刑警寧澤黎休,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站玉凯,受9級特大地震影響势腮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜壮啊,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一嫉鲸、第九天 我趴在偏房一處隱蔽的房頂上張望撑蒜。 院中可真熱鬧歹啼,春花似錦、人聲如沸座菠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽浴滴。三九已至拓萌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間升略,已是汗流浹背微王。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工屡限, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人炕倘。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓钧大,卻偏偏與公主長得像,于是被迫代替她去往敵國和親罩旋。 傳聞我的和親對象是個殘疾皇子啊央,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容