Android消息機制

該文章是一個系列文章,是本人在Android開發的漫漫長途上的一點感想和記錄,我會盡可能按照先易後難的順序進行編寫該系列。該系列引用了《Android開發藝術探索》以及《深刻理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相關知識,另外也借鑑了其餘的優質博客,在此向各位大神表示感謝,膜拜!!!另外,本系列文章知識可能須要有必定Android開發基礎和項目經驗的同窗才能更好理解,也就是說該系列文章面向的是Android中高級開發工程師。java


前言

上一篇咱們介紹了LeakCanary工具用來分析內存泄漏以及談了下幾種常見內存泄漏的表現和解決方法。本篇內容咱們來分析Android的消息機制。咱們爲何要介紹Android的消息機制呢,由於Android系統本質上來講就是一個消息驅動的系統。咱們在開發中何時會用到Handler呢,工做年限較長的開發工程師應該對這個Handler很熟悉了,由於在早期的開發中,不管是網絡請求刷新UI仍是子線程耗時任務的通知的應用場景都能看到Handler的身影。如今Handler在咱們的平常開發中少了一些,由於咱們有了RxJava、Retrofit等對Handler進行了很精美的封裝。可是理解Android的消息機制對於理解Android系統的運做包括那些開源框架的原理都有很大幫助。android

關於Android的消息機制網上也有好多文章,我本人也看了好多。可是不只沒有讓我更清晰明瞭,反而讓我陷入更深的迷惑。本篇的目的在於以一種相對更容易理解的方式來解釋。網絡

咱們先來模擬一個場景,在Activity中執行了耗時操做,耗時操做完成以後顯示一個Toast。這種應用場景仍是比較常見的。咱們來模擬代碼。多線程

public class MessageActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_message);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //模擬耗時操做
                    Thread.sleep(3* 60 * 1000);
                    //耗時操做完成以後顯示一個通知
                    Toast.makeText(MessageActivity.this,"test",Toast.LENGTH_SHORT).show();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }).start();
    }
}

咱們來運行上面的代碼。呦呵,崩潰了,咱們查看日誌獲得如下信息。
框架

關於上面的崩潰咱們稍後分析。async

ActivityThread

既然討論Android 消息機制,若是咱們全部的操做都能在一個線程中完成貌似就不須要這個消息處理機制了,,但這又是不現實的,正是由於咱們不能在一個線程中把全部的工做(網絡請求、耗時操做、更新UI)在一個線程中完成,咱們纔有了多線程,多線程的互相協做才造就了咱們這個Android欣欣向榮的世界。由此咱們不得不說到咱們Android App中的主線程(UI)線程,關於這個線程的叫法有不少。讀者只須要知道不能在這個線程以外的線程直接對UI進行操做就好了。Android 4.0 以上甚至不能在主線程中(UI線程)中進行網絡操做。不然的話會報android.os.NetworkOnMainThreadException,這個錯誤你們應該都見過把。那咱們就從這個主線程(UI線程提及)ide

