Android線程通信機(jī)制-Handler(Java層)

一、概述

Android的單線程UI模型,決定了在UI線程中不能進(jìn)行耗時(shí)任務(wù),在開發(fā)過程中,需要將網(wǎng)絡(luò)、io等耗時(shí)任務(wù)放在工作線程中執(zhí)行,工作線程中執(zhí)行完成后需要在UI線程中進(jìn)行刷新,因此就有了Handler進(jìn)程內(nèi)線程通信機(jī)制,當(dāng)然Handler并不是只能用在UI線程與工作線程間的切換,Android中任何線程間通信都可以使用Handler機(jī)制。
Android的Handler機(jī)制應(yīng)該說是有兩套實(shí)現(xiàn),Java層與native層分別實(shí)現(xiàn)了Handler機(jī)制,也就是說在Java層與native層各自維護(hù)了自己的消息隊(duì)列,native層消息優(yōu)先于Java層消息處理,在MessageQueue的源碼中可以看到很多的native代碼。這里只對(duì)Java層做個(gè)分析。

二、使用Handler實(shí)現(xiàn)線程間通信

1、在UI線程中使用Handler

UI線程中使用Handler非常簡(jiǎn)單,因?yàn)榭蚣芤呀?jīng)幫我們初始化好了Looper,只需要?jiǎng)?chuàng)建一個(gè)Handler對(duì)象即可,之后便可直接使用這個(gè)Handler實(shí)例向UI線程發(fā)送消息。

private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            // do something
        }
    };

注意:這種做法會(huì)導(dǎo)致內(nèi)存泄漏。
我們通過Handler發(fā)送消息,在Message對(duì)象中會(huì)持有當(dāng)前Handler對(duì)象的引用,在Java中非靜態(tài)成員類、內(nèi)部類、匿名類會(huì)持有外部對(duì)象的引用(這里在源碼中有提到),而Looper是線程局部變量,其生命周期與UI線程相同,Looper持有MessageQueue的引用,MessageQueue持有Message的引用,當(dāng)通過Handler發(fā)送一個(gè)延時(shí)消息未處理之前用戶已經(jīng)離開當(dāng)前Activity,會(huì)導(dǎo)致Activity不能及時(shí)釋放而內(nèi)存泄漏。

解決思路:

既然知道是因?yàn)镠andler持有Activity的引用而導(dǎo)致內(nèi)存泄漏,那便讓Activity在結(jié)束的時(shí)候不再有對(duì)象持有當(dāng)前Activity的引用,或者不再有對(duì)象持有該Handler的引用,總之便是將這條引用鏈切斷。

2、在非UI線程中使用Handler

在非UI線程中使用Handler一定要注意必須在創(chuàng)建Handler之前調(diào)用Looper.prepare()方法來初始化Looper,否則會(huì)報(bào)異常。如果不調(diào)用Looper.loop()方法,線程會(huì)在執(zhí)行完畢后退出,也無法接收到消息。

    private Handler handler;

    Thread threadA = new Thread(new Runnable() {
        @Override
        public void run() {
            // 這一步其實(shí)是創(chuàng)建threadA的線程本地變量Looper
            Looper.prepare();
            // 創(chuàng)建Handler
            handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    Bundle bundle =  msg.getData();
                    System.out.println(bundle.getString("msg"));
                }
            };
            // 讓threadA進(jìn)入Looper循環(huán)中,不斷的獲取消息
            Looper.loop();
        }
    });

    Thread threadB = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("<<<<<<<<<<<");
            // 創(chuàng)建消息
            Message msg = Message.obtain();
            Bundle bundle = new Bundle();
            bundle.putString("msg", "Hello World!");
            msg.setData(bundle);
            // 發(fā)送消息
            handler.sendMessage(msg);
            System.out.println(">>>>>>>>>>>");
        }
    });

這里只是為了簡(jiǎn)單寫的示例代碼,在實(shí)際開發(fā)中當(dāng)然不會(huì)這么做,異步任務(wù)都會(huì)管理起來,不然離開了當(dāng)前界面還有任務(wù)在執(zhí)行(比如請(qǐng)求數(shù)據(jù)),那便沒有意義了。

三、Handler機(jī)制實(shí)現(xiàn)原理

1、UML類圖

Handler機(jī)制主要由Handler、Looper、MessageQueue、Message四個(gè)類組成,從下面的UML中可以看到Handler持有一個(gè)Lopper實(shí)例,這個(gè)Looper實(shí)例與線程相關(guān),而Looper中管理著一個(gè)MessageQueue消息隊(duì)列,MessageQueue本質(zhì)上是一個(gè)鏈表。從UML類圖大致能看到Handler的一個(gè)整體結(jié)構(gòu)。

