Android消息機制(二):Message和MessageQueue

Message

消息結構

每個消息用Message表示,Message主要包含以下內容:

filed 含義 說明
what 消息類別 由用戶定義,用來區分不同的消息
arg1 參數1 是一種輕量級的傳遞數據的方式
arg2 參數2 是一種輕量級的傳遞數據的方式
obj 消息內容 任意對象,但是使用Messenger跨進程傳遞Message時不能為null
data Bundle數據 比較復雜的數據建議使用該變量(相比上面幾個,這個縣的比較重量級)
target 消息響應方 關聯的Handler對象,處理Message時會調用它分發處理Message對象
when 觸發響應時間 處理消息時間
next Message隊列里的下一個Message對象 用next指向下一條Message,實現一個鏈表數據結構,用戶一般使用不到該字段。

這里的用戶指一般的APP開發者。

一般不用手動設置target,調用Handler.obtainMessage()方法會自動的設置Message的target為當前的Handler。
得到Message之后可以調用sendToTarget(),發送消息給Handler,Handler再把消息放到message queue的尾部。
對Message除了給部分成員變量賦值外的操作都可以交由Handler來處理。

消息池

在通過Handler發送消息時,我們可以通過代碼Message message=new Message();新建一條消息,但是我們并不推薦這樣做,因為這樣每次都會新建一條消息,很容易造成資源浪費。Android中設計了消息池用于避免該現象:

  • 獲取消息 obtain()
    從消息池中獲取消息: Message msg=Message.obtain();
    obtain()方法源碼:
  public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null; //從sPool中取出一個Message對象,并消息鏈表斷開
                m.flags = 0; // clear in-use flag清除in-use flag
                sPoolSize--;//消息池的可用大小進行-1操作
                return m;
            }
        }
        return new Message();// 當消息池為空時,直接創建Message對象
    }

從消息池取Message,都是把消息池表頭的Message取走,再把表頭指向下一條消息next;

  • 回收消息 recycle()
    把不再使用的消息回收到消息池 mgs.recycle();
    recycle()方法源碼:
