1. 程式人生 > 其它 >android 二維碼解析原理_Android事件迴圈原理解析1

android 二維碼解析原理_Android事件迴圈原理解析1

技術標籤:android 二維碼解析原理kettle迴圈傳遞變數xsd 節點可迴圈

本文基於aosp master 分支最新原始碼說明(什麼意思?就是文章發出時的最新原始碼)

閱讀前提:

- 知道Android UI執行緒的作用(以下簡稱UI執行緒/主執行緒)

-知道如何使用android.os.Handler物件通知UI執行緒

- 知道簡單的資料結構,例如 連結串列,順序表等等

為什麼需要事件迴圈?

當用戶觸發一個事件時,應用程式根據程式設計師的編碼設計會執行某些操作來響應使用者觸發的事件. 比如使用者點選一個按鈕後,會觸發點選事件,執行的操作是將文字框顯示的數字增加到某個值,或者使用者點選這個按鈕會導致應用程式從網路下載某個檔案到本地, 或者手機的無線訊號斷開了,手機作業系統識別/探測到這個網路變化並將這個變化轉化為訊息通知到使用者的應用程式. 以上的例子中,如果我們的程式中沒有一個不斷工作的處理從別處傳遞而來的訊息的工人,我們的程式就不可能去響應這些操作,為了響應這些從其他地方傳遞而來的訊息,並且使這些訊息能夠及時的處理,我們需要有個勤奮努力的工人(執行緒

),它專門負責接受處理訊息並且它需要足夠快的處理訊息,使得我們連續傳遞訊息給這個工人的過程中能夠不間斷的獲得它的反饋,這種時時刻刻都能獲得反饋的結果讓我們判斷出這個工人/應用程式是響應式, 下面將會使用執行緒 這個更加專業的術語來代指工人並描述其工作過程.

持續工作的工人與隨時響應事件的應用

一個工人首先必須得是合格的,意味著傳送給這個工人的訊息都能夠被這個工人依次執行不會錯亂順序或者遺失訊息,同時一個優秀的工人能夠及時的響應每一條訊息,讓它的客戶能夠及時的得到反饋,還有一個聰明的工人能夠以最少的資源來完成前述的工作過程,不浪費是非常好的品質.此三點是Android實現訊息迴圈進而實現可響應應用程式的基礎.即不斷工作的執行緒 能夠依次不亂序不丟失的且能夠及時的處理每一條訊息,而且這個執行緒使用最少的cpu資源來執行這些操作,最少的cpu資源是什麼意思?我們考慮這樣兩種不同的實現,第一種 執行緒迴圈的的處理訊息,如果沒有訊息了那麼它依然持續輪訓訊息源,直到有訊息來了繼續處理,第二種 執行緒迴圈處理訊息,如果沒有訊息了,它就自己掛起,停止對cpu的佔用,直到有訊息到來重新進入執行. 很明顯

相對來說第二種方式 是資源更優的,因為它節約的cpu的利用,在掛起期間cpu會被其他的程式使用. 前面所說的最少的資源其意義就在這裡,更少的cpu佔用也意味著更少的能量消耗,更加節約電池續航. 同時第二種模式也保障的訊息的及時響應,執行緒迴圈就保障的只要有訊息就會立刻處理, 那麼如何保障依次執行不會亂序或者遺失訊息, 以及外部是如何將訊息投送到迴圈執行緒了,聰明的你一定能想到 需要一個儲存點來快取這些訊息, 迴圈工作執行緒也從這個儲存點不斷的讀取訊息來處理,考慮的依次執行的特點,這個儲存點的邏輯結構肯定是有序的,同時外部能夠方便的隨時及時的插入訊息, 那麼插入操作的時間複雜度就不能特別高,最好是常量時間, 回想我們在資料結構課上學習的一種結構正好符合這種場景,對就是 有序連結串列

, 所以總結下我們思考出來的方案, 外部 通過將訊息投入到一個有序連結串列, 工作執行緒不斷的從有序連結串列提取順序最前的訊息來處理, 一旦有序連結串列處理完畢也就是它變空了, 工作/迴圈執行緒就掛起等待外部重新填入訊息到有序連結串列中工作執行緒再恢復執行. 有序連結串列本身也正好保障了”依次執行不會錯亂順序或者遺失訊息“這個特質.

Android的訊息迴圈實現