Handler、Looper、MessageQueue、Message類圖

2、Handler工作流程

2.1、基本流程

Handler工作流程主要分為兩條支線,工作線程(也就是要發(fā)送消息的線程,后同)中發(fā)送消息實(shí)際上是將消息插入到消息隊(duì)列MessageQueue中,初始化Handler的線程(即接受消息的線程,后同)則通過Looper.loop()方法進(jìn)入無限循壞,不斷的從消息隊(duì)列MessageQueue中取出消息,通過Message本身持有的Handler去分發(fā)消息。

2.2、線程切換的關(guān)鍵

1、在Looper初始化的時(shí)候,其實(shí)是在當(dāng)前線程的本地變量(ThreadLocal)中存儲(chǔ)了一個(gè)Looper,而Looper.loop()在進(jìn)入循環(huán)時(shí),便是通過當(dāng)前線程拿到Looper對(duì)象,從而拿到當(dāng)前線程維持的MessageQueue消息隊(duì)列,不斷的讀取消息。至于在另一個(gè)線程中通過Handler發(fā)送消息簡(jiǎn)單的說,讀消息一直都是在初始化Handler的線程中進(jìn)行,之后的分發(fā)消息與處理消息當(dāng)然也是了。
2、在工作線程中發(fā)送消息,Handler本身持有了一個(gè)初始化線程的引用,當(dāng)發(fā)送消息時(shí),其實(shí)都是將消息放入初始化線程的消息隊(duì)列中。

Handler工作流程圖

3、源碼解析

3.1 Handler源碼

從創(chuàng)建Handler開始看,Handler的構(gòu)造方法有多個(gè)重載,最終都會(huì)走Handler(Callback callback, boolean async)或者Handler(Looper looper, Callback callback, boolean async)這兩個(gè)構(gòu)造方法。

public Handler(Callback callback, boolean async) {
        // 檢查是否是非靜態(tài)成員類、內(nèi)部類、匿名類,這幾種會(huì)導(dǎo)致內(nèi)存泄漏
        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());
            }
        }
        // Looper.myLooper()是從線程本地存儲(chǔ)中拿到與當(dāng)前線程相關(guān)的Looper
        mLooper = Looper.myLooper();
        // 這里拋出的異常就是我們經(jīng)??匆姷?,未再非UI線程中使用Handler卻沒有調(diào)用Looperprepare()初始化Looper
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        // 接下來幾行代碼初始化一些成員變量
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

Handler創(chuàng)建好了,接下來便是使用Handler,也就是通過Handler來發(fā)送消息。發(fā)送消息的方法主要有兩大類,post****系列發(fā)送一個(gè)Runnable,作為處理消息的callback,或者send****系列方法發(fā)送一個(gè)Message,本質(zhì)上都是構(gòu)造一個(gè)Message對(duì)象,加上消息分發(fā)的時(shí)間點(diǎn),最終都會(huì)調(diào)用sendMessageAtTime(Message msg, long uptimeMillis)這個(gè)方法。
有一個(gè)例外的方法,sendMessageAtFrontOfQueue(Message msg),默認(rèn)將消息的執(zhí)行時(shí)間點(diǎn)置為0,也就是立即分發(fā),在入消息隊(duì)列時(shí)會(huì)放置在隊(duì)列頭。

/**
 * @param msg 要發(fā)送的消息
 * @param uptimeMillis 消息的投遞時(shí)間
 * @return true 消息放入隊(duì)列成功, false則是失敗
 */
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        // 這個(gè)方法很簡(jiǎn)單,在這里檢查了下MessageQueue是否準(zhǔn)備好了
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        // 從命名可以看出是將消息加入隊(duì)列
        return enqueueMessage(queue, msg, uptimeMillis);
 }

這個(gè)方法只有幾行代碼,真正入隊(duì)列還是在MessageQueue中做的,卻在入隊(duì)列之前做了一件很重要的事情。

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        // 重要的事情在這,設(shè)置了Message的target,這也是后面分發(fā)消息的關(guān)鍵
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        // 調(diào)用MessageQueue的成員方法將消息入隊(duì)列
        return queue.enqueueMessage(msg, uptimeMillis);
    }