public void recycle() {
        if (isInUse()) {//判斷消息是否正在使用
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

    /**
     * 對于不再使用的消息,加入到消息池
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        //將消息標示位置為IN_USE,并清空消息所有的參數。
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {//當消息池沒有滿時,將Message對象加入消息池
                next = sPool;
                sPool = this;
                sPoolSize++;//消息池的可用大小進行加1操作
            }
        }
    }

消息回收,就是將Message內容重置后,再把Message加到鏈表的表頭,加入到消息池的過程;

MessageQueue

負責管理消息隊列,實際上Message類有一個next字段,會將Message對象串在一起成為一個消息隊列,所以并不需要LinkedList之類的數據結構將Message對象組在一起成為隊列。

  • 創建消息隊列
    MessageQueue(boolean quitAllowed) {
       mQuitAllowed = quitAllowed;
       mPtr = nativeInit();//通過native方法初始化消息隊列,其中mPtr是供native代碼使用
   }

在MessageQueue初始化的時候調用了nativeInit,這是一個Native方法:

static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) { 
   NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); 
   if (!nativeMessageQueue) { 
       jniThrowRuntimeException(env, "Unable to allocate native queue"); 
       return;
       }
   nativeMessageQueue->incStrong(env); 
   android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue);
} 
static void android_os_MessageQueue_setNativeMessageQueue(JNIEnv* env, jobject messageQueueObj,
       NativeMessageQueue* nativeMessageQueue) { 
   env->SetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr, 
   reinterpret_cast<jint>(nativeMessageQueue)); 
 }

方法名由java層類的包名+類名+方法名組成,這不是標準,是習慣寫法,也可以采用其他名稱組合,具體是什么名稱由JNINativeMethod方法中Java對象與c++對象的映射決定,此處是JNI方面的內容,不作過多解釋。
在nativeInit中,new了一個Native層的MessageQueue的對象,并將其地址保存在了Java層MessageQueue的成員mPtr中,Android中有好多這樣的實現,一個類在Java層與Native層都有實現,通過JNI的GetFieldID與SetIntField把Native層的類的實例地址保存到Java層類的實例的mPtr成員中,比如Parcel。

再看NativeMessageQueue的實現:

NativeMessageQueue::NativeMessageQueue() : mInCallback(false), mExceptionObj(NULL) { 
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
      mLooper = new Looper(false);
      Looper::setForThread(mLooper); 
    }
}
  • 消息入隊 enqueueMessage()
    enqueueMessage 用于將Message對象插入消息隊列。MessageQueue永遠是按照Message觸發的時間先后順序排列的,隊頭的消息是將要最早觸發的消息。當有消息需要加入消息隊列時,會從隊列頭開始遍歷,直到找到消息應該插入的合適位置,以保證所有消息的時間順序。
    該方法會被Handler對象調用。
    源碼如下:
   /**
     * 添加一條消息到消息隊列
     * @param msg 要添加的消息
     * @param when 消息處理時間
     * @return 添加成功與否
     */
    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {// 每一個Message必須有一個target
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                //p為null(代表MessageQueue沒有消息) 或者msg的觸發時間是隊列中最早的, 則進入該該分支
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                //將消息按時間順序插入到MessageQueue。一般地,不需要喚醒事件隊列,除非
                //消息隊頭存在barrier,并且同時Message是隊列中最早的異步消息。

                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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; // invariant: p == prev.next
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            //消息沒有退出,我們認為此時mPtr != 0
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
  • 消息輪詢 next()
    最重要的方法,用于獲取下一個Message對象,如果沒有需要處理的Message對象,該方法將阻塞。MessageQueue用本地方法做同步互斥,因為這樣時間更精準。每個Message對象都有一個什么時刻處理該Message對象的屬性when,沒到時間都不會處理該Message對象,如果時間不精準的話,會導致系統消息不能及時處理。
   /**
     * 依次從MessageQueue中取出Message
     * @return 消息
     */
    Message next() {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {//當消息循環已經退出,則直接返回
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration  循環迭代的首次為-1
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //阻塞操作,當等待nextPollTimeoutMillis時長,或者消息隊列被喚醒,都會返回。
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    //查詢MessageQueue中的下一條異步消息
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        //設置下一次輪詢消息的超時時間
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // 獲取一條消息,并返回
                        // Got a message.
                        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();//設置消息flag成使用狀態
                        return msg;//成功地獲取MessageQueue中的下一條即將要執行的消息
                    }
                } else {
                    // No more messages.//沒有消息了
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {//消息正在退出,返回null
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                //當消息隊列為空,或者消息隊列的第一個消息時
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    //沒有idle handlers 需要運行,則循環并等待。
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            //只有第一次循環時,會運行idle handlers,執行完成后,重置pendingIdleHandlerCount為0.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler//去掉handler的引用

                boolean keep = false;
                try {
                    keep = idler.queueIdle();//idle時執行的方法
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            //重置idle handler個數為0,以保證不會再次重復運行
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            //當調用一個空閑handler時,一個新message能夠被分發,因此無需等待可以直接查詢pending message.
            nextPollTimeoutMillis = 0;
        }
    }

nativePollOnce(ptr, nextPollTimeoutMillis)是一個native方法,是一個阻塞操作。其中nextPollTimeoutMillis代表下一個消息到來前,還需要等待的時長;當nextPollTimeoutMillis = -1時,表示消息隊列中無消息,會一直等待下去。空閑后,往往會執行IdleHandler中的方法。當nativePollOnce()返回后,next()從mMessages中提取一個消息。nativePollOnce()在native做了大量的工作,想深入研究可查看資料: Android消息機制2-Handler(Native層)

  • 移除消息 removeMessages()
    就是將消息從鏈表移除,同時將移除的消息添加到消息池,提供循環復用。
    采用了兩個while循環,第一個循環是從隊頭開始,移除符合條件的消息,第二個循環是從頭部移除完連續的滿足條件的消息之后,再從隊列后面繼續查詢是否有滿足條件的消息需要被移除。
    void removeMessages(Handler h, int what, Object object) {
        if (h == null) {
            return;
        }

        synchronized (this) {
            Message p = mMessages;
            //從消息隊列的頭部開始,移除所有符合條件的消息
            // Remove all messages at front.
            while (p != null && p.target == h && p.what == what
                   && (object == null || p.obj == object)) {
                Message n = p.next;
                mMessages = n;
                p.recycleUnchecked();
                p = n;
            }

            // Remove all messages after front.
            //移除剩余的符合要求的消息
            while (p != null) {
                Message n = p.next;
                if (n != null) {
                    if (n.target == h && n.what == what
                        && (object == null || n.obj == object)) {
                        Message nn = n.next;
                        n.recycleUnchecked();
                        p.next = nn;
                        continue;
                    }
                }
                p = n;
            }
        }
    }
  • 退出消息隊列
    消息退出的方式:

    • 當safe =true時,只移除尚未觸發的所有消息,對于正在觸發的消息并不移除;
    • 當safe =flase時,移除所有的消息

void quit(boolean safe) {
if (!mQuitAllowed) {// 當mQuitAllowed為false,表示不運行退出,強行調用quit()會拋出異常
throw new IllegalStateException("Main thread not allowed to quit.");
}

    synchronized (this) {
        if (mQuitting) { //防止多次執行退出操作
            return;
        }
        mQuitting = true;

        if (safe) {
            removeAllFutureMessagesLocked();//移除尚未觸發的所有消息
        } else {
            removeAllMessagesLocked();//移除所有的消息
        }

        // We can assume mPtr != 0 because mQuitting was previously false.
        //mQuitting=false,那么認定為 mPtr != 0
        nativeWake(mPtr);
    }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內容