當一個程序啟動的時候,系統會為程序創建一個名為main的線程。這個線程重要性在於它負責把事件分發給適合的用戶組件,這些事件包括繪制事件。並且這個線程也是你的程序與Android UI工具包中的組件(比如android.widget和android.view包中的組件)進行交互的線程。正因為如此,這個main線程有時也被稱為UI線程。
系統並不會為組件的每個實例都創建一個單獨的線程。運行在同一個進程中的所有組件都是在UI線程中實例化的,並且系統對這些組件的調用都是由UI分發的。所以,對系統的回調做出回應的方法都是運行在進程中的UI線程中的(比如,用於報告用戶操作的onKeyDown()方法,或者生命周期回調方法)。
比如,當用戶按下瞭屏幕上的一個按鈕,你程序的UI線程就會將這個觸屏事件分發給這個button組件,然後這個組件會依次設置它的按下狀態,並向事件隊列發送一個無效請求(post an invalidate request to the event queue)。然後UI線程將這個請求彈出隊列,並通知這個組件使其重繪。
當你的程序在為瞭響應用戶事件,而頻繁的執行操作的時候,除非你能很恰當的實現你的程序,否則的話,可能會給用戶帶來不好的體驗。特別是,如果讓程序中所有的工作都在主線程中完成的時候,像訪問互聯網或者數據庫這些需要長時間的操作將會堵塞整個UI。當UI線程被阻塞的時候,所有的事件都不能被分發,包括繪制事件。從用戶的角度來講,這時這個程序就像終止瞭一樣。更糟糕的是,如果UI線程被堵塞超過5秒鐘的話,系統將會彈出一個“application not responing”(ANR)對話框。然後用戶可能就會決定放棄你的這個程序甚至選擇卸載。
另外,Android的UI toolkit不是線程安全的。所以,你不能在你的其他線程中處理UI,你的所有的UI方面的工作都應該在UI線程來完成。這裡,有兩條關於Android單線程模型的規則:
1.不要堵塞UI線程
2.不要在UI線程之外訪問Android UI toolkit
worker thread
正是因為上面討論的這種單線程模型,為瞭程序的響應速度,你是絕不能堵塞UI線程的。如果你要做的工作不是瞬時就能完成的,那麼你就應該在新的線程("background"或者“worker”線程)中處理它們。
比如,下面是關於單擊一個監聽器來在一個單獨的線程中下載一張圖片,並使用ImageView顯示該圖片的代碼:
[java]
public void onClick(View v){
new Thread(new Runnable(){
Bitma b = loadImageFromNetwork("/wp-content/images1/20181007/2012040210524540478.png");
mImageView.setImageBitmap(b);
}).start();
}
乍看起來的話,這段代碼好像是沒問題的,它創建一個新的線程來處理聯網操作。然而它違反瞭上面的第二條規則:不能在UI線程之外訪問Android Ui tookit,因為這個例子中在新的worker線程中修改瞭ImageView,而不是在UI線程中。這樣做將會導致未定義或者意料不到的行為發生,這對於發現這個錯誤來說,將是很難很耗時的。
為瞭解決這個問題,Android提供如下的方式,來實現從其他線程中訪問UI線程。下面是具體方法:
[java]
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
比如,你可以使用View.post(Runnable)來修復上面的代碼:
[java]
public void onClick(View v){
new Thread(new Runnable(){
public void run(){
final Bitmap bitmap = loadImageFromNetwork("/wp-content/images1/20181007/2012040210525049879.png");
mImageView.post(new Runnable(){
public void run(){
mImageView.setImageBitmap(bitmap);
}
});
}
}).start();
}
現在這種實現方式,就是線程安全的瞭:聯網的操作在一個單獨線程中完成,而ImageView的處理是在UI線程中完成的。
然而,當隨著操作的復雜性提高的時候,上面的代碼可能會變得復雜,並且不容易維護。為瞭使用worker線程處理更復雜的操作,你可以考慮在你的worker線程使用Handler來處理從UI線程中分發過來的消息。或許最好的方式,是通過繼承AsyncTask這個類,這簡化瞭worker線程需要同UI進行交互的過程。下面介紹怎樣使用AsyncTask。
AsyncTask允許你對於UI執行異步的操作。它在worker線程中執行耗時的操作,然後在UI線程中更新結果,在這個過程中不需要你去處理線程或者handlers。
為瞭使用AsyncTask,你必須繼承AsyncTask並實現doInBackground回調方法,這些回調方法運行在後臺線程的池中(run in a pool of background threads)。為瞭更新你的UI,你應該實現onPostExecute()方法,這個方法將doInBackground()方法中的處理結果傳遞到UI線程中,這樣,你就可以安全的更新你的UI瞭。然後你就可以在UI線程中調用execute()方法來實現這整個過程瞭。
比如,你可以通過使用AsyncTask來修改上面的代碼:
[java]
public void onClick(View v){
new DownloadImageTask().execute("/wp-content/images1/20181007/2012040210525049879.png");
}
private class DownloadImageTask extends AsyncTask<String, Void, Bitmap>{
/**The sytem calls this to perform work in worker thread and delivers it the parameters given to AsyncTask.execute()*/
procted Bitmap doInBackground(String… urls){
return loadImageFromNetwork(urls[0]);
}
/**系統調用該方法來在更新UI線程,並將doInbackground()的結果返回出來*/
protected void onPostExecute(Bitmap result){
mImageView.setImageBitmap(result);
}
}
現在這個Ui是安全的,並且代碼更簡單。下面是對AsyncTask的一個簡單介紹:
-你可以通過使用泛型,來指定參數的類型,進度值(the progress values)和the final value of the task。
-方法doInBackground()將自動在worker線程中執行
-onPreExecute(),onPostExecute()和onProgressUpdate()方法都是在UI線程中觸發的
-doInBackground()方法的結果將返回到onPostExecute()方法中
-在doInbackground()方法中,你可以在任意的時間通過調用publishprogress()來在主線程中執行onProgressUpdate()
-你可以從任何線程中取消當前的task
這裡有一點需要註意的是當使用worker線程時,可能會因為設備運行時配置發生瞭改變(比如屏幕翻轉),而導致worker線程重啟。
摘自 yaozq的android開發