Handler 的理解

幾個(gè)關(guān)于Handler 的思考問(wèn)題

Handler 消息管理機(jī)制,如何管理事務(wù)?
Handler在線程間如何通信嫌术?
Handler 內(nèi)存共享方案

架構(gòu)簡(jiǎn)圖

->「主線程,消費(fèi)者」queue.next
[Message] <--------[Looper]     ===>[dispatchMessage]===>[handleMessage]
[Message]    
[Message]               
[Message]
[Message] 
.
.
.
[Message] <-----queue.enqueueMesage-----[Handler]
->「子線程栅盲,生產(chǎn)者」

我們使用時(shí)候的過(guò)程
子線程(bean)-> 主線程(顯示)

涉及到的知識(shí)點(diǎn)

ActiviyThread->AMS 流程
ActivitThead 里面的main()方法
我們的應(yīng)用啟動(dòng)過(guò)程
Launcher(app):zygote->jvm->ActivityThread.main()==>[Looper.prepareMainLooper() ------looper.loop]

設(shè)計(jì)思路迄本,handler 的message消息隊(duì)列為什么不阻塞拇囊?
這是因?yàn)檎麄€(gè)系統(tǒng)都再用handler 的message機(jī)制。如果阻塞會(huì)產(chǎn)生手機(jī)卡死的情況炭序。
設(shè)計(jì)模式,是一種生產(chǎn)折消費(fèi)者常用模式扫俺,生產(chǎn)--->緩存池----->消費(fèi)
生產(chǎn)者,通過(guò)enqueueMessage ----->[MessageQueue 倉(cāng)庫(kù)]-----> next() 取出消息柠贤,消費(fèi)者速缆。
異步消息,同步消息
看完這個(gè)鏈接朗鸠,或者看完本文

消息屏障/handlerThread IntentService
看完這個(gè)鏈接蹬竖,或者看完本文

參考掘金: https://juejin.im/post/6844903910113705998

MessageQueue特殊情況
兩個(gè)方面的阻塞:

  1. 消息沒(méi)到執(zhí)行時(shí)刻,會(huì)根據(jù)when 阻塞流酬,計(jì)算nextpollTimeout---nativePollOnce
    自動(dòng)喚醒

2.消息隊(duì)列為空的時(shí)候 nativePollOnce---ptr -nativeInt()
無(wú)限等待狀態(tài)芽腾。
當(dāng)其他消息進(jìn)來(lái)就會(huì)喚醒

源碼分析

開(kāi)始

使用的入口
Hndler=>
「無(wú)論調(diào)用那個(gè)postAtTime 最后會(huì)執(zhí)行」
「無(wú)論調(diào)用那個(gè)sendMessage最后會(huì)執(zhí)行」
=>sendMessageDelayed(Message msg, long delayMillis)
=> sendMessageAtTime(Message msg, long uptimeMillis)
「uptimeMills====SystemClock.uptimeMillis() + delayMillis 系統(tǒng)時(shí)間+我們傳入的延時(shí)時(shí)間 呻袭,在那個(gè)時(shí)間點(diǎn)執(zhí)行」
=>enqueueMessage(queue, msg, uptimeMillis)

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

傳送帶功能

MessageQueue跟隨Looper 順帶創(chuàng)建的

MessageQueue數(shù)據(jù)結(jié)構(gòu)
單鏈表實(shí)現(xiàn)的優(yōu)先級(jí)隊(duì)列(根據(jù)時(shí)間條件優(yōu)先級(jí)判斷)
根據(jù)時(shí)間優(yōu)先級(jí),是一個(gè)插入排序算法,優(yōu)先級(jí)隊(duì)列是取消息是從隊(duì)列頭開(kāi)始弹沽,

MessageQueue 持有 (Message mMessages;)
鏈表 Message->next->Message->next

Message => 持有(Handler target;)注意這個(gè)是Message 持有target相當(dāng)于Handler自身this檀夹,由于消息發(fā)送可能延時(shí),我們Activity聲明周期結(jié)束策橘,但是內(nèi)部類持有外部引用炸渡,造成內(nèi)存泄露的原因之一

