Android之Handler

我叫阿狸貓發表於2014-02-09

一、基礎知識:

  1. Android 的執行緒分兩種:主執行緒(又稱:UI執行緒)和子執行緒(又稱工作執行緒,即普通的new Thread)。
  2. 主執行緒由應用程式啟動時自動建立,使用者使用app進行介面互動、獲取操作結果,就是基於UI執行緒。如:activity跳轉、設定TextView文字、showToast等。而子執行緒則通過new Thread建立。
  3. UI執行緒執行程式碼效率越高,介面響應越快,使用者感覺就越流暢。為了更好的使用者體驗,Android系統中的耗時操作(包括耗效能、複雜數學運算、大檔案下載等)我們一般都在子執行緒中執行,從而避免影響使用者點選或者佈局渲染等處理。在Android4.4版本之後,如果直接在主執行緒進行網路請求,系統會丟擲NetWrokOnMainThreadException異常,以此要求我們使用子執行緒處理。主執行緒應做到專執行緒專用:只處理UI相關操作,否則程式會報ANR錯誤。
  4. 同步和非同步的區別:
  • 同步:在UI執行緒下載圖片,下載成功後,在UI執行緒更新圖片
  • 非同步:在UI執行緒new一個子執行緒,在子執行緒執行下載操作,成功後,子執行緒發通知告訴UI執行緒,UI執行緒顯示下載圖片。

    上面提到:子執行緒作用是執行比較耗時的操作,如聯網下載資料等,但Android子執行緒是不安全的,只能在主執行緒中更新UI。相信聰明的你看到這裡,一定能夠體會“非同步”的作用了!那“子執行緒發通知告訴UI執行緒”是怎麼實現呢?這就要提到handler了。

     

二、Handler的作用:

在子執行緒傳送訊息(Message),通知建立它的執行緒(通常是UI執行緒)處理資料結果。

 

 

三、如何使用Handler:

public class MainActivity extends Activity {

    private static final int MAKE_TOAST = 0x01;

    // handler建構函式為空,等同於new Handler(getMainLooper()),與Activity所在的UI執行緒關聯
    private Handler mHandler = new Handler(){
              @Override
              public void handleMessage(Message msg) {
                        switch (msg.what){
                               case MAKE_TOAST:
                                     Toast.makeText(MainActivity.this, "hello", Toast.LENGTH_SHORT).show();
                                     break;
               }
           }
    };

             @Override
             protected void onCreate(Bundle savedInstanceState) {
                       super.onCreate(savedInstanceState);
                       setContentView(R.layout.activity_main);

                       HttpUtils.doGet("https://www.baidu.com/", new Callback() {
                               @Override
                                public void onFailure(Call call, IOException e) {

                                }

                               @Override
                               public void onResponse(Call call, Response response) throws IOException {
                                              mHandler.sendEmptyMessageDelayed(MAKE_TOAST,200); // handler傳送訊息
                               }
                        });
            }
}


 

四、開發者容易犯的錯誤:

獲取資料後更新UI的問題,比如:在onClick事件(執行在UI執行緒)中,執行以下程式碼(doGet方法執行了new Thread,建立了子執行緒),在獲取網路資訊後,需要彈出一個Toast

HttpUtils.doGet("https://www.baidu.com/", new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {

    }

    @Override
    public void onResponse(Call call, Response response) throws IOException {
        Toast.makeText(OkHttpActivity.this,"",Toast.LENGTH_SHORT).show();
    }
});

/*例子2,執行時報java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()異常*/
new Thread(new Runnable(){

                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++) {
                        i++;
                    }
                    Handler handler=new Handler(); //Handler放到當前新執行緒中
                    handler.sendEmptyMessage(3);
                }
            }).start();

直接在在新的執行緒中建立Handler和傳送訊息,這是不允許的

這是因為Android規定了只允許UI執行緒修改Activity裡的UI元件,而我們剛才的操作在子執行緒中修改Activity裡的UI元件,才會導致UI操作的執行緒不安全,並報出錯誤。

 

 

但細心的朋友會問:前面 “三、Handler常見用法” 示例中,為什麼不會報錯,在子執行緒用就出錯了?
奧妙在於:
Android系統會自動為
主執行緒建立Looper物件,開啟訊息迴圈,
但子執行緒預設沒有開啟訊息迴圈。

如果子執行緒要使用handler,應該手動開啟訊息迴圈。

