淺析Android中的訊息機制

來源:互聯網
上載者:User

在分析Android訊息機制之前,我們先來看一段代碼:

 

[java] view plaincopy

  1. public class MainActivity extends Activity implements View.OnClickListener {  
  2.       
  3.     private TextView stateText;  
  4.     private Button btn;  
  5.       
  6.     @Override  
  7.     public void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.         setContentView(R.layout.main);  
  10.         stateText = (TextView) findViewById(R.id.tv);  
  11.         btn = (Button) findViewById(R.id.btn);  
  12.           
  13.         btn.setOnClickListener(this);  
  14.     }  
  15.   
  16.     @Override  
  17.     public void onClick(View v) {  
  18.         new WorkThread().start();  
  19.     }  
  20.       
  21.     //背景工作執行緒  
  22.     private class WorkThread extends Thread {  
  23.         @Override  
  24.         public void run() {  
  25.             //......處理比較耗時的操作  
  26.               
  27.             //處理完成後改變狀態  
  28.             stateText.setText("completed");  
  29.         }  
  30.     }  
  31. }  

 

這段代碼似乎看上去很正常,但是當你運行時就會發現,它會報一個致命性的異常:

 

[java] view plaincopy

  1. ERROR/AndroidRuntime(421): FATAL EXCEPTION: Thread-8  
  2. ERROR/AndroidRuntime(421): android.view.ViewRoot$CalledFromWrongThreadException:   
  3. Only the original thread that created a view hierarchy can touch its views.  

 

到底是怎麼回事呢?原因在於,Android系統中的視圖組件並不是安全執行緒的,如果要更新視圖,必須在主線程中更新,不可以在子線程中執行更新的操作。

既然這樣,我們就在子線程中通知主線程,讓主線程做更新操作吧。那麼,我們如何通知主線程呢?我們需要使用到Handler對象。

我們稍微修改一下上面的代碼:

 

[java] view plaincopy

  1. public class MainActivity extends Activity implements View.OnClickListener {  
  2.       
  3.     private static final int COMPLETED = 0;  
  4.       
  5.     private TextView stateText;  
  6.     private Button btn;  
  7.       
  8.     private Handler handler = new Handler() {  
  9.         @Override  
  10.         public void handleMessage(Message msg) {  
  11.             if (msg.what == COMPLETED) {  
  12.                 stateText.setText("completed");  
  13.             }  
  14.         }  
  15.     };  
  16.       
  17.     @Override  
  18.     public void onCreate(Bundle savedInstanceState) {  
  19.         super.onCreate(savedInstanceState);  
  20.         setContentView(R.layout.main);  
  21.         stateText = (TextView) findViewById(R.id.tv);  
  22.         btn = (Button) findViewById(R.id.btn);  
  23.           
  24.         btn.setOnClickListener(this);  
  25.     }  
  26.   
  27.     @Override  
  28.     public void onClick(View v) {  
  29.         new WorkThread().start();  
  30.     }  
  31.       
  32.     //背景工作執行緒  
  33.     private class WorkThread extends Thread {  
  34.         @Override  
  35.         public void run() {  
  36.             //......處理比較耗時的操作  
  37.               
  38.             //處理完成後給handler發送訊息  
  39.             Message msg = new Message();  
  40.             msg.what = COMPLETED;  
  41.             handler.sendMessage(msg);  
  42.         }  
  43.     }  
  44. }  

 

通過上面這種方式,我們就可以解決安全執行緒的問題,把複雜的任務處理工作交給子線程去完成,然後子線程通過handler對象告知主線程,由主線程更新視圖,這個過程中訊息機制起著重要的作用。

下面,我們就來分析一下Android中的訊息機制。

熟悉Windows編程的朋友知道Windows程式是訊息驅動的,並且有全域的訊息迴圈系統。Google參考了Windows的訊息迴圈機制,也在Android系統中實現了訊息迴圈機制。Android通過Looper、Handler來實現訊息迴圈機制。Android的訊息迴圈是針對線程的,每個線程都可以有自己的訊息佇列和訊息迴圈。

Android系統中的Looper負責管理線程的訊息佇列和訊息迴圈。通過Looper.myLooper()得到當前線程的Looper對象,通過Looper.getMainLooper()得到當前進程的主線程的Looper對象。