MessageQueue 的 mQuitting默認(rèn)false調(diào)用quit ---true
quit清空消息,調(diào)用本地nativeWake喚醒
下面的Looper

void quit(boolean safe) {
        if (!mQuitAllowed) {
            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.
            nativeWake(mPtr);
        }
    }

MessageQueue 的 synchronized (this)
內(nèi)置鎖保證安全
一個(gè)線程只有一個(gè)MessageQueue丽已,而且MessageQueue還有內(nèi)置對(duì)象鎖所以保證唯一

 boolean enqueueMessage(Message msg, long when) {

  ->「注意這里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) {
  ->「注意這里mQuitting」
            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;
->「如果消息隊(duì)列有消息蚌堵,判斷節(jié)點(diǎn)p,根據(jù)我要加入消息when比p時(shí)間早,輪詢判斷進(jìn)行插入」
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // 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;
            }
->「當(dāng)消息為空的時(shí)候吼畏,再次來(lái)其他消息督赤,需要喚醒」
            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

輪詢?nèi)∠?/p>

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
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
->「nativePollOnce 承接上文喚醒隊(duì)列」
            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;
->「 如果target==null,那么它就是屏障宫仗,需要循環(huán)遍歷,一直往后找到第一個(gè)異步的消息
」
                if (msg != null && msg.target == null) {
                    // 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();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    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) {
                    // 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.
            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);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            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.
            nextPollTimeoutMillis = 0;
        }
    }

我們有很多native方法旁仿。系統(tǒng)實(shí)現(xiàn)了很多JNI層
nativePollOnce(睡眠)/nativeWake 喚醒操作
底層調(diào)用epoll方法藕夫,使線程阻塞

private native static long nativeInit();
    private native static void nativeDestroy(long ptr);
    @UnsupportedAppUsage
    private native void nativePollOnce(long ptr, int timeoutMillis); /*non-static for callbacks*/
    private native static void nativeWake(long ptr);
    private native static boolean nativeIsPolling(long ptr);
    private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);

動(dòng)力

Looper 相關(guān)

-> Looper 的初始化
構(gòu)造函數(shù)是私有的,不能隨便創(chuàng)建

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

持有一個(gè)引用final MessageQueue mQueue;
我們的MessageQueue 就是在這里順帶創(chuàng)建的

在prepare里面進(jìn)行的初始化