Looper物件通過MessageQueue來存放訊息和事件。
一個執行緒只能有一個Looper,對應一個MessageQueue。
然後通過Looper.loop() 讓Looper開始工作,從訊息佇列裡取訊息,處理訊息。
注意:
寫在Looper.loop()之後的程式碼不會被執行,這個函式內部應該是一個迴圈,當呼叫mHandler.getLooper().quit()後,loop才會中止,其後的程式碼才能得以執行。

 

 

五、Handler工作原理

一)Android訊息機制的相關概念

  • 主執行緒(UI執行緒) 
    定義:當程式第一次啟動時,Android會同時啟動一條主執行緒(Main Thread)
    作用:主執行緒主要負責處理與UI相關的事件
     
  • Message(訊息) 
    定義:Handler接收和處理的訊息物件(Bean物件)
    作用:通訊時相關資訊的存放和傳遞
     
  • ThreadLocal 
    定義:執行緒內部的資料儲存類
    作用:負責儲存和獲取本執行緒的Looper
     
  • Message Queue(訊息佇列) 
    定義:採用單連結串列的資料結構來儲存訊息列表
    作用:用來存放通過Handler發過來的Message,按照先進先出執行
     
  • Handler(處理者) 
    定義:Message的主要處理者
    作用:負責傳送Message到訊息佇列&處理Looper分派過來的Message
     
  • Looper(迴圈器) 
    定義:扮演Message Queue和Handler之間橋樑的角色
    作用: 
               訊息迴圈:迴圈取出Message Queue的Message 
               訊息派發:將取出的Message交付給相應的Handler

二)圖片解讀它們之間的關係


 

三)文字解讀它們之間的關係

Looper中存放有MessageQueen,MessageQueen中又有很多Message,當我們的Handler傳送訊息的時候,會獲取當前的Looper,並在當前的Looper的MessageQueen當中存放我們傳送的訊息,而我們的MessageQueen也會在Looper的帶動下,一直迴圈的讀取Message資訊,並將Message資訊傳送給Handler,並執行HandlerMessage()方法

其實這是一個迴圈的過程,讀懂這句話和看懂圖解很重要,會給我們下面的原始碼分析帶來很大的幫助,所以建議大家先讀懂前面的內容。


Android訊息機制的通訊流程

原始碼分析請參考後面“Looper原始碼分析”。

 


六、子執行緒之間使用Handler

package lib.com.myapplication;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
 
public class MainActivity extends AppCompatActivity {
 

    // 注意:這兩個雖然屬於Activity變數,但它們是在子執行緒建立的,
    // 建立時呼叫new Handler()建構函式取“所線上程的looper”,
    // 即等效於 new Hanlder(子執行緒.looper)

    private Handler handler1 ;  
    private Handler handler2 ;

 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new MyThread1().start();
        new MyThread2().start();
    }
 
    class MyThread1 extends Thread { 
        @Override
        public void run() {
            super.run();

           // 為當前執行緒建立looper
            Looper.prepare();
 

             // 關聯Thread1 的 looper
            handler1 = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    System.out.println( "threadName--" + Thread.currentThread().getName() + "messageWhat-"+ msg.what );
                }
            };
 
            try {
                sleep( 3000 );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
           
handler2.sendEmptyMessage( 2 ) ; // 傳送訊息給子執行緒2
 
            Looper.loop();
// 讓Looper迴圈取訊息,傳送給handler,讓handler處理
        }
    }
 
    class MyThread2 extends Thread {
        @Override
        public void run() {
            super.run();
            Looper.prepare();
 

             // 關聯Thread2 的 looper
            handler2 = new Handler(){
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    System.out.println( "threadName--" + Thread.currentThread().getName() + "messageWhat-"+ msg.what );
                }
            };
 
            try {
                sleep( 4000 );
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
           
handler1.sendEmptyMessage( 5 ) ;
 
            Looper.loop();
        }
    }
}

注意:
1、呼叫Looper類的 prepare() 方法可以為當前執行緒建立一個訊息迴圈,呼叫loop() 方法使之處理資訊,直到迴圈結束。
2、new Handler時,有幾個建構函式可選,如果不選擇Looper類物件引數,會獲取當前執行緒的Looper物件,即將當前執行緒的訊息迴圈作為Handler關聯的訊息迴圈。

3、訊息處理機制中,訊息存放在一個訊息佇列中,而執行緒圍繞這個佇列進入一個無限迴圈,直到程式退出。如果佇列中有訊息,執行緒就會把訊息取出來,並分發給相應的Handler進行處理;如果佇列中沒有訊息,執行緒就會進入空閒等待狀態,等待下一個訊息的到來。

 