Handler的另一個(gè)任務(wù)是分發(fā)消息并交給合適的方法去處理消息,由Handler.dispatchMessage(Message msg)這個(gè)方法完成。

  public void dispatchMessage(Message msg) {
        if (msg.callback != null) {  
            // 這里的callback就是個(gè)Runable對(duì)象,會(huì)執(zhí)行其run()方法
            handleCallback(msg);
        } else {
            if (mCallback != null) {  
                 // mCallback是在創(chuàng)建Handler對(duì)象時(shí)設(shè)置的監(jiān)聽
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            // 這個(gè)方法很熟悉了,就是創(chuàng)建Handler時(shí)復(fù)寫的,也是最常用的
            handleMessage(msg);
        }
   }
3.2 Looper源碼

Looper的代碼非常少,先來看一下初始化方法,有兩個(gè)重載Looper. prepare()Looper. prepare(boolean quitAllowed)。

 public static void prepare() {
        // 調(diào)用的有參的那個(gè)構(gòu)造方法
        prepare(true);
   }
  /**
   * @param quitAllowed 是否允許退出循環(huán)
   */
  private static void prepare(boolean quitAllowed) {
        // 檢查是否已經(jīng)初始化過了,從里可以看到`Looper.prepare()`在一個(gè)線程中只能調(diào)用一次
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        // 這里可以看到創(chuàng)建了一個(gè)Looper對(duì)象,并存入TLS
        sThreadLocal.set(new Looper(quitAllowed));
   }

接下來時(shí)Looper的構(gòu)造方法,這個(gè)構(gòu)造方法是私有的,也就是說我們?cè)诔跏蓟疞ooper時(shí)只能通過Looper.prepare()這個(gè)方法。

private Looper(boolean quitAllowed) {
        // 創(chuàng)建了消息隊(duì)列,每個(gè)線程只有一個(gè)MessageQueue對(duì)象
        mQueue = new MessageQueue(quitAllowed);
        // 存儲(chǔ)當(dāng)前線程的引用
        mThread = Thread.currentThread();
 }

還有個(gè)prepareMainLooper()方法,也是用來初始化Looper的,這個(gè)方法只是為了UI線程使用,UI線程Looper的初始化是在ActivityThreadmain()方法中進(jìn)行的。我們可以通過Looper.getMainLooper().getThread()來獲取UI線程。

接下來是Looper的消息循環(huán)方法Looper.loop(),這個(gè)方法去掉一些安全性驗(yàn)證與Log,核心代碼也很短。

public static void loop() {
        // 獲取當(dāng)前線程綁定的Looper
        final Looper me = myLooper();
        // 未初始化Looper
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }   
       // 當(dāng)前線程的消息隊(duì)列
        final MessageQueue queue = me.mQueue;
        ...
        for (;;) {
            // 從消息隊(duì)列中取出下一個(gè)消息,沒有則阻塞
            Message msg = queue.next(); // might block
            // 取到null,這是因?yàn)镸essageQueue已經(jīng)退出消息循壞
            if (msg == null) {
                return;
            }
            // 分發(fā)消息,這里的target就是Handler對(duì)象,在Handler中消息入隊(duì)列時(shí)設(shè)置的
            msg.target.dispatchMessage(msg);
            ...
            // 將Message放入消息池
            msg.recycleUnchecked();
        }
 }

Looper的任務(wù)就是創(chuàng)建一個(gè)循環(huán)器,不斷的從消息隊(duì)列取消息,交給Handler去分發(fā)。

3.3 MessageQueue源碼

前面說Handler機(jī)制在native層與Java層都有實(shí)現(xiàn),而HandlerLooper中都未出現(xiàn)native層的代碼,其實(shí)是在MessageQueue中將Java層與native層聯(lián)系了起來,這里只分析Java層實(shí)現(xiàn),在做應(yīng)用開發(fā)的時(shí)候也往往只使用Java層的Handler機(jī)制。
首先來看一下MessageQueue的構(gòu)造方法,這個(gè)構(gòu)造方法是包級(jí)權(quán)限,也就是說我們是無法在自定義的類中創(chuàng)建MessageQueue這個(gè)類的實(shí)例的。

MessageQueue(boolean quitAllowed) {
        // 是否允許退出消息循環(huán)
        mQuitAllowed = quitAllowed;
        // mPtr是native層消息隊(duì)列的頭指針
        mPtr = nativeInit();
}

在分析Handler發(fā)送消息的時(shí)候,可以看到最終都是將消息Message放入消息隊(duì)列MessageQueue中,MessageQueue稱為消息隊(duì)列,其實(shí)現(xiàn)的數(shù)據(jù)結(jié)構(gòu)卻并不是真正的隊(duì)列,而是一個(gè)單鏈表,在插入消息節(jié)點(diǎn)Message時(shí)按照時(shí)間點(diǎn)來確定位置。看下一下將消息入隊(duì)列的MessageQueue.enqueueMessage(Message msg, long when)方法。