private static void prepare(boolean quitAllowed) {
->「注意承接上面枯冈,保證唯一行threadLocal get 檢查」
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

所有的當(dāng)前線程的Looper myLooper方法毅贮,唯一性

/**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

Looper 的 ThreadLocal

初始化中
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

上下文存儲(chǔ)變量,內(nèi)部有一個(gè)ThreadLocalMap k 就是threadlocal 本身,而他是final的 ,每次set<k,value>先get(k)如果存在直接返回

是一個(gè)常量保證了唯一性

ThreadLocal 維護(hù)類自己的Map

  static class ThreadLocalMap {
  static class Entry extends WeakReference<ThreadLocal<?>> {
   Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            } 
}
...
  }

一個(gè)線程----》只有一個(gè)Looper 不能改
一個(gè)線程----》ThreadLocalMap---》<唯一的ThreadLocal,value>

內(nèi)部維護(hù)原子性操作
private static AtomicInteger nextHashCode =
new AtomicInteger();

prepare 的時(shí)候先get 判斷是否存在

Looper loop()

執(zhí)行流程
looper.loop()->messageQueue.next()->handler.dispatchMessage->handler.handlerMessage()

承接Message
「msg.target 相當(dāng)于====handler===dispatchMessage 回調(diào)」

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

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        // Allow overriding a threshold with a system prop. e.g.
        // adb shell 'setprop log.looper.1000.main.slow 1 && stop && start'
        final int thresholdOverride =
                SystemProperties.getInt("log.looper."
                        + Process.myUid() + "."
                        + Thread.currentThread().getName()
                        + ".slow", 0);

        boolean slowDeliveryDetected = false;

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }
            // Make sure the observer won't change while processing a transaction.
            final Observer observer = sObserver;

            final long traceTag = me.mTraceTag;
            long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
            long slowDeliveryThresholdMs = me.mSlowDeliveryThresholdMs;
            if (thresholdOverride > 0) {
                slowDispatchThresholdMs = thresholdOverride;
                slowDeliveryThresholdMs = thresholdOverride;
            }
            final boolean logSlowDelivery = (slowDeliveryThresholdMs > 0) && (msg.when > 0);
            final boolean logSlowDispatch = (slowDispatchThresholdMs > 0);

            final boolean needStartTime = logSlowDelivery || logSlowDispatch;
            final boolean needEndTime = logSlowDispatch;

            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }

            final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
            final long dispatchEnd;
            Object token = null;
            if (observer != null) {
                token = observer.messageDispatchStarting();
            }
            long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
            try {
->注意留意「msg.target」
                msg.target.dispatchMessage(msg);
                if (observer != null) {
                    observer.messageDispatched(token, msg);
                }
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            if (logSlowDelivery) {
                if (slowDeliveryDetected) {
                    if ((dispatchStart - msg.when) <= 10) {
                        Slog.w(TAG, "Drained");
                        slowDeliveryDetected = false;
                    }
                } else {
                    if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
                            msg)) {
                        // Once we write a slow delivery log, suppress until the queue drains.
                        slowDeliveryDetected = true;
                    }
                }
            }
            if (logSlowDispatch) {
                showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

結(jié)束

Handler 的Callback
Handler->handlerMessage()

共享內(nèi)存

整個(gè)過(guò)程是Message在動(dòng)的過(guò)程
Message產(chǎn)生尘奏,new Message() 或者 obtain過(guò)來(lái)滩褥,所以就體現(xiàn)子線程到主線程的內(nèi)存共享

內(nèi)存不分線程,所以子線程能用炫加,主線程也能用瑰煎。

也是為什么不能不斷new Message的原因

退出和空消息

如果子線程調(diào)用 消息為空如何處理
需要主動(dòng)調(diào)用quite,Looper 的loop 是個(gè)無(wú)限循環(huán)俗孝,msg == null

quit-》喚醒-》messagequeue = null -> 退出Looper
quit函數(shù)---》nativeWake()

Handler 內(nèi)存泄露的原因和解決

  1. Activity 使用錯(cuò)誤,使用了匿名內(nèi)部類酒甸。默認(rèn)會(huì)持有 this,handler 持有外部類Activity。-----------解決改成static 靜態(tài)內(nèi)部類

Handler handler = new Handler(){
......
}

  1. 其他的內(nèi)部類持有為什么沒(méi)有這種問(wèn)題赋铝,recyclerview 的Adapter 的viewholder插勤?聲明周期的原因

sendMessageAtTime--->enqueueMessage{
msg.target = this;=====handler
}
整個(gè)message傳遞過(guò)程,需要延遲處理革骨,message持有handler农尖,handler持有activity。GC無(wú)法回收良哲,可達(dá)性算法不能回收
------------------解決 在 onDestroy的時(shí)候 主動(dòng)調(diào)用removeCallbacksAndMessages 釋放

HandlerThread是什么?為什么它會(huì)存在?

HandlerThread是Thread的子類盛卡,嚴(yán)格意義上來(lái)說(shuō)就是一個(gè)線程,只是它在自己的線程里面幫我們創(chuàng)建了Looper HandlerThread 存在的意義如下:

  1. 方便使用:a. 方便初始化筑凫,b窟扑,方便獲取線程looper 2)保證了線程安全
    我們一般在Thread里面 線程Looper進(jìn)行初始化的代碼里面,必須要對(duì)Looper.prepare(),同時(shí)要調(diào)用Loop漏健。 loop();
@Override
public void run() { Looper.prepare(); Looper.loop();
}

而我們要使用子線程中的Looper的方式是怎樣的呢?看下面的代碼

Thread thread = new Thread(new Runnable() {
    Looper looper;
@Override
public void run() {
// Log.d(TAG, "click2: " + Thread.currentThread().getName());
Looper.prepare();
looper =Looper.myLooper(); Looper.loop();
}
    public Looper getLooper() {
        return looper;
} });
thread.start();
Handler handler = new Handler(thread.getLooper());

上面這段代碼有沒(méi)有問(wèn)題呢? 肯定有
1)在初始化子線程的handler的時(shí)候嚎货,我們無(wú)法將子線程的looper傳值給Handler,解決辦法有如下辦法:
a. 可以將Handler的初始化放到 Thread里面進(jìn)行
b. 可以創(chuàng)建一個(gè)獨(dú)立的類繼承Thread,然后蔫浆,通過(guò)類的對(duì)象獲取殖属。
這兩種辦法都可以,但是瓦盛,這個(gè)工作 HandlerThread幫我們完成了
2)依據(jù)多線程的工作原理洗显,我們?cè)谏厦娴拇a中外潜,調(diào)用 thread.getLooper()的時(shí)候,此時(shí)的looper可能還沒(méi)有初 始化挠唆,此時(shí)是不是可能會(huì)掛掉呢?
以上問(wèn)題
HandlerThread 已經(jīng)幫我們完美的解決了处窥,這就是 handlerThread存在的必要性了。

我們?cè)倏?HandlerThread源碼

public void run() {
mTid = Process.myTid(); Looper.prepare(); synchronized (this) {
mLooper = Looper.myLooper();
notifyAll(); //此時(shí)喚醒其他等待的鎖玄组,但是 }
Process.setThreadPriority(mPriority); onLooperPrepared();
Looper.loop();
mTid = -1;
}

它的優(yōu)點(diǎn)就在于它的多線程操作滔驾,可以幫我們保證使用Thread的handler時(shí)一定是安全的。

IntentService 默認(rèn)創(chuàng)建HandlerThread 參見(jiàn) http://www.reibang.com/p/ad5613d3955e

應(yīng)用場(chǎng)景1:

IntentService 應(yīng)用:本身繼承service: 處理后臺(tái)耗時(shí)任務(wù)
處理完》 IntentService 自動(dòng)停止:內(nèi)存釋放

應(yīng)用需求:一項(xiàng)任務(wù)分成幾個(gè)子任務(wù)俄讹,子任務(wù)按順序執(zhí)行哆致,子任務(wù)全部執(zhí)行完成后,這項(xiàng)任務(wù)才算成功
這個(gè)需求可以用多個(gè)線程來(lái)處理患膛,一個(gè)線程處理完 -> 下一個(gè)線程 -> 下一個(gè)線程

IntentService就可以幫助我們完成這個(gè)工作摊阀,而且,能夠很好的管理線程踪蹬,保證只有一個(gè)線程處理工作胞此,而且是一個(gè)一個(gè)的完成任務(wù),有條不紊的進(jìn)行
同一個(gè)線程-》順序執(zhí)行1 2 3 4 : 對(duì)線程的控制么
原因:
IntentService 每個(gè)HandlerThread 維護(hù)一個(gè)Looper 保證looper綁定
每個(gè)任務(wù)是一個(gè)消息跃捣,維護(hù)了一個(gè)隊(duì)列messageQueue
先來(lái)后到 保證線程消息一個(gè)一個(gè)執(zhí)行豌鹤。

類似的場(chǎng)景2:緩存操作參見(jiàn)
Messagequeue 隊(duì)列處理機(jī)制在fragment生命周期管理中的應(yīng)用,glide

http://www.reibang.com/p/317b2d6bde1b

題外話:我們?cè)贕lide里面handler的場(chǎng)景

Glide.with(context).from(url).into(iamgeView)
我們通常context 縮小范圍枝缔,如果默認(rèn)用子線程他會(huì)默認(rèn)給你轉(zhuǎn)成applicationContext容易造成泄露
context:? fragment.getAppalicationContext

 RequestManagerFragment getRequestManagerFragment(final android.app.FragmentManager fm) {
    
    pendingRequestManagerFragments = new HashMap<Fragment>();
    //嘗試根據(jù)id去找到此前創(chuàng)建的RequestManagerFragment
    RequestManagerFragment current = (RequestManagerFragment) fm.findFragmentByTag(FRAGMENT_TAG);
    if (current == null) {
        //如果沒(méi)有找到布疙,那么從臨時(shí)存儲(chǔ)中尋找
        current = pendingRequestManagerFragments.get(fm);
        if (current == null) {
            //如果仍然沒(méi)有找到,那么新建一個(gè)RequestManagerFragment愿卸,并添加到臨時(shí)存儲(chǔ)中灵临。
            //然后開(kāi)啟事務(wù)綁定fragment并使用handler發(fā)送消息來(lái)將臨時(shí)存儲(chǔ)的fragment移除。
            current = new RequestManagerFragment();
            pendingRequestManagerFragments.put(fm, current);
            fm.beginTransaction().add(current, FRAGMENT_TAG).commitAllowingStateLoss();
            handler.obtainMessage(ID_REMOVE_FRAGMENT_MANAGER, fm).sendToTarget();
        }
    
    }

場(chǎng)景3:Fragment的聲明周期管理趴荸,也用到了MessageQueue和handler相關(guān)東西儒溉。

android View中的Looper Handler message

Choreographer 屏幕的點(diǎn)擊 「翻譯過(guò)來(lái)是舞者」
同步屏障,保證不掉幀 vsync
我們現(xiàn)在手機(jī)最低60hz 发钝,也就是刷新率1000ms/60幀率=16幀/ms

消息機(jī)制之同步屏障

線程的消息都是放到同一個(gè)MessageQueue里面顿涣,取消息的時(shí)候是互斥取消息,而 且 酝豪,而添加消息是按照消息的執(zhí)行的先后順序進(jìn)行的排序涛碑,那么問(wèn)題來(lái)了,同一個(gè)時(shí)間范圍內(nèi)的消 息孵淘,如果它是需要立刻執(zhí)行的蒲障,那我們?cè)趺崔k,按照常規(guī)的辦法,我們需要等到隊(duì)列輪詢到我自己的時(shí)候才能執(zhí)行 哦揉阎,那豈不是黃花菜都涼了庄撮。所以,我們需要給緊急需要執(zhí)行的消息一個(gè)綠色通道毙籽,這個(gè)綠色通道就是同步屏障的概 念洞斯。
同步屏障是什么?
屏障的意思即為阻礙,顧名思義坑赡,同步屏障就是阻礙同步消息烙如,只讓異步消息通過(guò)。如何開(kāi)啟同步屏障呢?如下而 已:

 MessageQueue#postSyncBarrier()

源代碼是這樣

 /**
     * Posts a synchronization barrier to the Looper's message queue.
     *
     * Message processing occurs as usual until the message queue encounters the
     * synchronization barrier that has been posted.  When the barrier is encountered,
     * later synchronous messages in the queue are stalled (prevented from being executed)
     * until the barrier is released by calling {@link #removeSyncBarrier} and specifying
     * the token that identifies the synchronization barrier.
     *
     * This method is used to immediately postpone execution of all subsequently posted
     * synchronous messages until a condition is met that releases the barrier.
     * Asynchronous messages (see {@link Message#isAsynchronous} are exempt from the barrier
     * and continue to be processed as usual.
     *
     * This call must be always matched by a call to {@link #removeSyncBarrier} with
     * the same token to ensure that the message queue resumes normal operation.
     * Otherwise the application will probably hang!
     *
     * @return A token that uniquely identifies the barrier.  This token must be
     * passed to {@link #removeSyncBarrier} to release the barrier.
     *
     * @hide
     */
    @TestApi
    public int postSyncBarrier() {
        return postSyncBarrier(SystemClock.uptimeMillis());
    }

    private int postSyncBarrier(long when) {
        // Enqueue a new sync barrier token.
        // We don't need to wake the queue because the purpose of a barrier is to stall it.
        synchronized (this) {
            final int token = mNextBarrierToken++;
            final Message msg = Message.obtain();
            msg.markInUse();
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) { // invariant: p == prev.next
                msg.next = p;
                prev.next = msg;
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token;
        }
    }

可以看到垮衷,Message 對(duì)象初始化的時(shí)候并沒(méi)有給 target 賦值厅翔,因此乖坠, target == null 的 來(lái)源就找到了搀突。上面消 息的插入也做了相應(yīng)的注釋。這樣熊泵,一條target == null 的消息就進(jìn)入了消息隊(duì)列仰迁。
那么,開(kāi)啟同步屏障后顽分,所謂的異步消息又是如何被處理的呢? 如果對(duì)消息機(jī)制有所了解的話徐许,應(yīng)該知道消息的最終處理是在消息輪詢器 Looper#loop() 中,而 loop() 循環(huán)中會(huì)
調(diào)用 MessageQueue#next() 從消息隊(duì)列中進(jìn)行取消息卒蘸。

->上面的「?jìng)魉蛶А筂essage next() 查看源碼

