5.UI執行緒和非UI執行緒的互動方式
阿新 • • 發佈:2019-02-20
一般來說有三種方式:
1.Activity.unOnUiThread(Runnable)
如果當前執行緒是UI Thread,立馬執行action.run方法;否則將Runnable傳送到UI Thread的event 佇列中。
2. view.post(Runnable)
將action加入到UI thread 的message queue。
3.view.postDelayed(Runnable,long)同2一樣,經過long時間之後將runnable傳送給message queue。
5.1UI執行緒
當一個app啟動時,系統為該app建立一個執行緒,成為主執行緒 。這個主執行緒管理ui中的元件,包括分發事件,重繪view等。因此也叫做UI
Thread。我們還知道當預設情況下,系統並不為四大元件建立新的執行緒,都是執行在這個UI Thread之中,這種方式也稱為單執行緒模型。所以一旦app中出現了耗時的工作(訪問網路,寫檔案,讀寫資料庫等等)有可能導致thread阻塞,如果這時系統無法及時分發事件,對於使用者來說最直觀的感受就是卡住了,而這時使用者又不停的點選螢幕很有可能出現ANR。在設計app時要盡力避免出現ANR的出現,關於ANR請看5.2
而且UI toolkit不是執行緒安全的,所以一定不能從子執行緒中操作ui,只能在ui thread中操作ui。對於android中的這種單執行緒模型有兩條規則
1.不要阻塞UI thread
2.不要在非UI Thread中操作UI
如果在子執行緒中訪問ui執行緒中的view,會報錯:android.view.ViewRootImpl$CalledFromWrongThreadException:Onlythe original thread that created a view hierarchy
can touch its views.
但是現實中需要進行耗時操作的場景又非常多,如果需要耗時操作,然後更新UI怎麼做呢(下載圖片然後更新imageview)?我們先來按照它的規則來指定自己的方案:
先按照規則1:不在UI Thread中做耗時操作,那麼我們開啟一個子執行緒來處理耗時操作,然後再更新view
newThread(new Runnable(){
public void run(){
//模擬耗時操作,比如上網下載圖片
Bitmap b = loadBitmapFormWeb();
imageview.setBitmap(b);//在子執行緒中如果直接訪問UI執行緒,會報錯,不允許這麼做。
//這裡可以操作runOnUiThread,imageView.post(Runnable),imageView.postDelay();
imageview.post(new Runnable(){
imageview.setImageBitmap(b);
});
}
}).start();
但是這又不符合規則2,不能在非UI Thread中操作UI。這就很矛盾了,android系統在設計時已經考慮過這個問題,所以在android系統提供了幾種方式在非UI Thread中訪問UI執行緒:
Activity.runOnUiThread,View.post(),View.postDelayed()。
這樣的話,耗時操作在子執行緒中執行,UI 更新在UI Thread中執行,兩者互不干擾。
如果子執行緒過多時,程式碼就會顯得很臃腫、可讀性不好。所以android 系統中集成了一個AsyncTask工具類用來包裝Thread+Handler,這個類的主要在子執行緒執行耗時操作,然後在UI執行緒更新UI。同時使得程式碼的可讀性好多了。關於AsyncTask有專門介紹。
5.2 ANR
在使用android系統的時候或多或少可能遇到過ANR(Application Not Responding), 類似的這種ANR對話方塊應該都見過。
為什麼會出現:android中的所有元件預設都是在UI執行緒中完成的,其中有ActivityManagerService(AMS)和WindowsManagerService(WMS)會監控一個動作的響應時間,如果在一定範圍內對使用者互動沒有處理完畢就會出現ANR。比如說分派事件,假如某一段時間內UI 執行緒做了耗時操作,執行緒阻塞了,那麼使用者的一個觸控事件就可能來不得及處理,超過一定的時間就會導致ANR。當然這只是ANR其中的一種出現原因。對於用於來說一旦開始互動事件沒有響應,就有可能不停的觸控。這更增加了ANR的機率,對於開發者來說開發過程中一定儘量避免ANR出現的可能。
ANR出現有原因的,要同時滿足幾個條件:
1.主執行緒也就是必須在UI執行緒中相應超時。所以我們可以將耗時操作放在子執行緒中執行來避免這個條件出現。
2.超時,必須超過一定的時間限制,不同的ANR的時間限制不一樣,下面有介紹。
3.互動時間或者特定操作。不同的ANR觸發的時間不一樣。
主要有三種ANR型別
1.KeyDispatchTimeOut(5s),主執行緒對使用者互動事件5s中沒有處理完畢(這種情況最常見):當app獲得使用者互動事件時(按鍵,觸控),系統先獲得這個互動事件然後分派到這個app,分派到app之後再分派給對應的View(比如button)。如果在5s內,該互動事件沒有處理完成就會通過一系列的回撥函式,最終到AMS中處理該處理keyDispatchTimeOut超時。
一定要注意:出現這種ANR前提是有使用者互動事件,如果沒有互動事件,就算是主執行緒阻塞主執行緒也不會ANR,因為這個時候就沒有事件派發。可以測試,如果僅僅是Thread.sleep(60000),不做任何互動都不會出現ANR(看具體的裝置和系統,我的榮耀3c暢玩版就不會出現,HTC的就出現)。
2.BroadcastTimeOut(10s)BroadcastReceiver 中的onReceiver方法執行超過10s。
3.ServiceTimeout(20s),service生命週期方法中執行事件超過20s。
2和3中不會產生對話方塊形式(和裝置有關。我的華為榮耀3c暢玩版、htc都出現了)。這三種anr的出現影響最大的就是第一個,但是三者之間並不是單一出現的,有可能receiver中執行時阻塞,使用者互動,系統沒法及時處理該互動,出現第一種;如果receiver超時後又出現第二種。不管怎樣,避免這三種操作就能最大程度的避免ANR的出現。
出現ANR時,有兩個輸出檔案要重視:一個就是系統輸出的log tag,一個就是/data/anr/traces.txt,這兩個檔案使我們定位ANR的利器
先看系統的log:
//ANR 在哪個程序發生了ANR
05-14 15:24:10.519: E/ActivityManager(499): ANR in com.example.servicedemo, time=1037238
//reson指明原因
05-14 15:24:10.519: E/ActivityManager(499): Reason:
Broadcast of Intent { act=com.service.ACTION flg=0x10 cmp=com.example.servicedemo/.MyReceiver }
//ago 指明ANR之前cup使用情況
05-14 15:24:10.519: E/ActivityManager(499): CPU usage from 4973ms to -1016msago:
//later指明ANR之後cpu使用情況
05-14 15:24:10.519: E/ActivityManager(499): CPU usage from 472ms to 993mslater:
再來看traces.txt內容:(目錄:/data/anr/traces.txt)
----- pid 5085 at 2015-05-14 15:24:09 -----
Cmd line: com.example.servicedemo
DALVIK THREADS:
(mutexes: tll=0 tsl=0 tscl=0 ghl=0)
"main" prio=5 tid=1 TIMED_WAIT
| group="main" sCount=1 dsCount=0 obj=0x40ae3490 self=0x15cad58
| sysTid=5085 nice=0 sched=0/0 cgrp=default handle=1074472136
| schedstat=( 0 0 0 ) utm=17 stm=6 core=0
at java.lang.VMThread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:1047)
at java.lang.Thread.sleep(Thread.java:1029)
at com.example.servicedemo.MyReceiver.onReceive(MyReceiver.java:19)
這兩個資訊是如何輸出的:在ams中的appNotResponding函式: synchronized (this) { //在某些情況下忽略ANR
// PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
if (mShuttingDown) {
Slog.i(TAG, "During shutdown skipping ANR: " + app + " " + annotation);
return;
} else if (app.notResponding) {
Slog.i(TAG, "Skipping duplicate ANR: " + app + " " + annotation);
return;
} else if (app.crashing) {
Slog.i(TAG, "Crashing app skipping ANR: " + app + " " + annotation);
return;
}
//..... // Log the ANR to the main log. StringBuilder info = mStringBuilder;
info.setLength(0);
info.append("ANR in ").append(app.processName);
if (activity != null && activity.shortComponentName != null) {
info.append(" (").append(activity.shortComponentName).append(")");
} info.append("\n");
if (annotation != null) {
info.append("Reason: ").append(annotation).append("\n");
}
if (parent != null && parent != activity) {
info.append("Parent: ").append(parent.shortComponentName).append("\n");
} //指定traces檔案儲存地址 File tracesFile = dumpStackTraces(true, firstPids, processStats, lastPids); String cpuInfo = null;
if (MONITOR_CPU_USAGE) {
updateCpuStatsNow();
synchronized (mProcessStatsThread) {
cpuInfo = mProcessStats.printCurrentState(anrTime);
} //anr之前cpu負載資訊
info.append(processStats.printCurrentLoad());
info.append(cpuInfo);
}
//anr之後cpu負載資訊
info.append(processStats.printCurrentState(anrTime)); //將anr資訊加入到dropBox中(管理log的工具) addErrorToDropBox("anr", app, activity, parent, annotation, cpuInfo, tracesFile, null); // Unless configured otherwise, swallow ANRs in background processes & kill the process. //讀取使用者配置,是否顯示後臺程序anr對話方塊。不顯示就直接殺死 boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; synchronized (this) { if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {
Slog.w(TAG, "Killing " + app + ": background ANR");
EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
app.processName, app.setAdj, "background ANR");
Process.killProcessQuiet(app.pid);//殺死程序
return;
}
// Bring up the infamous App Not Responding dialog,傳送資訊給mHandler,提示可以顯示ANR對話方塊
Message msg = Message.obtain();
HashMap map = new HashMap();
msg.what = SHOW_NOT_RESPONDING_MSG;
msg.obj = map;
map.put("app", app);
if (activity != null) {
map.put("activity", activity);
}
mHandler.sendMessage(msg); mHandler中handleMessage函式中相應的處理為: case SHOW_NOT_RESPONDING_MSG: {
synchronized (ActivityManagerService.this) { Dialog d = new AppNotRespondingDialog(ActivityManagerService.this,
mContext, proc, (ActivityRecord)data.get("activity"));
d.show();//顯示anr對話方塊
}
} break; 如何避免:ANR產生的原因是因為在主執行緒中執行了耗時的操作,所以將耗時操作方法在子執行緒中執行,可以使用handler+thread 或者AsyncTask方式。 同時為了給使用者好的體驗,也有幾個建議: 1.執行耗時操作時,給使用者ui提示,比如進度對話方塊。 2.遊戲類app用子執行緒計算。 3.應用程式的初始化,可以使用一個splash或者過場動畫,這種使用的很多,比如一開始開啟app時顯示一張歡迎圖片(天天動聽),這樣既給使用者app正在響應的感覺,同時可以執行耗時操作。
Cmd line: com.example.servicedemo
DALVIK THREADS:
(mutexes: tll=0 tsl=0 tscl=0 ghl=0)
"main" prio=5 tid=1 TIMED_WAIT
| group="main" sCount=1 dsCount=0 obj=0x40ae3490 self=0x15cad58
| sysTid=5085 nice=0 sched=0/0 cgrp=default handle=1074472136
| schedstat=( 0 0 0 ) utm=17 stm=6 core=0
at java.lang.VMThread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:1047)
at java.lang.Thread.sleep(Thread.java:1029)
at com.example.servicedemo.MyReceiver.onReceive(MyReceiver.java:19)
這兩個資訊是如何輸出的:在ams中的appNotResponding函式: synchronized (this) { //在某些情況下忽略ANR
// PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down.
if (mShuttingDown) {
Slog.i(TAG, "During shutdown skipping ANR: " + app + " " + annotation);
return;
} else if (app.notResponding) {
Slog.i(TAG, "Skipping duplicate ANR: " + app + " " + annotation);
return;
} else if (app.crashing) {
Slog.i(TAG, "Crashing app skipping ANR: " + app + " " + annotation);
return;
}
//..... // Log the ANR to the main log. StringBuilder info = mStringBuilder;
info.setLength(0);
info.append("ANR in ").append(app.processName);
if (activity != null && activity.shortComponentName != null) {
info.append(" (").append(activity.shortComponentName).append(")");
} info.append("\n");
if (annotation != null) {
info.append("Reason: ").append(annotation).append("\n");
}
if (parent != null && parent != activity) {
info.append("Parent: ").append(parent.shortComponentName).append("\n");
} //指定traces檔案儲存地址 File tracesFile = dumpStackTraces(true, firstPids, processStats, lastPids); String cpuInfo = null;
if (MONITOR_CPU_USAGE) {
updateCpuStatsNow();
synchronized (mProcessStatsThread) {
cpuInfo = mProcessStats.printCurrentState(anrTime);
} //anr之前cpu負載資訊
info.append(processStats.printCurrentLoad());
info.append(cpuInfo);
}
//anr之後cpu負載資訊
info.append(processStats.printCurrentState(anrTime)); //將anr資訊加入到dropBox中(管理log的工具) addErrorToDropBox("anr", app, activity, parent, annotation, cpuInfo, tracesFile, null); // Unless configured otherwise, swallow ANRs in background processes & kill the process. //讀取使用者配置,是否顯示後臺程序anr對話方塊。不顯示就直接殺死 boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(),
Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; synchronized (this) { if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) {
Slog.w(TAG, "Killing " + app + ": background ANR");
EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
app.processName, app.setAdj, "background ANR");
Process.killProcessQuiet(app.pid);//殺死程序
return;
}
// Bring up the infamous App Not Responding dialog,傳送資訊給mHandler,提示可以顯示ANR對話方塊
Message msg = Message.obtain();
HashMap map = new HashMap();
msg.what = SHOW_NOT_RESPONDING_MSG;
msg.obj = map;
map.put("app", app);
if (activity != null) {
map.put("activity", activity);
}
mHandler.sendMessage(msg); mHandler中handleMessage函式中相應的處理為: case SHOW_NOT_RESPONDING_MSG: {
synchronized (ActivityManagerService.this) { Dialog d = new AppNotRespondingDialog(ActivityManagerService.this,
mContext, proc, (ActivityRecord)data.get("activity"));
d.show();//顯示anr對話方塊
}
} break; 如何避免:ANR產生的原因是因為在主執行緒中執行了耗時的操作,所以將耗時操作方法在子執行緒中執行,可以使用handler+thread 或者AsyncTask方式。 同時為了給使用者好的體驗,也有幾個建議: 1.執行耗時操作時,給使用者ui提示,比如進度對話方塊。 2.遊戲類app用子執行緒計算。 3.應用程式的初始化,可以使用一個splash或者過場動畫,這種使用的很多,比如一開始開啟app時顯示一張歡迎圖片(天天動聽),這樣既給使用者app正在響應的感覺,同時可以執行耗時操作。