前面提到,Android的訊息佇列和訊息迴圈都是針對具體線程的,一個線程可以存在一個訊息佇列和訊息迴圈,特定線程的訊息只能分發給本線程,不能跨線程和跨進程通訊。但是建立的背景工作執行緒預設是沒有訊息佇列和訊息迴圈的,如果想讓背景工作執行緒具有訊息佇列和訊息迴圈,就需要線上程中先調用Looper.prepare()來建立訊息佇列,然後調用Looper.loop()進入訊息迴圈。下面是我們建立的背景工作執行緒:

 

[java] view plaincopy

  1. class WorkThread extends Thread {  
  2.       public Handler mHandler;  
  3.   
  4.       public void run() {  
  5.           Looper.prepare();  
  6.   
  7.           mHandler = new Handler() {  
  8.               public void handleMessage(Message msg) {  
  9.                   // 處理收到的訊息  
  10.               }  
  11.           };  
  12.   
  13.           Looper.loop();  
  14.       }  
  15.   }  

 

這樣一來,我們建立的背景工作執行緒就具有了訊息處理機制了。

那麼,為什麼前邊的樣本中,我們怎麼沒有看到Looper.prepare()和Looper.loop()的調用呢?原因在於,我們的Activity是一個UI線程,運行在主線程中,Android系統會在Activity啟動時為其建立一個訊息佇列和訊息迴圈。

前面提到最多的是訊息佇列(MessageQueue)和訊息迴圈(Looper),但是我們看到每個訊息處理的地方都有Handler的存在,它是做什麼的呢?Handler的作用是把訊息加入特定的Looper所管理的訊息佇列中,並分發和處理該訊息佇列中的訊息。構造Handler的時候可以指定一個Looper對象,如果不指定則利用當前線程的Looper對象建立。下面是Handler的兩個構造方法:

 

[java] view plaincopy

  1. /** 
  2.      * Default constructor associates this handler with the queue for the 
  3.      * current thread. 
  4.      * 
  5.      * If there isn't one, this handler won't be able to receive messages. 
  6.      */  
  7.     public Handler() {  
  8.         if (FIND_POTENTIAL_LEAKS) {  
  9.             final Class<? extends Handler> klass = getClass();  
  10.             if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&  
  11.                     (klass.getModifiers() & Modifier.STATIC) == 0) {  
  12.                 Log.w(TAG, "The following Handler class should be static or leaks might occur: " +  
  13.                     klass.getCanonicalName());  
  14.             }  
  15.         }  
  16.   
  17.         mLooper = Looper.myLooper();  
  18.         if (mLooper == null) {  
  19.             throw new RuntimeException(  
  20.                 "Can't create handler inside thread that has not called Looper.prepare()");  
  21.         }  
  22.         mQueue = mLooper.mQueue;  
  23.         mCallback = null;  
  24.     }  
  25.   
  26. /** 
  27.      * Use the provided queue instead of the default one. 
  28.      */  
  29.     public Handler(Looper looper) {  
  30.         mLooper = looper;  
  31.         mQueue = looper.mQueue;  
  32.         mCallback = null;  
  33.     }  

 

下面是訊息機制中幾個重要成員的關係圖:

一個Activity中可以建立出多個背景工作執行緒,如果這些線程把他們訊息放入Activity主線程的訊息佇列中,那麼訊息就會在主線程中處理了。因為主線程一般負責視圖組件的更新操作,對於不是安全執行緒的視圖組件來說,這種方式能夠很好的實現視圖的更新。

那麼,子線程如何把訊息放入主線程的訊息佇列中呢?只要Handler對象以主線程的Looper建立,那麼當調用Handler的sendMessage方法,系統就會把訊息主線程的訊息佇列,並且將會在調用handleMessage方法時處理主線程訊息佇列中的訊息。

對於子線程訪問主線程的Handler對象,你可能會問,多個子線程都訪問主線程的Handler對象,發送訊息和處理訊息的過程中會不會出現資料的不一致呢?答案是Handler對象不會出現問題,因為Handler對象管理的Looper對象是安全執行緒的,不管是添加訊息到訊息佇列還是從訊息佇列中讀取訊息都是同步保護的,所以不會出現資料不一致現象。

深入理解Android訊息處理機制對於應用程式開發非常重要,也可以讓我們對線程同步有更加深刻的認識,希望這篇文章可以對朋友們有所協助。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.