從上面可以看出雌隅,當(dāng)消息隊(duì)列開(kāi)啟同步屏障的時(shí)候(即標(biāo)識(shí)為msg.target == null),消息機(jī)制在處理消息的時(shí) 候缸沃,優(yōu)先處理異步消息恰起。這樣,同步屏障就起到了一種過(guò)濾和優(yōu)先級(jí)的作用趾牧。

同步屏障.png

同步消息的應(yīng)用場(chǎng)景

似乎在日常的應(yīng)用開(kāi)發(fā)中检盼,很少會(huì)用到同步屏障。那么翘单,同步屏障在系統(tǒng)源碼中有哪些使用場(chǎng)景呢?Android 系統(tǒng)中
的 UI 更新相關(guān)的消息即為異步消息吨枉,需要優(yōu)先處理。
比如哄芜,在 View 更新時(shí)貌亭,draw、requestLayout认臊、invalidate 等很多地方都調(diào)用了
ViewRootImpl#scheduleTraversals() 属提,如下:

//ViewRootImpl.java

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
-》【開(kāi)啟同步屏障】
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
-》【發(fā)送異步消息】
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }

postCallback() 最終走到了

Choreographer-》postCallbackDelayedInternal() :



private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        if (DEBUG_FRAMES) {
            Log.d(TAG, "PostCallback: type=" + callbackType
                    + ", action=" + action + ", token=" + token
                    + ", delayMillis=" + delayMillis);
        }

        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);

            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