七、HandlerThread介紹

顧名思義,Handler---->Thread,handler是修飾thread的,說明它是一個Thread執行緒類。
HandlerThread是一個內建 Looper 的Thread,讓我們可以直接線上程中使用 Handler 來處理非同步任務。

  • HandlerThread將loop轉到子執行緒中處理,說白了就是將分擔MainLooper的工作量,降低了主執行緒的壓力,使主介面更流暢
  • 開啟一個執行緒起到多個執行緒的作用。處理任務是序列執行,按訊息傳送順序進行處理。
    相比多次使用new Thread(){…}.start()這樣的方式節省系統資源。
    但是由於每一個任務都將以佇列的方式逐個被執行到,一旦佇列中有某個任務執行時間過長,那麼就會導致後續的任務都會被延遲處理。
  • HandlerThread擁有自己的訊息佇列,它不會干擾或阻塞UI執行緒。
  • 通過設定優先順序就可以同步工作順序的執行,而又不影響UI的初始化;
  • 比較適用於單執行緒+非同步佇列的場景,比如IO讀寫操作,耗時不多而且也不會產生較大的阻塞。對於網路IO操作,HandlerThread並不適合,因為它只有一個執行緒,還得排隊一個一個等著。

原始碼不多:

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

    //注意:指定執行緒優先順序,是android.os.Process.xxx 而不是 java.lang.Thread.xxx 的優先順序!
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }

    // 子類需要重寫的方法,在這裡做一些執行前的初始化工作
    protected void onLooperPrepared() {
    }

    //獲取當前執行緒的 Looper
    //如果執行緒不是正常執行的就返回 null
    //如果執行緒啟動後,Looper 還沒建立,就 wait() 等待 建立 Looper 後 notify
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }

        synchronized (this) {
            while (isAlive() && mLooper == null) {    //迴圈等待
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }

    //呼叫 start() 後就會執行的 run()
    @Override
    public void run() {
        mTid = Process.myTid();
        Looper.prepare();            //幫我們建立了 Looper
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();    //Looper 已經建立,喚醒阻塞在獲取 Looper 的執行緒
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();    

        // 執行loop內部方法,開始一直迴圈:訊息佇列,取訊息給handler。不會執行下一句
        // 注意:迴圈執行的是loop方法內部程式碼,而不是run方法
        Looper.loop();        



        // 當外部執行HandlerThread.quit/quitSafely時,才會執行這句。
        mTid = -1;    
    }

    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }

    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }

    public int getThreadId() {
        return mTid;
    }
}

可以看到,
①HandlerThread 本質還是個 Thread,建立後別忘了呼叫 start()
②在 run() 方法中建立了 Looper,呼叫 onLooperPrepared 後開啟了迴圈
③我們要做的就是在子類中重寫 onLooperPrepared,做一些初始化工作
④在建立 HandlerThread 時可以指定優先順序,注意這裡的引數是 Process.XXX 而不是 Thread.XXX

Process.setThreadPriority(int) A Linux priority level, from -20 for highest scheduling priority to 19 for lowest scheduling priority.

可選的值如下:
public static final int THREAD_PRIORITY_DEFAULT = 0;
public static final int THREAD_PRIORITY_LOWEST = 19;
public static final int THREAD_PRIORITY_BACKGROUND = 10;
public static final int THREAD_PRIORITY_FOREGROUND = -2;
public static final int THREAD_PRIORITY_DISPLAY = -4;
public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8;
public static final int THREAD_PRIORITY_AUDIO = -16;

 

Looper.quit和Looper.quitSafely區別:

Looper.quit():自API Level 1就存在,它會把MessageQueue訊息池中所有的 延遲 + 非延遲 訊息全部清空,無論是延遲訊息(延遲訊息是指通過sendMessageDelayed或通過postDelayed等方法傳送的需要延遲執行的訊息)還是非延遲訊息。

Looper.quitSafely():在API Level 18後加入,只會清空所有的延遲訊息,並將所有的非延遲訊息派發出去讓Handler去處理.

兩者共同點:只要呼叫,Looper就不再接收新的訊息。這時候再通過Handler呼叫sendMessage或post等方法傳送訊息時均返回false,表示訊息沒有成功放入訊息佇列MessageQueue中,因為訊息佇列已經退出了。

 

HandlerThread的簡單用法:

//1.初始化,引數為名字,也就是執行緒的名字,後面我們會結合原始碼來看
mHandlerThread = new HandlerThread("WorkThread");
//必須呼叫start方法,因為HandlerThread繼承自Thread來啟動執行緒
mHandlerThread.start();
//初始化Handler,只是傳遞了一個mHandlerThread內部的一個looper
mHandler = new Handler(mHandlerThread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Log.d("WorkThread", (Looper.getMainLooper() == Looper.myLooper()) + "," + msg.what);
    }
};

//2.使用
public void send(View view) {
    new SendThread(mHandler).start();
}

private final class SendThread extends Thread {
    private Handler mHandler;

    SendThread(Handler handler) {
        this.mHandler = handler;
    }

    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 3; i++) {
            mHandler.sendEmptyMessage(0x1);
            SystemClock.sleep(1000);
        }
    }
}

//3.別忘記釋放,停止Looper訊息迴圈,內部還是呼叫了looper的quit,及時釋放防止記憶體洩漏哦!!
mHandlerThread.quit();

 

 

八、防記憶體洩露:This Handler class should be static or leaks might occur

 

我們在handler物件建立的時候卻會報警告:This Handler class should be static or leaks might occur

public class AutoActivity extends Activity {

   // 建立handler時,Looper預設當前UI執行緒關聯
   
Handler handler = new Handler(){
        public void handleMessage(android.os.Message msg) {

        };
   
};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_auto);
    }
}

意思是:Handler類應該為static型別,否則可能會造成記憶體洩漏。為什麼會造成這種情況呢?
原因:從上文可以知道,handler的作用就是從這個訊息佇列中放入和取出訊息。當我們通過handler將一個Message放入訊息佇列時,這個Message就會持有一個handler物件的引用。
Activity在被結束之後,
MessageQueue並不會隨之被結束,如果這個訊息佇列中存在Message,則導致持有handler的引用,但是又由於Activity被結束了,Message無法被處理,從而導致永久持有handler物件,handler永久持有Activity物件,於是發生記憶體洩漏。

為什麼用might occur?
從Activity被結束後,到這個Message被取出來處理之前,這一段時間內這個might用詞非常準確,一方面訊息可能處理完畢,另一方面延遲未處理才會出現這種情況,若不延遲那麼久,可能性不算高。)這個Message會繼續存活,但是這個Message持有handler的引用,而handler在Activity中建立,會持有Activity的引用,因而當Activity結束後,Activity物件並不能夠被gc回收,因而出現記憶體洩漏。
 



上面的程式碼可能不夠清晰,再來一段直白點的:
public class SampleActivity extends Activity {
                 
  private final Handler mHandler = new Handler() {
         @Override
         public void handleMessage(Message msg) {

          }
  }
                 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
                 

        // 延時10分鐘再傳送訊息
       mHandler.postDelayed(new Runnable() {
              @Override
               public void run() {

               }
         }, 60 * 10 * 1000);  
                 
         
finish(); // 關閉當前Activity
    }
}

當Activity被finish()掉,這時activity總該被回收了吧?答案是否定的!因為Message 將存在於訊息佇列中長達10分鐘的時間才會被執行到,這個Message持有一個對Handler的引用,Handler也會持有一個對於外部類(SampleActivity)的隱式引用,這些引用在Message被執行前將一直保持,這樣會保證Activity的上下文不被垃圾回收機制回收,同時也會洩露應用程式的資源(views and resources)。


為什麼為static型別就會解決這個問題呢?

因為在java中所有非靜態的物件都會持有當前類的強引用,而靜態物件則只會持有當前類的弱引用。宣告為靜態後,handler將會持有一個Activity的弱引用,而弱引用會很容易被gc回收,這樣就能解決Activity結束後,gc卻無法回收的情況。

所以解決這個警告就有幾種方法: 
一:將hanlder物件宣告為靜態的物件。 
二:使用靜態內部類,通過WeakReference實現對Activity的弱引用。具體實現看以下程式碼:

public class AutoActivity extends Activity {

    MyHandler handler = new MyHandler(this);

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_auto);
    }

    static class MyHandler extends Handler {
        WeakReference<AutoActivity> mactivity;

        public MyHandler(AutoActivity activity){
            mactivity = new WeakReference<AutoActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);           
            switch (msg.what) {
            case 100:               
                //在這裡面處理msg
                //通過mactivity.get()獲取Activity的引用(即上下文context)
                break;              
            default:
                break;
            }
        }
    }
}