有了前面的理論基礎, 接下來我們看看android是如何按照理論實現android版本的訊息迴圈執行緒的, 首先我們看看Android如何將一個普通的執行緒轉變訊息迴圈執行緒

class LooperThread extends Thread {publicHandlermHandler;publicvoidrun(){Looper.prepare();  // 1mHandler=newHandler(){publicvoidhandleMessage(Messagemsg){//處理髮送而來的訊息 msg}};// 2Looper.loop();//3}}

將一個執行緒轉變成迴圈執行緒,在android中是非常方便的,不需要我們自己去實現執行緒迴圈,有序連結串列,執行緒的掛起與恢復過程, android已經提供系統庫來幫助我們快速實現這個過程, 上述程式碼相信讀者都看了無數次了, 重點就三行程式碼 我已經編號為1 ,2, 3了. 首先是配置階段, 然後是 建立一個外部用以通知迴圈執行緒的物件mHandler, 最後是開始迴圈 呼叫loop(). 接下來我們深入這三行程式碼

Looper.prepare()揭祕

// sThreadLocal.get() will return null unless you've called prepare()[email protected]finalThreadLocalsThreadLocal=newThreadLocal();/** Initialize the current thread as a looper.  * This gives you a chance to create handlers that then reference  * this looper, before actually starting the loop. Be sure to call  * {@link #loop()} after calling this method, and end it by calling  * {@link #quit()}.  */public static void prepare() {    prepare(true);}private static void prepare(boolean quitAllowed) {        if (sThreadLocal.get() != null) {            throw new RuntimeException("Only one Looper may be created per thread");        }        sThreadLocal.set(new Looper(quitAllowed));}

我們可以看到 靜態方法prepare() 呼叫 私有靜態方法prepare(boolean), 而這個方法工作內容很簡單,就是判斷 一個靜態ThreadLocal變數是否已經儲存過一個

Looper物件,如果已經儲存了就丟擲執行時異常, 否則就建立一個Looper例項並新增到ThreadLocal中,關於ThreadLocal 這裡簡單的解釋下, ThreadLocal的作用就和它的名稱所體現的一樣,是一個用來儲存執行緒本地變數的容器,它的方法

get()和set() 會為當前呼叫這兩個方法的執行緒儲存專屬於這個執行緒的變數,比如

這裡的作用就是會呼叫set()方法的執行緒儲存一個只有該執行緒可以獲取的Looper例項, 通過這段程式碼,保障了 每一個執行緒最多持有(通過ThreadLocal持有)只有一個Looper, 如果多次prepare呼叫會導致丟擲異常.

接下來我們繼續深入看看new Looper(true) 做了什麼, 提醒大家牢記文章最開頭的理論過程.