-》【異步消息】
                msg.setAsynchronous(true);
                mHandler.sendMessageAtTime(msg, dueTime);
            }
        }
    }

這里就開(kāi)啟了同步屏障,并發(fā)送異步消息,由于 UI 更新相關(guān)的消息是優(yōu)先級(jí)最高的冤议,這樣系統(tǒng)就會(huì)優(yōu)先處理這些異 步消息斟薇。
最后,當(dāng)要移除同步屏障的時(shí)候需要調(diào)用 ViewRootImpl#unscheduleTraversals() 恕酸。

void unscheduleTraversals() {
    if (mTraversalScheduled) {
} }

小結(jié)
同步屏障的設(shè)置可以方便地處理那些優(yōu)先級(jí)較高的異步消息堪滨。當(dāng)我們調(diào)用
Handler.getLooper().getQueue().postSyncBarrier() 并設(shè)置消息的 setAsynchronous(true) 時(shí),target 即 為 null 蕊温,也就開(kāi)啟了同步屏障袱箱。當(dāng)在消息輪詢器 Looper 在 loop() 中循環(huán)處理消息時(shí),如若開(kāi)啟了同步屏障义矛,會(huì)優(yōu) 先處理其中的異步消息发笔,而阻礙同步消息。

享元設(shè)計(jì)模式(內(nèi)存復(fù)用)