順便說下軟引用和弱引用的區別:

  • 軟引用(SoftReference):當虛擬機器記憶體不足時,將會回收它指向的物件;需要獲取物件時,可以呼叫get方法 
  • 弱引用(WeakReference):隨時可能會被垃圾回收器回收,不一定要等到虛擬機器記憶體不足時才強制回收。要獲取物件時,同樣可以呼叫get方法。 

 

 

二. SoftReference:實現快取機制

例如從網路上獲取圖片,然後將獲取的圖片顯示的同時,通過軟引用快取起來。當下次再去網路上獲取圖片時,首先會檢查要獲取的圖片快取中是否存在,若存在,直接取出來,不需要再去網路上獲取。

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

另附:

Looper原始碼分析

一、根據上面的例子,為什麼Handler可以在主執行緒中直接可以使用呢?

因為主執行緒(UI執行緒)的Looper在應用程式開啟時建立好了,即在ActivityThread.main方法中建立的,該函式為Android應用程式的入口

public static void main(String[] args) {
    ...
    Process.setArgV0("<pre-initialized>");
    //1. 建立訊息迴圈Looper
    Looper.prepareMainLooper();

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

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    ...

    //2. 執行訊息迴圈
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}


Looper中最為重要的兩個方法:

  1. Looper.prepareMainLooper():該方法是Looper物件的初始化
  2. Looper.loop():該方法會迴圈取出Message Queue的Message,將取出的Message交付給相應的Handler(Looper的作用就體現在這裡)

二、Looper.prepareMainLooper()

//在主執行緒中初始化Looper
public static void prepareMainLooper() {
    //在這裡會呼叫prepare(boolean quitAllowed)方法
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

//看下prepare(boolean quitAllowed)方法
private static void prepare(boolean quitAllowed) {
    //判斷sThreadLocal是否為null,否則丟擲異常
    //即Looper.prepare()方法不能被呼叫兩次
    //也就是說,一個執行緒中只能對應一個Looper例項
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //初始化Looper物件設定到ThreadLocal中
    sThreadLocal.set(new Looper(quitAllowed));
}

//看下Looper的構造方法
private Looper(boolean quitAllowed) {
    //建立了一個MessageQueue(訊息佇列)
    //這說明,當建立一個Looper例項時,會自動建立一個與之配對的MessageQueue(訊息佇列)
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

整個Looper的初始化準備工作就完了,這裡做了哪幾件事:

Looper的建立會關聯一個MessageQueen的建立
Looper物件只能被建立一次
Looper物件建立後被存放在sThreadLocal中

三、Looper.loop()

public static void loop() {
    //myLooper()方法作用是返回sThreadLocal儲存的Looper例項,如果me為null,loop()則丟擲異常
    //也就是說loop方法的執行必須在prepare方法之後執行
    //也就是說,訊息迴圈必須要先線上程當中建立Looper例項
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //獲取looper例項中的mQueue(訊息佇列)
    final MessageQueue queue = me.mQueue;

    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    //進入訊息迴圈
    for (;;) {
        //next()方法用於取出訊息佇列裡的訊息
        //如果取出的訊息為空,則執行緒阻塞
        Message msg = queue.next(); 
        if (msg == null) {

            return;
        }

        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        final long traceTag = me.mTraceTag;
        if (traceTag != 0) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        try {
            //訊息派發:把訊息派發給msg的target屬性,然後用dispatchMessage方法去處理
            //Msg的target其實就是handler物件,下面會繼續分析
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }

        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + " to 0x"
                    + Long.toHexString(newIdent) + " while dispatching to "
                    + msg.target.getClass().getName() + " "
                    + msg.callback + " what=" + msg.what);
        }
        //釋放訊息佔據的資源
        msg.recycleUnchecked();
    }
}

整個Looper的迴圈過程就完了,這裡做了哪幾件事:

取出Looper和MessageQueen
進入訊息迴圈,有訊息則分發出去
訊息資源的回收
四、Looper的退出

當然Looper也提供了兩個方法可以退出一個Looper:

quit():quit會直接退出Looper
quitSafety():quitSafety只是設定一個退出標記,然後把訊息佇列中的已有訊息處理完畢後退出Looper
MessageQueen原始碼分析

一、由於MessageQueen是用來存放Message的,那麼是如何儲存Message的呢?

由於Handler使用Post()方法將Message傳遞到MessageQueen中,在MessageQueen中會使用enqueueMessage()方法儲存Message,其實現的方式是通過單連結串列的資料結構來儲存訊息列表