    private Looper(boolean quitAllowed) {        mQueue = new MessageQueue(quitAllowed);        mThread = Thread.currentThread();    }

注意 這裡quitAllowed 的值是true, 看看例項化Looper究竟做了什麼特別的事情呢? 我們看到它只做了兩件事情,第一 建立了一個MessageQueue, 然後將當前執行緒引用給持有下來, 分別作為成員變數 mQueue與mThread, 我們在前面的理論論述中應說明了需要一個儲存結構來儲存待處理的訊息, 這裡很明顯就是這個MessageQueue 物件,我們可以預測這個MessageQueue就是我們前面所說的有序連結串列用來接收快取外部傳遞的訊息,同時Looper物件會從其中提取訊息來處理,也就是說這個MessageQueue物件至少會提供兩組功能, 一組是放入訊息,另一組是提取訊息.

我們來仔細看看MessageQueue是如何實現我們的有序連結串列的

// True if the message queue can be [email protected]finalbooleanmQuitAllowed;@[email protected]("unused")private long mPtr; // used by native codeprivate native static long nativeInit();@UnsupportedAppUsageMessagemMessages;MessageQueue(boolean quitAllowed) {        mQuitAllowed = quitAllowed;        mPtr = nativeInit();}

看了上面的程式碼後,可能會有些讓人搞不清楚,連結串列在哪裡呢? 構造方法裡操作也很簡單,首先把傳遞來的true存起來,然後呼叫jni方法獲取一個native層的long值, 這個long值有什麼作用我們在後面討論,我們先聚焦有序連結串列究竟是如何實現的,上下看了個遍,敏感的你可能已經發現了mMessages成員變數就是實現有序連結串列的關鍵點, 這裡我們還要回想基礎知識, 還記得如何實現一個有序連結串列嗎?首先需要一個表頭, 然後必須有節點結構, 節點儲存了節點本身的值,以及指向下一節點的指標,思路清晰了吧, 所以這裡的mMessages實際上就是表頭, 而這個表頭的物件型別Message就是連結串列的節點型別,Message內部必然有至少兩個屬性,一個是Message本身的值,另一個就是指向下一個Message的指標.

我們這就看看Message原始碼

public final class Message implements Parcelable {  public int what;  public int arg1;  public Object obj;  public Messenger replyTo;  int flags;  /**     * The targeted delivery time of this message. The time-base is     * {@link SystemClock#uptimeMillis}.     * @hide Only for use within the tests.     */    @UnsupportedAppUsage    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)    public long when;   /*package*/ Bundle data;    @UnsupportedAppUsage    /*package*/ Handler target;    @UnsupportedAppUsage    /*package*/ Runnable callback;    // sometimes we store linked lists of these things    @UnsupportedAppUsage    /*package*/ Message next;  private static Message sPool;  public static final Object sPoolSync = new Object();  private static int sPoolSize = 0;  private static final int MAX_POOL_SIZE = 50; public Message() {} }  

看了Message物件突然整個人就炸了fa0213361afe25bcb1fe683ff0a7c955.pngfdebec52c5894dd44bc3fc0913f79f84.png不是說好的兩個屬性嗎? 人與人之間的信任呢cf98a2bf092c78d1c2f79deaa3d283af.png?

各位老闆且慢急躁.

我們細細看,前面我們說到有序連結串列的節點有兩個屬性,一個是值,另一個是下一節點,實際上我們在程式碼裡可以直接找到的就是 next屬性,它就是下一節點的指標, 那麼值是什麼呢?其實值不一定只有一個欄位屬性,有時候業務需求增加會導致需要多個欄位來表徵值,典型的就是有序連結串列必須有一個欄位來表示順序,事件迴圈的訊息要進行排序依據什麼,當然是訊息的時間了,什麼時間呢? 當然是訊息應該被處理的時間,這個時間就是Message物件public long when 這個欄位了, 所有的訊息按照when的大小(也就是訊息發生的先後順序)在連結串列中從前到後排列,when大的就排隊在連結串列後面,when小的就排隊在前面,迴圈提取Message總是提取連結串列中的when值最小的訊息優先處理.初此外的其他成員變數都是為了滿足android特定業務而增加的值欄位, 典型的就是what,arg1, arg2,object, callback,他們分別用於不同場景下持有待處理的資料,可能只需要用到其中一個欄位,也可能需要多個欄位配合,之所以這麼多屬性來表示節點值 完全是因為android訊息迴圈的使用場景比較豐富,需要這些欄位來配合完成這些場景的訊息值的儲存.

這裡提一下sPool這個靜態變數, 我們前面我們已經說了 好的工人 用最少的資源完成工作, 這個sPool作用就在這裡,外部使用Message物件為載體將訊息傳遞給MessageQueue,如果不做特別處理,外部自己例項化Message物件,那麼有多少訊息就會建立多少Message物件,而Android中Message物件的傳遞是貫穿應用程整個生命週期的,如此多數量的物件建立,處理完後就丟棄會造成大量的GC,影響程式的記憶體使用,甚至可能消耗玩應用可使用的記憶體導致OOM異常

於是這裡就採取了物件池的思想,同樣用一個連結串列來快取處理完畢的Message物件, 同時提供了obtain 方法來獲取快取池中的物件,以及recycle方法來回收使用完畢的Message物件,這裡不再深入研究,我們把注意力集中在訊息迴圈機制上.

----

這裡我們總結一下:

訊息迴圈需要一個有序連結串列作為溝通外部與訊息迴圈執行緒的橋樑,MessageQueue實際就表示了這個有序連結串列,其中的mMessages成員就是有序連結串列的表頭,其型別是Message,表頭Message內有多個值屬性共同完成不同場景下的訊息內容的儲存, 同時有一個when欄位表示訊息應該被處理的時間,這個欄位也是有序連結串列的順序排序的依據, 還有一個next欄位指向了下一個節點,同樣是Message物件.

各位老闆 今天就到這裡 我們下期再見.