Message
obtain() 用的一個(gè)對(duì)象池

    public static final Object sPoolSync = new Object();

message 用的是對(duì)象池
當(dāng)結(jié)束后凉翻,會(huì)回收

點(diǎn)擊事件會(huì)轉(zhuǎn)換成message發(fā)送出去

Looper 的 loop死循環(huán) 跟ANR 沒(méi)有關(guān)系了讨,只是ANR發(fā)生 5s handler會(huì)發(fā)出提醒
ActivityThread的 main 方法的主要作用就是做消息循環(huán),一旦退出消息循環(huán)制轰,主線程運(yùn)行完畢前计,那么你的應(yīng)用也就退出了。Android是事件驅(qū)動(dòng)的垃杖,在Looper.loop()中不斷接收事件男杈、處理事件,而Activity的生命周期都依靠于主線程的 Loop.loop() 來(lái)調(diào)度调俘,所以可想而知它的存活周期和 Activity 也是一致的伶棒。當(dāng)沒(méi)有事件需要處理時(shí),主線程就會(huì)阻塞彩库;當(dāng)子線程往消息隊(duì)列發(fā)送消息肤无,并且往管道文件寫(xiě)數(shù)據(jù)時(shí),主線程就被喚醒侧巨。真正會(huì)卡死主線程的操作是在執(zhí)行回調(diào)方法 onCreate/onStart/onResume 等操作的時(shí)間過(guò)長(zhǎng)舅锄,導(dǎo)致掉幀,甚至發(fā)生ANR司忱,looper.loop() 本身不會(huì)導(dǎo)致應(yīng)用卡死皇忿。

