Android Handler消息機制

在android中提供了一種異步回調機制Handler,使用它,我們可以在完成一個很長時間的任務後做出相應的通知

    handler基本使用:

        在主線程中,使用handler很簡單,new一個Handler對象實現其handleMessage方法,在handleMessage中
提供收到消息後相應的處理方法即可,這裏不對handler使用進行詳細說明,在看本博文前,讀者應該先掌握handler的基本使用,我這裏主要深入描述handler的內部機制

   .現在我們首先就有一個問題,我們使用myThreadHandler.sendEmptyMessage(0);發送一個message對象,那麼Handler是如何接收該message對象並處理的呢?我先畫一個數據結構圖:

   

從這個圖中我們很清楚可以看到調用sendEmptyMessage後,會把Message對象放入一個MessageQueue隊列,該隊列屬於某個Looper對象,每個Looper對象通過ThreadLocal.set(new Looper())跟一個Thread綁定了,Looper對象所屬的線程在Looper.Loop方法中循環執行從MessageQueue隊列讀取Message對象,並把Message對象交由Handler處理,調用Handler的dispatchMessage方法。

     現在我們再來看一下使用Handler的基本實現代碼:

               // 主線程中新建一個handler
                normalHandler = new Handler() {
                        public void handleMessage(android.os.Message msg) {
                                btnSendMsg2NormalHandler.setText("normalHandler");
                                Log.d(Constant.TAG, MessageFormat.format("Thread[{0}]--normalHandler handleMessage run...", Thread.currentThread()
                                                .getName()));
                        }
                };

...
//發送消息到hanlder
myThreadHandler.sendEmptyMessage(0); 

你現在已經很清楚了sendEmptyMessage到handleMessage的過程,途中經過Looper.MessageQueue隊列,轉由Looper所在的線程去處理了,這是一個異步的過程,當然Looper所在的線程也可以是sendEmptyMessage所在的線程。 

     看了上面你也許還是迷惑不解,那麼什麼要Looper了,跟我們要用的Handler又有啥鳥關係呢?

     我在前面一直強調在主線程中使用handler,爲什麼要這麼說呢,因爲你在自己new一個新線程中去像我前面那樣簡單建立一個Handler,程序執行是會報錯的:

    java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
     at android.os.Handler.<init>(Handler.java:121)
     at com.cao.android.demos.handles.HandleTestActivity$MyThread$1.<init>(HandleTestActivity.java:86)
     at com.cao.android.demos.handles.HandleTestActivity$MyThread.run(HandleTestActivity.java:86)

    爲什麼在主線程中不會報錯,而在自己新見的線程中就會報這個錯誤呢?很簡單,因爲主線程它已經建立了Looper,你可以打開ActivityThread的源碼看一下:

    public static final void main(String[] args) {
        SamplingProfilerIntegration.start();

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

       Looper.loop();

        if (Process.supportsProcesses()) {
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }

        thread.detach();
        String name = (thread.mInitialApplication != null)
            ? thread.mInitialApplication.getPackageName()
            : "<unknown>";
        Slog.i(TAG, "Main thread of " + name + " is now exiting");
    }

    在main函數中它已經做了這個事情了,爲什麼要調用 Looper.prepareMainLooper(); Looper.loop();我們可以進去看一下,在prepareMainLooper方法中新建了一個looper對象,並與當前進程進行了綁定,而在Looper.loop方法中,線程建立消息循環機制,循環從MessageQueue獲取Message對象,調用  msg.target.dispatchMessage(msg);進行處理msg.target在myThreadHandler.sendEmptyMessage(0)設置進去的,因爲一個Thead中可以建立多個Hander,通過msg.target保證MessageQueue中的每個msg交由發送message的handler進行處理,那麼Handler又是怎樣與Looper建立聯繫的呢,在Handler構造函數中有這樣一段代碼:

       mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;

在新建Handler時需要設置mLooper成員,Looper.myLooper是從當前線程中獲取綁定的Looper對象:

public static final Looper myLooper() {
        return
(Looper)sThreadLocal.get();
    }

    若Looper對象沒有創建,就會拋異常"Can't create handler inside thread that has not called Looper.prepare()"
這跟我前面講的是一致的。所以我們在一個新線程中要創建一個Handler就需要這樣寫:

        class MyThread extends Thread {

                public void run() {               
                        Log.d(Constant.TAG, MessageFormat.format("Thread[{0}]-- run...", Thread
                                        .currentThread().getName()));
                        // 其它線程中新建一個handler
                        Looper.prepare();//
創建該線程的Looper對象,用於接收消息,在非主線程中是沒有looper的所以在創建handler前一定要使用prepare()創建一個Looper
                        myThreadHandler = new Handler() {
                                public void handleMessage(android.os.Message msg) {
                                        Log.d(Constant.TAG, MessageFormat.format("Thread[{0}]--myThreadHandler handleMessage run...", Thread
                                                        .currentThread().getName()));
                                }
                        };
                      
  Looper.myLooper().loop();//建立一個消息循環,該線程不會退出
                }
        }

   現在,你應該對Handler的機制有所瞭解了吧,若有什麼疑問,歡迎在評論中提出

  在其它線程中Handler使用主線程的Looper

   前面我說了在新線程中要新建一個Handler需要調用Looper.prepare();也有另一種方法就是使用主線程中的Looper,那就不必新建Looper對象了:

                        threadMainLoopHandler =new Handler(Looper.getMainLooper()){
                                public void handleMessage(android.os.Message msg) {
                                        Log.d(Constant.TAG, MessageFormat.format("Thread[{0}]--threadMainLoopHandler handleMessage run...", Thread
                                                        .currentThread().getName()));                                       
                                }
                                //該handleMessage方法將在mainthread中執行
                        };

  這時候注意不要在handleMessage做太多的操作,因爲它在主線程中執行,會影響主線程執行ui更新操作。

    使用Message.callback回調

   public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
