Android 在其他線程中更新UI線程的解決方法

Android中消息機制: 

 
引用

Message:消息,其中包含了消息ID,消息處理對象以及處理的數據等,由MessageQueue統一列隊,終由Handler處理。 
Handler:處理者,負責Message的發送及處理。使用Handler時,需要實現handleMessage(Message msg)方法來對特定的Message進行處理,例如更新UI等。 
MessageQueue:消息隊列,用來存放Handler發送過來的消息,並按照FIFO規則執行。當然,存放Message並非實際意義的保存,而是將Message以鏈表的方式串聯起來的,等待Looper的抽取。 
Looper:消息泵,不斷地從MessageQueue中抽取Message執行。因此,一個MessageQueue需要一個Looper。 

Thread:線程,負責調度整個消息循環,即消息循環的執行場所。 

方法一:用Handler 

1、主線程中定義Handler: 

Java代碼  收藏代碼
  1. Handler mHandler = new Handler() {  
  2.   
  3.         @Override  
  4.         public void handleMessage(Message msg) {  
  5.             super.handleMessage(msg);  
  6.             switch (msg.what) {  
  7.             case 0:  
  8.                 //完成主界面更新,拿到數據  
  9.                 String data = (String)msg.obj;  
  10.                   
  11.                 updateWeather();  
  12.                 textView.setText(data);  
  13.                 break;  
  14.             default:  
  15.                 break;  
  16.             }  
  17.         }  
  18.   
  19.     };  


2、子線程發消息,通知Handler完成UI更新: 
Java代碼  收藏代碼
  1. private void updateWeather() {  
  2.           
  3.           
  4.         new Thread(new Runnable(){  
  5.   
  6.             @Override  
  7.             public void run() {  
  8.                 //耗時操作,完成之後發送消息給Handler,完成UI更新;  
  9.                 mHandler.sendEmptyMessage(0);  
  10.                   
  11.                 //需要數據傳遞,用下面方法;  
  12.                 Message msg =new Message();  
  13.                 msg.obj = "數據";//可以是基本類型,可以是對象,可以是List、map等;  
  14.                 mHandler.sendMessage(msg);  
  15.             }  
  16.               
  17.         }).start();  
  18.           
  19.     }  

方法一的Handler對象必須定義在主線程中,如果是多個類直接互相調用,就不是很方便,需要傳遞content對象或通過接口調用; 

方法二:用Activity對象的runOnUiThread方法更新 
在子線程中通過runOnUiThread()方法更新UI: 
Java代碼  收藏代碼
  1. new Thread() {  
  2.             public void run() {  
  3.                 //這兒是耗時操作,完成之後更新UI;  
  4.                 runOnUiThread(new Runnable(){  
  5.   
  6.                     @Override  
  7.                     public void run() {  
  8.                         //更新UI  
  9.                         imageView.setImageBitmap(bitmap);  
  10.                     }  
  11.                       
  12.                 });  
  13.             }  
  14.         }.start();  

如果在非上下文類中(Activity),可以通過傳遞上下文實現調用; 
Java代碼  收藏代碼
  1. Activity activity = (Activity) imageView.getContext();  
  2.                 activity.runOnUiThread(new Runnable() {  
  3.   
  4.                     @Override  
  5.                     public void run() {  
  6.                         imageView.setImageBitmap(bitmap);  
  7.                     }  
  8.                 });  

這種方法使用比較靈活,但如果Thread定義在其他地方,需要傳遞Activity對象; 

方法三:View.post(Runnable r) 

子線程如果持有某個View的引用,要對該View進行更新,則可調用該View對象的post(Runnable r)或postDelay(Runnable r)方法

Handler對象也有post()方法。其實在Android的源碼中,這些post()方法都是藉助下面的第3種方法:Handler + Message來實現的。



Java代碼  收藏代碼
  1. imageView.post(new Runnable(){  
  2.   
  3.                     @Override  
  4.                     public void run() {  
  5.                         imageView.setImageBitmap(bitmap);  
  6.                     }  
  7.                       
  8.                 });  

4、Broadcast

子線程中發送廣播,主線程中接收廣播並更新UI

5、AsyncTask

AsyncTask可方便地實現新開一個線程,並將結果返回給UI線程,而不需要開發者手動去新開一個線程,也無須開發者使用Handler,非常方便。

應當注意的是AsyncTask是一個抽象類,其三個泛型參數的意義如下:
AsyncTask<Param, Progress, Result>
Param:發送給新開的線程的參數類型
Progress:表徵任務處理進度的類型。
Result:線程任務處理完之後,返回給UI線程的值的類型。

該類中有四個抽象函數,onPreExecute(), doInBackground(Params... param),
onProgressUpdate(Progress... progress), onPostExecute(Result result)。
除了,doInBackground(Params...)方法,其它三個方法都運行在UI線程。

自定義一個類繼承AsyncTask並至少實現doInBackground()函數。在該函數中執行的過程中,可以隨時調用publishProgress(Progress...)報告其執行進度。此時會觸發另一個方法onProgressUpdate(Progress... progress),以便在UI線程中的某些控件(如ProgressBar)上更新任務處理的進度。

也可以等doInBackground()執行完,進入onPostExecute()方法後,再進行UI控件的更新。

可在任意時間,任意線程中,取消AsyncTask開啓的任務(調用自定義的AsynTask子類的cancel(boolean mayInterruptIfRunning)方法)

使用示例如下:

//如果沒記錯的話,這個例子應該是之前總結的時候從官網剪下來的

public void onClick(View v) {
   new DownloadImageTask().execute("http://example.com/image.png");
}

private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
   /** The system calls this to perform work in a worker thread and
     * delivers it the parameters given to AsyncTask.execute() */
   protected Bitmap doInBackground(String... urls) {
       return loadImageFromNetwork(urls[0]);
   }
   
   /** The system calls this to perform work in the UI thread and delivers
     * the result from doInBackground() */
   protected void onPostExecute(Bitmap result) {
       mImageView.setImageBitmap(result);
   }
}


這種方法更簡單,但需要傳遞要更新的View過去; 
總結:UI的更新必須在主線程中完成,所以不管上述那種方法,都是將更新UI的消息發送到了主線程的消息對象,讓主線程做處理;
發佈了7 篇原創文章 · 獲贊 7 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章