Android線程間通訊機制

當android應用程序運行時,一個主線程被建立(也稱做UI線程),此線程主要負責處理UI相關的事件,因爲Android採用UI單線程模型,因此只能在主線程中對UI元素進行操做,若是在非UI線程直接對UI進行了操做,則會報錯,另外,對於運算量較大的操做和IO操做,咱們須要新開線程來處理這些工做,以避免阻塞UI線程,子線程與主線程之間是怎樣進行通訊的呢?此時就要採用消息循環機制(Looper)與Handler進行處理。java

1、基本概念

Looper:每個線程均可以產生一個Looper,用來管理線程的Message,Looper對象會創建一個MessgaeQueue數據結構來存放message。android

Handler:與Looper溝通的對象,能夠push消息或者runnable對象到MessgaeQueue,也能夠從MessageQueue獲得消息。數據結構

查看其構造函數:ide

Handler()函數

Default constructor associates this handler with the queue for the current thread.//如不指定Looper參數則默認利用當前線程的Looper建立oop

Handler(Looper looper)post

Use the provided queue instead of the default one.//使用指定的Looper對象建立Handlerthis

線程A的Handler對象引用能夠傳遞給別的線程,讓別的線程B或C等能送消息來給線程A。spa

線程A的Message Queue裏的消息,只有線程A所屬的對象能夠處理。線程

注意:Android裏沒有global的MessageQueue,不一樣進程(或APK之間)不能經過MessageQueue交換消息。


2、Handler經過Message通訊的基本方式

使用Looper.myLooper能夠取得當前線程的Looper對象。

使用mHandler = new Handler(Looper.myLooper()); 可產生用來處理當前線程的Handler對象。

使用mHandler = new Handler(Looper.getMainLooper()); 可誕生用來處理main線程的Handler對象。

使用Handler傳遞消息對象時將消息封裝到一個Message對象中,Message對象中主要字段以下:

Message對象能夠經過Message類的構造函數得到,但Google推薦使用Message.obtain()方法得到,該方法會從全局的對象池裏返回一個可複用的Messgae實例,API中解釋以下:

Message()

Constructor (but the preferred way to get a Message is to call Message.obtain()).

Handler發出消息時,既能夠指定消息被接受後立刻處理,也能夠指定通過必定時間間隔以後被處理,如sendMessageDelayed(Message msg, long delayMillis),具體請參考API。

Handler消息被髮送出去以後,將由handleMessage(Message msg)方法處理。

注意:在Android裏,新誕生一個線程,並不會自動創建其Message Loop能夠經過調用Looper.prepare()爲該線程創建一個MessageQueue,再調用Looper.loop()進行消息循環。

下面舉例說明:

在main.xml中定義兩個button及一個textview

public class HandlerTestActivity extends Activity implements OnClickListener{

    public TextView tv;
    private myThread myT;
    Button bt1, bt2;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        bt1 = (Button)findViewById(R.id.a);
        bt2 = (Button)findViewById(R.id.b);
        tv = (TextView)findViewById(R.id.tv);
        bt1.setId(1);//爲兩個button設置ID,此ID用於後面判斷是哪一個button被按下
        bt2.setId(2);
        bt1.setOnClickListener(this);//增長監聽器
        bt2.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
       switch(v.getId()){//按鍵事件響應,若是是第一個按鍵將啓動一個新線程
       case 1:
            myT = new myThread();
            myT.start();
            break;
       case 2:
            finish();
            break;
       default:
            break;
       }
    }  

    class myThread extends Thread {
        private EHandler mHandler;
        public void run() {
             Looper myLooper, mainLooper;
             myLooper = Looper.myLooper();//獲得當前線程的Looper
             mainLooper = Looper.getMainLooper();//獲得UI線程的Looper
             String obj;
             if(myLooper == null) { //判斷當前線程是否有消息循環Looper
                  mHandler = new EHandler(mainLooper);
                  obj = "current thread has no looper!";//當前Looper爲空,EHandler用mainLooper對象構造
             } else {
              mHandler = new EHandler(myLooper);//當前Looper不爲空,EHandler用當前線程的Looper對象構造
              obj = "This is from current thread.";
             }

             mHandler.removeMessages(0);//清空消息隊列裏的內容
             Message m = mHandler.obtainMessage(1, 1, 1, obj);
             mHandler.sendMessage(m);//發送消息
           }
    }
    class EHandler extends Handler {

        public EHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) { //消息處理函數
           tv.setText((String)msg.obj);//設置TextView內容
        }
    }
}

程序運行後點擊TestLooper按鍵,TextView輸出以下,說明新建立的線程裏Looper爲空,也就說明了新建立的線程並不會本身創建Message Looper。

修改myThread類:

class myThread extends Thread {

    private EHandler mHandler;

    public void run() {
        Looper myLooper, mainLooper;
        Looper.prepare();
        myLooper = Looper.myLooper();
        mainLooper = Looper.getMainLooper();
        ......
        ......
        mHandler.sendMessage(m);
        Looper.loop();
    }

}

Looper.prepare爲當前線程建立一個Message Looper,Looper.loop()開啓消息循環。這樣修改是OK呢?

答案是否認的!運行時Logcat將拋出CalledFromWrongThreadException異常錯誤,提示以下:

意思就是說「只有原始建立這個視圖層次的線程才能修改它的視圖」,本例中的TextView是在UI線程(主線程)中建立,所以,myThread線程不能修改其顯示內容!

通常的作法是在子線程裏獲取主線程裏的Handler對象,而後經過該對象向主線程的消息隊列裏發送消息,進行通訊。

若是子線程想修改主線程的UI,能夠經過發送Message給主線程的消息隊列,主線程經行判斷處理再對UI經行操做,具體能夠參考以前的代碼。


3、Handler經過runnable通訊的基本方式

咱們能夠經過Handler的post方法實現線程間的通訊,API中關於post方法說明以下

public final boolean post (Runnable r)

Causes the Runnable r to be added to the message queue. The runnable will be run on the thread to which this handler is attached.

Post方法將安排runnable對象在主線程的某個位置運行,可是並不會開啓一個新的線程,驗證代碼以下:

public class HandlerTestActivity extends Activity {

    private Handler handlerTest;

    Runnable runnableTest = new Runnable() {

       public void run() {
           String runID = String.valueOf(Thread.currentThread().getId());//輸出Runnable 線程的ID號
           Log.v("Debug",runID);
       }
    };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        handlerTest = new Handler();
        String mainID = String.valueOf(Thread.currentThread().getId());
        Log.v("Debug",mainID);//輸出主線程的ID號
        handlerTest.post(runnableTest);
    }
}

Logcat裏輸出以下:

說明只是把runnable裏的run方法放到UI線程裏運行,並不會建立新線程

所以咱們能夠在子線程中將runnable加入到主線程的MessageQueue,而後主線程將調用runnable的方法,能夠在此方法中更新主線程UI。

將以前的代碼修改以下:

public class HandlerTestActivity extends Activity {

    private Handler handlerTest;
    private TextView tv;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        tv = (TextView)findViewById(R.id.tv);//TextView初始化爲空
        handlerTest = new Handler();
        myThread myT = new myThread();
        myT.start();//開啓子線程
    }

    class myThread extends Thread{
        public void run(){
            handlerTest.post(runnableTest);//子線程將runnable加入消息隊列
        }
    }

    Runnable runnableTest = new Runnable() {

       public void run() {
           tv.setText("此信息由子線程輸出!");
       }
    };

}

至關於在主線程中調用了runnalbe的run方法,更改了TextView的UI!

相關文章
相關標籤/搜索