public static void main(String[] args) {
    ......
    //1 建立Looper 和 MessageQueue,原本該線程也是一個普通的線程,可是建立了Looper以及結合後文的Looper.loop()方法,使這個線程成爲了Looper線程(讀者能夠簡單的理解爲擁有Looper的線程,而這個Looper就是Android消息處理機制的一部分)。
    Looper.prepareMainLooper();

      //2 創建與AMS的通訊
    ActivityThread thread = new ActivityThread();
    thread.attach(false);


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

       
    ......
    //3 無限循環
    Looper.loop();
    //能夠看出來主線程也是在無限的循環的,異常退出循環的時候會報錯. 
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

1 建立Looper 和 MessageQueue

public final class Looper {
    
     ......
    public static void prepare() {
        prepare(true);
    }
    
    //prepare 函數 
    private static void prepare(boolean quitAllowed) {
        //判斷sThreadLocal.get()是否爲空,若是不爲空說明已經爲該線程設置了Looper,不能重複設置。
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        //若是sThreadLocal.get()爲空,說明尚未爲該線程設置Looper,那麼建立Looper並設置
        sThreadLocal.set(new Looper(quitAllowed));
    }

       //ActivityThread 調用Looper.prepareMainLooper();該函數調用prepare(false);
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

 
    public static Looper getMainLooper() {
        synchronized (Looper.class) {
            return sMainLooper;
        }
    }


    

    ......
   
}

在這裏呢有個靜態變量sThreadLocal,它的定義以下函數

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

那麼咱們就得來說解ThreadLocal這個類:工具

線程本地存儲區(Thread Local Storage,簡稱爲TLS),每一個線程都有本身的私有的本地存儲區域,不一樣線程之間彼此不能訪問對方的TLS區域。這裏線程本身的本地存儲區域存放是線程本身的Looper。簡單的來講就是經過ThreadLocal來進行Looper的統一存儲和讀取。那麼接着來看被ThreadLocal存儲的對象Looper的構造函數。oop

//Looper的構造函數
private Looper(boolean quitAllowed) {
    //這裏建立了MessageQueue
    mQueue = new MessageQueue(quitAllowed);
    
    mThread = Thread.currentThread();
}

這裏建立了MessageQueue爲後續的步驟作準備,MessageQueue能夠簡單理解爲一個「隊列」(其底層其實是一個單向鏈表),之因此是打上引號的「隊列」,是由於其並非嚴格意義上的隊列,而是一個單項鍊表,使用者能夠根據節點的優先級等等插入該鏈表。鏈表上的節點就是Message。第①步 整個的結構圖以下所示

2 創建與AMS的通訊

關於這一部份內容必須得對Android Binder知識有相關了解才能更好的理解。咱們下一篇就會講解Android Binder,到時候咱們在回來這裏。

3 無限循環

在上面的工做中咱們已經準備好Looper和MessageQueue,下面就有了兩個問題,① Message從何而來,② Message如何處理。

Message

咱們在討論Message的來源以及如何處理以前,先來看一下Message類

public class Message{
    //消息碼
    public int what;
    //handler
    Handler target;
    //下一級節點
    Message next;
    //消息發送的時間
    long when;

}

上面的代碼也從側面證實了咱們的MessageQueue是一個由Message組成的單向鏈表

咱們先來看Message如何處理,至於爲何,固然是保證由於咱們的思路不被打斷,咱們先分析ActivityThread的最後Looper.loop()函數作了什麼。

② Message如何處理

咱們來到了ActivityThread的最後一步Looper.loop()
ActivityThread.java

public static void loop() {
    //獲得Looper
    final Looper me = myLooper();
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    //獲得MessageQueue
    final MessageQueue queue = me.mQueue;

    ......
    for (;;) {//無限循環
        Message msg = queue.next(); // 取下一個Message 可能阻塞在這裏
        if (msg == null) {
            //若是隊列爲空直接return直接結束了該方法,即循環結束
            return;
        }
        ......
       
        
        try {
            //分發message到指定的target handler
            msg.target.dispatchMessage(msg);
            ......
        } finally {
           
        }
        
        ......
    }
}

主線程由此進入無限循環等待消息,有人看到這裏就由疑問了,執行到for循環時,不就「卡死」在這個無限循環內了嗎?其餘的操做沒法獲得CPU怎麼執行呢?關鍵點就在於queue.next()方法。
爲了更好的理解這個方法咱們先來說一下關於線程阻塞與喚醒的知識

線程阻塞

什麼是阻塞呢?好比某個時候你在等快遞,可是你不知道快遞何時過來,並且你沒有別的事能夠幹(或者說接下來的事要等快遞來了才能作);那麼你能夠去睡覺了,由於你知道快遞把貨送來時必定會給你打個電話(假定必定能叫醒你)。結合咱們上面的代碼。咱們的代碼運行Message msg = queue.next();這一句時,主線程可能一直阻塞在這裏等待消息的到來(它去睡覺去了,也就是說咱們的主線程,竟然是大部分時間都在睡覺,心真大啊)。

注:線程阻塞跟線程忙循環輪詢是有本質區別的,不要聽到線程阻塞就覺得是CPU一直在無限循環輪詢狀態啊。線程阻塞是不佔用CPU資源的,可是線程忙循環輪詢就不同了,將幾乎佔滿CPU資源。什麼是CPU資源,簡單的來講CPU資源就是分配給程序的執行時間。

線程喚醒

要想把主線程活動起來通常有兩種方式:一種是系統喚醒主線程,而且將點擊事件傳遞給主線程;第二種是其餘線程使用Handler向MessageQueue中存放了一條消息,致使loop被喚醒繼續執行。在下面的Message從何而來中咱們這裏使用了hander向MessageQueue中存放了一條消息,致使loop被喚醒繼續執行。

① Message從何而來

public class MessageActivity extends AppCompatActivity {

    private Handler mHandler= new Handler(){
        //處理消息
        @Override
        public void handleMessage(Message msg) {
            handleMsg(msg);
        }

    };
    private void handleMsg(Message msg) {
        switch (msg.what){
            case 0:
                Toast.makeText(this,"成功",Toast.LENGTH_SHORT).show();
                break;
        }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_message);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //模擬耗時操做
                    Thread.sleep(3*1000);
                    //發送消息
                    mHandler.sendEmptyMessage(0);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }).start();
    }
}

咱們常用上面的代碼來作耗時操做,那麼這裏這裏咱們的豬腳就出場了,mHandler是Handler的對象。咱們來看一下Handler類

public class Handler { 
    //handler類有個Looper
     final Looper mLooper;
    //handler類有個MessageQueue
    final MessageQueue mQueue;
    //handler類有個Callback
    final Callback mCallback;
    

