Android通信方式篇(一)-消息機制(Java層)

Android從某種意義上看是一個以消息驅(qū)動的系統(tǒng)识椰,內(nèi)部含有大量以消息驅(qū)動的當時進行的交互陡厘,比如四大組件的啟動校哎、又比如常見的將子線程的任務(wù)切換到Handler所在的主線程中執(zhí)行等等吏夯,它屬于進程內(nèi)部的一種通信方式静浴。這篇文章就對android的消息機制做一個簡單的梳理磷籍。

一适荣、關(guān)鍵類簡介

消息機制主要涉及Looper/MessageQueue/Message/Handler/ThreadLocal這幾個類丙躏。

  • Looper:消息泵。一個死循環(huán)束凑,有消息來就處理晒旅,沒有消息就等待。一個線程最多只能有一個Looper對象汪诉,一個Looper對應(yīng)管理此線程的MessageQueue废恋,兩者一一對應(yīng)。

  • MessageQueue:消息隊列扒寄。內(nèi)部存儲了一組消息鱼鼓,以隊列的形式對外提供向消息池投遞消息(MessageQueue.enqueueMessage)和取走消息(MessageQueue.next)的工作,內(nèi)部采用單鏈表的數(shù)據(jù)結(jié)構(gòu)來存儲消息列表该编。

  • Message:消息迄本。分為硬件產(chǎn)生的消息(如按鈕、觸摸)和軟件生成的消息课竣。

  • Handler:消息處理者嘉赎。主要向消息池發(fā)送各種消息事件(Handler.sendMessage)和處理相應(yīng)消息事件(Handler.handleMessage);

  • ThreadLocal:一個線程內(nèi)部的數(shù)據(jù)存儲類于樟。保證線程內(nèi)部數(shù)據(jù)在各線程間相互獨立公条。

先看看消息機制整體流程:

from gityuan

二、源碼分析

2.1 Looper

Looper的字面意思是“循環(huán)者”迂曲,它被設(shè)計用來使一個普通線程變成Looper線程靶橱。所謂Looper線程就是循環(huán)工作的線程。

主要方法有兩個:

1) prepare() : 使Thread變成looper線程路捧,具備循環(huán)的能力

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));
}

看這個方法关霸,就是創(chuàng)建一個Looper放到當前Thread對應(yīng)的sThreadLocal里去。ThreadLocal前面講過杰扫,它主要作用就是讓線程間存儲數(shù)據(jù)相互獨立队寇,顯然這里它保證的是線程對應(yīng)的 Looper一一對應(yīng)且相互獨立。

我們再看看Looper的構(gòu)造方法:

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

Looper內(nèi)包含了一個初始化的MessageQueue涉波,也關(guān)聯(lián)了當前的Thread英上。
那么總結(jié)下關(guān)系就是:一個Thread對應(yīng)的ThreadLocal保持了對應(yīng)的Looper,而Looper關(guān)聯(lián)了MessageQueue與當前Thread啤覆。

2) loop():

public static void loop() {
    final Looper me = myLooper();//獲取當前線程本地存儲區(qū)存儲的 Looper對象
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;//獲取Looper對象對應(yīng)的消息隊列
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();
    for (;;) {//looper主循環(huán)方法,是個死循環(huán)
        Message msg = queue.next(); // 取走消息池的消息去處理惭聂,可能會阻塞
        ...
        try {
            msg.target.dispatchMessage(msg);//分發(fā)Message
        } 
         ...
        msg.recycleUnchecked();//將消息放入消息池
    }
}

loop()進入循環(huán)模式窗声,不斷重復下面的操作,直到?jīng)]有消息時退出循環(huán)

  • 讀取MessageQueue的下一條Message辜纲; Message msg = queue.next();
  • 把Message分發(fā)給相應(yīng)的target笨觅;msg.target.dispatchMessage(msg)拦耐;
  • 再把分發(fā)后的Message回收到消息池,以便重復利用见剩。msg.recycleUnchecked()杀糯;

總結(jié): Looper主要就是讓線程具備消息泵的循環(huán)能力,僅此而已苍苞。

from Jeanboydev
2.2MessageQueue

MessageQueue看名字像是一個隊列, 其實是以鏈表的形式保存message固翰。其次,MessageQueue是消息機制的Java層和C++層的連接紐帶羹呵,大部分核心方法都交給native層來處理骂际。

例如消息的阻塞處理:

private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/

在此我先只關(guān)心幾個方法:

1) next():

之前在Looper 的loop()死循環(huán)中見過此方法,作用是取出消息進行處理冈欢,而且有可能會阻塞歉铝。
下面我們來看看源碼,方法稍微有點長:

Message next() {
    final long ptr = mPtr;
    if (ptr == 0) {//當消息循環(huán)已經(jīng)退出凑耻,直接返回
        return null;
    }
    int pendingIdleHandlerCount = -1; // 循環(huán)迭代的首次為-1
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
       //阻塞操作太示,當?shù)却齨extPollTimeoutMillis時長,或者消息隊列被喚醒香浩,都會返回
        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) {
                    //當異步消息觸發(fā)的時間大于當前時間先匪,則設(shè)置下一次輪詢的超時時長
                    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
            if (mQuitting) {
                dispose();
                return null;
            }
            //當消息隊列為空,或者是消息隊列的第一個消息時
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
               //沒有idle handlers 需要運行镜盯,則循環(huán)并等待岸裙。
                mBlocked = true;
                continue;
            }
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        //只有第一次循環(huán)時,會運行idle handlers速缆,執(zhí)行完成后降允,重置pendingIdleHandlerCount為0.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; //去掉handler的引用
            boolean keep = false;
            try {
                keep = idler.queueIdle();//idle時執(zhí)行的方法
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
       //重置idle handler個數(shù)為0,以保證不會再次重復運行
        pendingIdleHandlerCount = 0;
       //當調(diào)用一個空閑handler時艺糜,一個新message能夠被分發(fā)剧董,因此無需等待可以直接查詢pending message.
        nextPollTimeoutMillis = 0;
    }
}

nativePollOnce是阻塞操作,其中nextPollTimeoutMillis代表下一個消息到來前破停,還需要等待的時長翅楼;當nextPollTimeoutMillis = -1時,表示消息隊列中無消息真慢,會一直等待下去毅臊。

當處于空閑時,往往會執(zhí)行IdleHandler中的方法黑界。當nativePollOnce()返回后管嬉,next()從mMessages中提取一個消息皂林。
nativePollOnce()在native做了大量的工作。

2) enqueueMessage:

該方法是添加一條消息到消息隊列蚯撩,下面具體來看看實現(xiàn):

boolean enqueueMessage(Message msg, long when) {
    // 每一個普通Message必須有一個target
    if (msg.target == null) {
        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) {//正在退出時础倍,回收msg,加入到消息池
            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) {
           //p==null代表MessageQueue沒消息胎挎,或者msg的觸發(fā)時間是隊列中最早的沟启,則進入該分支
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;//當阻塞時需要喚醒
        } else {
      //將消息按時間順序插入到MessageQueue。一般地呀癣,不需要喚醒事件隊列美浦,除非   
     //消息隊頭存在barrier,并且同時Message是隊列中最早的異步消息项栏。
            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;
        }
        // 消息沒有退出浦辨,我們認為此時mPtr != 0
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

MessageQueue是按照Message觸發(fā)時間的先后順序排列的,隊頭的消息是將要最早觸發(fā)的消息沼沈。當有消息需要加入消息隊列時流酬,會從隊列頭開始遍歷,直到找到消息應(yīng)該插入的合適位置列另,以保證所有消息的時間順序芽腾。

3) removeMessage:

方法顧名思義,從MessageQueue中移除消息:

void removeMessages(Handler h, int what, Object object) {
    if (h == null) {
        return;
    }
    synchronized (this) {
        Message p = mMessages;
       //從消息隊列頭部開始页衙,移除所有符合條件的消息
        while (p != null && p.target == h && p.what == what
               && (object == null || p.obj == object)) {
            Message n = p.next;
            mMessages = n;
            p.recycleUnchecked();
            p = n;
        }
       //移除剩余的符合要求的消息
        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;
        }
    }
}

這個移除消息的方法摊滔,采用了兩個while循環(huán),第一個循環(huán)是從隊頭開始店乐,移除符合條件的消息艰躺,第二個循環(huán)是從頭部移除完連續(xù)的滿足條件的消息之后,再從隊列后面繼續(xù)查詢是否有滿足條件的消息需要被移除眨八。

4) postSyncBarrier和removeSyncBarrier

攔截同步消息和取消攔截腺兴,可以自行研究。

總結(jié):MessageQueue就是一個鏈表結(jié)構(gòu)的消息池廉侧,內(nèi)部按照Message觸發(fā)時間的先后順序排列页响,提供基本的投遞、獲取段誊、刪除消息的功能闰蚕。

from Jeanboydev
2.3 Message

message 其實就是對消息封裝的對象。針對它枕扫,主要了解的是消息池的概念陪腌,以及獲取消息和回收消息的兩個方法。

  1. 消息池:

message引入了消息池烟瞧,這樣的好處是诗鸭,當消息池不為空時,可以直接從消息池中獲取Message對象参滴,而不是直接創(chuàng)建强岸,提高效率。類似于設(shè)計模式中享元模式這么一個概念砾赔。

靜態(tài)變量sPool的數(shù)據(jù)類型為Message蝌箍,通過next成員變量,維護一個消息池暴心;靜態(tài)變量MAX_POOL_SIZE代表消息池的可用大屑嗣ぁ;消息池的默認大小為50专普。

  1. 消息池的主要操作obtain()和recycle()悯衬。
  • obtain方法:從消息池中獲取消息
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;//從sPool中取出一個Message對象,并讓消息鏈斷開
            m.flags = 0; // 清除in-use flag
            sPoolSize—;//消息池的可用大小進行-1操作
            return m;
        }
    }
    return new Message();  //當消息池為空檀夹,則直接創(chuàng)建Message對象
}