從dispatchMessage定義可以看出,如果Message對象自帶callback對象,handler不會執行handleMessage方法而是執行message.callback中定義的run方法,當然callback還是在handler關聯的looper所綁定的線程中執行的。實際上Handler.post(Runnable r)方法就是把r添加到一個msg.callback的,也就是說,下面兩種寫法,沒有什麼區別:

1.使用Message.callback

01.Message msg = Message.obtain(myThreadHandler,new Runnable() {  
02.      
03.    @Override  
04.    public void run() {  
05.        Log.d(Constant.TAG, MessageFormat.format("Thread[{0}]--myThreadHandler.Message.callback.run",  
06.                Thread.currentThread().getName()));   
07.    }  
08.});  
09.myThreadHandler.sendMessage(msg);  

2.使用Handler.post

01.myThreadHandler.post(new Runnable() {  
02.                      
03.                    @Override  
04.                    public void run() {  
05.                        Log.d(Constant.TAG, MessageFormat.format("Thread[{0}]--myThreadHandler.Message.callback.run",  
06.                                Thread.currentThread().getName()));   
07.                    }  
08.                }); 

HandlerThread繼承於Thread,所以它本質就是個Thread。與普通Thread的差別就在於,它有個Looper成員變量。這個Looper其實就是對消息隊列以及隊列處理邏輯的封裝,簡單說就是 消息隊列+消息循環。

當我們需要一個工作者線程,而不是把它當作一次性消耗品,用過即廢棄的話,就可以使用它。

    private Handler mHandler = null;

    private HandlerThread mHandlerThread = null;

    private void sentRunnableToWorker(Runnable run){

        if (null == mHandlerThread)
        {
            mHandlerThread = new HandlerThread("WorkerThread");

            // 給工作者線程低優先級 
            mHandlerThread.setPriority(Thread.MIN_PRIORITY);
            mHandlerThread.start();
        }

        if (null == mHandler)
            mHandler = new Handler(mHandlerThread.getLooper());

        mHandler.post(run);

    }

AsyncQueryHandler就是基於HandlerThread封裝了線程間雙向通信,而HandlerThread只做了一半。

 

 

源碼分析:

 

  1. public class HandlerThread extends Thread{
  2.           //線程的優先級
  3.            private int mPriority;
  4.           //線程的id
  5.            private int mTid=-1;
  6.            private Looper mLooper;
  7.            public HandlerThread(String name){
  8.                       super(name);
  9.                      //設置優先級爲默認線程
  10.                      mPriority=Process.THREAD_PRIORITY_DEFAULT;
  11.            }
  12.            public HandlerThread(String name,int priority){
  13.                       super(name);
  14.                      mPriority=priority;
  15.            }
  16.           //這個如果有需要的話可以繼承重寫,例如可以在裏面聲明個Handler 關聯此線程。
  17.            protected void onLooperPrepared(){}
  18.            //這個很熟悉吧
  19.            public void run(){
  20.                //得到當前線程的id
  21.                 mTid=Process.myTid;
  22.                //一旦調用這句代碼,就在此線程中創建了Looper 對象。這就是爲什麼我們要在調用線程的start()方法後
  23.                //才能得到Looper 對象即 當 調用Looper.myLooper()時不爲Null。
  24.                 Looper.prepare();
  25.                 //同步代碼塊,意思就是當獲得mLooper對象後,喚醒所有線程。(會在以後的例子中有所體現)
  26.                synchronized(this){
  27.                      mLooper=Looper.myLooper();
  28.                      notifyAll();
  29.                }
  30.               //設置線程的優先級
  31.                Process.setThreadPriority(mPriority);
  32.              //調用上面的方法(需要用戶重寫)
  33.               onLooperPrepared();
  34.                //建立了消息循環
  35.                 Looper.loop();
  36.                mTid=-1;
  37.            }
  38.            public Looper getLooper(){
  39.                     //線程死了,那就只有NULL了。
  40.                      if(!isAlive()){
  41.                         return null;
  42.                     }
  43.             //看見沒,又是同步代碼塊,正好和上面的形成對應,就是說只要線程活着並且我的looper爲NULL,那麼我就讓你一直等。。。
  44.                    synchronized(this){
  45.                           while(isAlive()&&mLooper==null){
  46.                                try{
  47.                                          wait();
  48.                                    }catch(InterruptedException e){}
  49.                           }
  50.                    }
  51.                              return mLooper;
  52.            }
  53.           public boolean quit(){
  54.               Looper looper =getLooper();
  55.                if(looper!=null){
  56.                   //退出消息循環
  57.                    looper.quit();
  58.                    return true;
  59.               }
  60.               return false;
  61.        }
  62.          //返回線程ID
  63.          public int getThreadId(){
  64.                return mTid;
  65.         }
  66. }
複製代碼

整體來說代碼還是比較淺顯易懂的。主要的作用是建立了一個線程,並且創立了消息隊列,有來自己的looper,可以讓我們在自己的線程中分發和處理消息。具體的使用示例,我會在下一帖中體現。還有要說明的是handler 與誰相關聯不是看聲明在什麼地方,是看與哪個線程的looper掛鉤。默認是主線程的looper.因爲主線程中默認就有了looper,消息循環隊列。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章