     public Handler() {//咱們使用的是這一個
        this(null, false);
    }


    public Handler(Callback callback) {
        this(callback, false);
    }

   
    public Handler(Looper looper) {
        this(looper, null, false);
    }

  
    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }

    public Handler(boolean async) {
        this(null, async);
    }

  
    public Handler(Callback callback, boolean async) {
        //這裏獲取主線程的Looper,Handler的mLooper指向ThreadLocal內的Looper對象
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
           //這裏獲取主線程的Looper的MessageQueue,Handler的mQueue指向ThreadLocal內Looper對象內的MessageQueue對象
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

  
    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

    

}

建立Handler 以後就調用 mHandler.sendEmptyMessage(0);發送消息(Handler的發送消息的方式有好多種,但這不是咱們的重點),最終調用到Handler enqueueMessage 方法
Handler.java

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    設置msg.target 爲當前Handler對象
    msg.target = this;
       ......
    //調用MessageQueue的enqueueMessage()方法
    return queue.enqueueMessage(msg, uptimeMillis);
}

咱們再來看一下MessageQueue的enqueueMessage()
MessageQueue.java

boolean enqueueMessage(Message msg, long when) {
      ......

    synchronized (this) {
       ......
        
        msg.when = when;
        Message p = mMessages;
        //檢測當前頭指針是否爲空(隊列爲空)或者沒有設置when 或者設置的when比頭指針的when要前
        if (p == null || when == 0 || when < p.when) {
            //插入隊列頭部,而且喚醒線程處理msg
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
           // 幾種狀況要喚醒線程處理消息:1)隊列是堵塞的 2)barrier,頭部結點無target 3)當前msg是堵塞的
            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; // 將當前msg插入第一個比其when值大的結點前。
            prev.next = msg;
        }

        //調用Native方法進行底層操做,在這裏把那個沉睡的主線程喚醒
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

咱們的Handler在發送消息的時候把自身設置給了msg.target,發送消息並喚醒Looper,Looper被喚醒後便使用queue.next()取出Message,並根據msg.target進行派發。Handler總體過程以下圖

咱們再稍微看下Handler的dispatchMessage方法
Handler.java

public void dispatchMessage(Message msg) {
    
    if (msg.callback != null) {//判斷有沒有爲Message設置callback(這裏的callback是個Runnable接口,咱們在爲Message設置callback的時候須要本身實現run方法),若是設置了,那麼調用Runnable實例的run方法
        handleCallback(msg);
    } else {
        if (mCallback != null) {//判斷Handler的mCallback是否爲空(這裏的Handler是個Callback接口,咱們在爲Handler設置mCallback的時候須要本身實現handleMessage方法),若是設置了,那麼調用Callback實例的handleMessage方法
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //調用handleMessage方法
        handleMessage(msg);
    }
}

咱們在建立Handler使用的是沒法構造函數,並重寫了handleMessage方法,因此咱們的重寫的handleMessage獲得調用,彈出了Toast

本篇總結

本篇比較詳細的介紹了Android的消息機制,不過有一部份內容須要其餘的知識做爲基礎才能更好的理解。不過這不影響咱們分析Android的消息機制的整個流程。咱們在這裏再梳理一下。

1. 主線程準備Looper和MessageQueue

2. 建立一個線程(由於下面咱們進入死循環了,因此在這以前建立一個線程用來處理,這是個Binder線程)

3. 主線程進入無限循環等待並處理消息。(這個消息多是系統自己的消息,也有多是咱們本身的消息。在本例中分析的是咱們本身建立的Handler發送的消息。)

咱們再上個整圖

這裏呢咱們呢是使用Activity的建立做爲分析,由於這是Activity的起點。在註釋第2步中的代碼sendMessage(H.LAUNCH_ACTIVITY, r);與咱們例子中 mHandler.sendEmptyMessage(0);並無什麼大的不一樣。

如今也是揭曉咱們文章開頭的那個崩潰的祕密的時候了,相信讀者也有答案了。沒錯,是由於咱們在非UI線程中更新了UI,致使了異常。緣由是咱們在子線程沒有Looper啊。你能夠作出以下更改就不會有異常了。

public class MessageActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_message);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //模擬耗時操做
                    Thread.sleep(3* 60 * 1000);
                    
                    //在子線程中更新UI以前,先準備一個Looper,與主線程相同
                    if (Looper.myLooper() != null){
                        Looper.prepare();
                    }
                    //耗時操做完成以後顯示一個通知
                    Toast.makeText(MessageActivity.this,"test",Toast.LENGTH_SHORT).show();

                    //無限循環
                    Looper.loop();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }).start();
    }
}

下篇預告

好了,咱們下一篇介紹Android的Binder,Binder是個大工程哈。。


此致,敬禮

相關文章
相關標籤/搜索