主線程Looper從消息隊(duì)列讀取消息,當(dāng)讀完所有消息時(shí)坦仍,主線程阻塞鳍烁。子線程往消息隊(duì)列發(fā)送消息,并且往管道文件寫(xiě)數(shù)據(jù)繁扎,主線程即被喚醒幔荒,從管道文件讀取數(shù)據(jù)糊闽,主線程被喚醒只是為了讀取消息,當(dāng)消息讀取完畢爹梁,再次睡眠右犹。因此loop的循環(huán)并不會(huì)對(duì)CPU性能有過(guò)多的消耗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載姚垃,如需轉(zhuǎn)載請(qǐng)通過(guò)簡(jiǎn)信或評(píng)論聯(lián)系作者念链。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市积糯,隨后出現(xiàn)的幾起案子掂墓,更是在濱河造成了極大的恐慌,老刑警劉巖看成,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件君编,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡川慌,警方通過(guò)查閱死者的電腦和手機(jī)吃嘿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)窘游,“玉大人唠椭,你說(shuō)我怎么就攤上這事跳纳∪淌危” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵寺庄,是天一觀的道長(zhǎng)艾蓝。 經(jīng)常有香客問(wèn)我,道長(zhǎng)斗塘,這世上最難降的妖魔是什么赢织? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮馍盟,結(jié)果婚禮上于置,老公的妹妹穿的比我還像新娘。我一直安慰自己贞岭,他們只是感情好八毯,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著瞄桨,像睡著了一般话速。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芯侥,一...
    開(kāi)封第一講書(shū)人閱讀 52,785評(píng)論 1 314
  • 那天泊交,我揣著相機(jī)與錄音乳讥,去河邊找鬼。 笑死廓俭,一個(gè)胖子當(dāng)著我的面吹牛云石,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播研乒,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼留晚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了告嘲?” 一聲冷哼從身側(cè)響起错维,我...
    開(kāi)封第一講書(shū)人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎橄唬,沒(méi)想到半個(gè)月后赋焕,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仰楚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年隆判,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片僧界。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡侨嘀,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出捂襟,到底是詐尸還是另有隱情咬腕,我是刑警寧澤,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布葬荷,位于F島的核電站涨共,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏宠漩。R本人自食惡果不足惜举反,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扒吁。 院中可真熱鬧火鼻,春花似錦、人聲如沸雕崩。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)晨逝。三九已至蛾默,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捉貌,已是汗流浹背支鸡。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工冬念, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人牧挣。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓急前,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親瀑构。 傳聞我的和親對(duì)象是個(gè)殘疾皇子裆针,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361