boolean enqueueMessage(Message msg, long when) {
    ...
    synchronized (this) {
        ...
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {    
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; 
            prev.next = msg;
        }

        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
整個進佇列的過程就完了,這裡做了哪幾件事:

首先判斷訊息佇列裡有沒有訊息,沒有的話則將當前插入的訊息作為隊頭,並且這時訊息佇列如果處於等待狀態的話則將其喚醒
若是在中間插入,則根據Message建立的時間進行插入
二、既然MessageQueen存了訊息之後,是如何提供取出來的方法的呢?

我們知道存訊息是Handler存進來的,那麼取訊息就應該是Looper中取了,從Looper的原始碼可以看出,訊息就是在Looper中取出的,其實現是用MessageQueen裡面的next()方法

Message next() {
    ......
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        // nativePollOnce方法在native層,若是nextPollTimeoutMillis為-1,這時候訊息佇列處於等待狀態。   
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;

            if (msg != null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            //按照我們設定的時間取出訊息
            if (msg != null) {
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // 如果訊息佇列中沒有訊息,將nextPollTimeoutMillis設為-1,下次迴圈訊息佇列則處於等待狀態
                nextPollTimeoutMillis = -1;
            }

            //退出訊息佇列,返回null,這時候Looper中的訊息迴圈也會終止。 
            if (mQuitting) {
                dispose();
                return null;
            }
            ......
        }
        .....
    }
}


三、在MessageQueen存訊息的媒介當然是通過Message物件啦,那這個Message物件又是什麼呢?

其實這個Message就是用來儲存Message中各種資訊的Bean物件,從原始碼中可以其屬性,這裡例舉我們常用的幾個

public int what;
public int arg1;
public int arg2;
public Object obj;
public Messenger replyTo;
int flags;
long when;   
Bundle data;    
Handler target;
Runnable callback;



Handler原始碼分析

一、Handler的建立

Handler的建立會關聯一個Looper物件,而Looper物件是關聯著MessageQueen物件,所以在Handler建立時候,取出Looper和MessageQueen

public Handler(Callback callback, boolean async) {
    ...
    //取出Looper
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    //取出Looper中的MessageQueen
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}


前面我們也說過了Looper是存放在ThreadLocal裡面的,可以看到下面的原始碼就知道了

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

整個建立的過程就完了,這裡做了哪幾件事:

取出Looper
取出Looper中的MessageQueen
二、Handler傳送訊息

1、方式一:sendMessage(Message msg)

//從這裡開始
public final boolean sendEmptyMessage(int what)
{
    return sendEmptyMessageDelayed(what, 0);
}

//往下追蹤
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

//往下追蹤
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

//往下追蹤
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    //直接獲取MessageQueue
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

//呼叫sendMessage方法其實最後是呼叫了enqueueMessage方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    //為msg.target賦值為this,也就是把當前的handler作為msg的target屬性
    //如果大家還記得Looper的loop()方法會取出每個msg然後執行msg.target.dispatchMessage(msg)去處理訊息,其實就是派發給相應的Handler
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    //最終呼叫queue的enqueueMessage的方法,也就是說handler發出的訊息,最終會儲存到訊息佇列中去
    return queue.enqueueMessage(msg, uptimeMillis);
}


2、方式二:post(Ruunable r)

public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

其實post()方法最終也會儲存到訊息佇列中去,和上面不同的是它傳進來的一個Runnable物件,執行了getPostMessage()方法,我們往下追蹤

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}


實質上就是將這個Runnable儲存在Message的變數中,這就導致了我們下面處理訊息的時候有兩種不同方案

三、Handler處理訊息

你還記得前面所說Looper中msg.target.dispatchMessage()方法嗎?這個方法就是呼叫Handler的dispatchMessage()

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        //1. post()方法的處理方法
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //2. sendMessage()方法的處理方法
        handleMessage(msg);
    }
}

//1. post()方法的最終處理方法
private static void handleCallback(Message message) {
    message.callback.run();
}

//2. sendMessage()方法的最終處理方法
public void handleMessage(Message msg) {
}

整個處理的過程就完了,這裡做了哪幾件事:

post()方法的處理方法就是將傳進來的Runnable執行run()方法
sendMessage()方法的處理方法就是執行handleMessage()空方法,這也是我們為什麼要在Handler重寫這個方法的原因

 

 


參考:https://blog.csdn.net/qq_30379689/article/details/53394061 

相關文章