obtain(): 從消息池取Message筋粗,都是把消息池表頭的Message取走,再把表頭指向next;

  • recycle方法:把不再使用的消息加入消息池
public void recycle() {
    if (isInUse()) {//判斷消息是否正在使用
        if (gCheckRecycle) {// 版本>Android5.0 = true  版本<=Android5.0 = false
            throw new IllegalStateException("This message cannot be recycled because it "
                    + "is still in use.");
        }
        return;
    }
    recycleUnchecked();
}
//對于不再使用的消息炸渡,加入消息池
void recycleUnchecked() {
//將消息標志位設(shè)為IN_USE,并清空消息所有參數(shù)
    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) {//當消息池沒有滿時娜亿,將Message對象加入消息池
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;//消息池可用大小+1
        }
    }
}

recycle(): 將Message加入到消息池的過程,都是把Message加到鏈表的表頭蚌堵;

總結(jié): Message 就是消息對象本身买决,內(nèi)部有對象池,能一定程度優(yōu)化頻繁創(chuàng)建和銷毀消息帶來的性能問題吼畏。

2.4 Handler

Handler是消息處理者督赤,由它發(fā)起消息操作。主要扮演了往MessageQueue上添加消息和處理消息的角色宫仗。

首先看看Handler的構(gòu)造方法:

public class Handler {
final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }
    mLooper = Looper.myLooper(); //默認關(guān)聯(lián)當前線程的Looper
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;//關(guān)聯(lián)當前線程Looper對應(yīng)的MessageQueue
    mCallback = callback;
    mAsynchronous = async;
   }
}

那么我們知道了够挂,Handler在哪個線程被創(chuàng)建就屬于哪個線程,就為哪個線程服務(wù)藕夫,因為關(guān)聯(lián)的是當前線程的Looper孽糖,操作的也就是當前線程的MessageQueue

Handler發(fā)送和處理消息

Handler最核心的功能就兩個,一個是向MessageQueue發(fā)送消息毅贮,一個是處理MessageQueue分發(fā)出來的消息办悟。

1) 發(fā)送消息:

handler有許多發(fā)送消息的方法:

post(Runnable)
postAtTime(Runnable, long)
postDelayed(Runnable, long)
sendEmptyMessage(int)
sendMessage(Message)
sendMessageAtTime(Message, long)
sendMessageDelayed(Message, long)

光看這些方法參數(shù)你可能會覺得handler能發(fā)兩種消息,一種是Runnable對象滩褥,一種是message對象病蛉,這是直觀的理解,但其實post發(fā)出的Runnable對象最后都被封裝成message對象了。

以post()方法為例:

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

最終還是封裝成了Message,而且還是從Message的消息池里取的铺然。

另外俗孝,這些發(fā)送消息的方法最終都會走到如下方法:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;//把當前msg的target與當前Handler一一對應(yīng)
    if (mAsynchronous) {//這個值是在Handler的構(gòu)造方法中設(shè)置是否異步
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);//最終發(fā)送消息
}

2) 處理消息

消息的處理是通過核心方法dispatchMessage與鉤子方法handleMessage完成的

在Looper的 loop() 方法中:
msg.target.dispatchMessage(msg); 也就是執(zhí)行當前Hanlder的dispatchMessage(msg)方法:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg); 
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

都是回調(diào)方法的處理,邏輯很明顯魄健,不贅述了赋铝。

最后一張圖看整個Java層面消息處理流程:


from Jeanboydev
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者沽瘦。
  • 序言:七十年代末革骨,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子析恋,更是在濱河造成了極大的恐慌良哲,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件助隧,死亡現(xiàn)場離奇詭異筑凫,居然都是意外死亡,警方通過查閱死者的電腦和手機喇颁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門漏健,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人橘霎,你說我怎么就攤上這事蔫浆。” “怎么了姐叁?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵瓦盛,是天一觀的道長。 經(jīng)常有香客問我外潜,道長原环,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任处窥,我火速辦了婚禮嘱吗,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘滔驾。我一直安慰自己谒麦,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布哆致。 她就那樣靜靜地躺著绕德,像睡著了一般。 火紅的嫁衣襯著肌膚如雪摊阀。 梳的紋絲不亂的頭發(fā)上耻蛇,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天踪蹬,我揣著相機與錄音,去河邊找鬼臣咖。 笑死跃捣,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的亡哄。 我是一名探鬼主播枝缔,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼布疙,長吁一口氣:“原來是場噩夢啊……” “哼蚊惯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起灵临,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤截型,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后儒溉,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宦焦,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年顿涣,在試婚紗的時候發(fā)現(xiàn)自己被綠了波闹。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡涛碑,死狀恐怖精堕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蒲障,我是刑警寧澤歹篓,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站揉阎,受9級特大地震影響庄撮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜毙籽,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一洞斯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧坑赡,春花似錦烙如、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至搀突,卻和暖如春刀闷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工甸昏, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留顽分,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓施蜜,卻偏偏與公主長得像卒蘸,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子翻默,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容