boolean enqueueMessage(Message msg, long when) {
        // 如果沒有target,消息無法被處理
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        // 消息如果已經(jīng)被使用,那無法再次入隊(duì)列
        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;
            }
            // 設(shè)置消息使用的標(biāo)志位
            msg.markInUse();
            // 設(shè)置消息被處理的時(shí)間點(diǎn)
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            // 消息隊(duì)列中無消息,或消息的時(shí)間為立即執(zhí)行,或消息的時(shí)間小于隊(duì)列中第一個(gè)消息的時(shí)間,則將消息加入到隊(duì)列頭
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {   // 根據(jù)消息的時(shí)間將消息放入隊(duì)列的合適位置,就是單鏈表的插入
                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.
            if (needWake) {
                // 喚醒線程
                nativeWake(mPtr);
            }
        }
        return true;
    }

接下來看一下取出消息MessageQueue.next()方法,這個(gè)方法挺長(zhǎng)的,而且與native層交互很多。

簡(jiǎn)單說下大概的邏輯:
  • 1.消息循環(huán)已經(jīng)退出,則返回null,在Looper.loop()方法結(jié)束循環(huán)
  • 2.阻塞操作,等待nextPollTimeoutMillis到達(dá),或者線程被喚醒未到下一次喚醒時(shí)間,則阻塞線程,等待喚醒,在MessageQueue.enqueueMessage(Message msg, long when)方法中我們看到了喚醒的操作。
  • 3.將取到的消息的時(shí)間與當(dāng)前時(shí)間做比較,若還未到處理時(shí)間,則設(shè)置下一次輪詢的超時(shí)時(shí)間
  • 4.取出一條消息返回
  • 5.消息隊(duì)列為空,設(shè)置下一次超時(shí)時(shí)間為-1,會(huì)使線程一直阻塞,等待喚醒
 Message next() {
        // step 1
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            // step 2
            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) {   // step 3
                        // 下一條消息執(zhí)行時(shí)間還未到,設(shè)置一個(gè)超時(shí)時(shí)間
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {    // step 4
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        // 設(shè)置消息使用的標(biāo)志位
                        msg.markInUse();
                        return msg;
                    }
                } else { 
                    // step 5
                    nextPollTimeoutMillis = -1;
                }

                // 執(zhí)行退出消息,返回null
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // 消息隊(duì)列為空或者消息未到執(zhí)行時(shí)間,線程空閑,可以執(zhí)行IdleHandlers
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // 沒有IdleHandlers執(zhí)行,直接進(jìn)入下一次循環(huán)繼續(xù)等待消息
                    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.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

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

            // 重置IdleHandler,不會(huì)被再次執(zhí)行
            pendingIdleHandlerCount = 0;
            nextPollTimeoutMillis = 0;
        }
    }
3.4 Message源碼

Message就是消息的實(shí)體類,也是消息隊(duì)列MessageQueue的節(jié)點(diǎn),除了具有一些消息的基本屬性,還持有下一條消息的指針。
Message的構(gòu)造方法是空的,其所有屬性都是通過set方法來設(shè)置。在開發(fā)過程中,盡量使用obtain系列的方法來獲取一個(gè)消息實(shí)例,內(nèi)部通過消息池來實(shí)現(xiàn),減少因?yàn)閯?chuàng)建對(duì)象而造成的開銷,以達(dá)到復(fù)用的效果。
obtain方法有許多重載,本質(zhì)上都是調(diào)用無參的Message.obtain()方法從消息池中取出一個(gè)消息實(shí)例,設(shè)置不同的屬性。

public static Message obtain() {
        // 通過同步sPoolSync對(duì)象,將sPool加鎖,保證線程安全
        synchronized (sPoolSync) {
            if (sPool != null) {  // 消息池不為空,則從消息池中取出一個(gè)消息實(shí)例
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // 清楚使用標(biāo)志位
                sPoolSize--;  // 消息池?cái)?shù)量減1
                return m;
            }
        }
        // 如果消息池為空,則創(chuàng)建一個(gè)新的消息對(duì)象
        return new Message();
    }

既然有消息池,那么消息池中的消息是哪來的呢,Message中有個(gè)Message.recycle()方法,這個(gè)方法便是將消息放入消息池中。

public void recycle() {
        if (isInUse()) {  // 正在使用的消息無法回收
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        // 真正的實(shí)現(xiàn)回收消息
        recycleUnchecked();
    }

Message.recycleUnchecked()多了個(gè)uncheck,是真的不檢查是否在使用,強(qiáng)行回收。

void recycleUnchecked() {
        // 設(shè)置使用標(biāo)志位
        flags = FLAG_IN_USE;
        // 清楚Message的所有屬性
        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) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

四、結(jié)束

大概記錄了Handler機(jī)制在Java層的一個(gè)實(shí)現(xiàn)流程,從Handler發(fā)送消息,將消息加入到MessageQueue中,Looper不斷的循環(huán)從MessageQueue中取出消息,將消息交給Handler處理。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容