Handler機(jī)制你需要知道的一切

1. 前言

安卓在子線程中不能更新UI,所以大部分情況下,我們需要借助Handler切換到主線程中去更新消息.而消息機(jī)制(即Handler那一坨)在安卓中的地位非常非常重要,我們需要詳細(xì)了解其原理.這一塊,學(xué)過(guò)很多次,但是,我覺(jué)得還是再學(xué)億次,寫(xiě)成博客輸出.希望對(duì)大家有所幫助,有一些新的感悟.

2. ThreadLocal工作原理

ThreadLocal主要是可以在不同的線程中存儲(chǔ)不同的數(shù)據(jù),它是將數(shù)據(jù)存儲(chǔ)在線程內(nèi)部的,其他線程無(wú)法訪問(wèn).對(duì)于同一個(gè)ThreadLocal對(duì)象,不同的線程有不同的數(shù)據(jù),這些數(shù)據(jù)互不干擾.比如Handler機(jī)制中的Looper,Looper的作用域是線程,ThreadLocal可以將Looper存儲(chǔ)在線程中,然后其他線程是無(wú)法訪問(wèn)到這個(gè)線程中的Looper的,只供當(dāng)前線程自己內(nèi)部使用.

2.1 ThreadLocal demo

下面簡(jiǎn)單舉個(gè)例子:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private static final ThreadLocal<Integer> INTEGER_THREAD_LOCAL = new ThreadLocal<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        //設(shè)置ThreadLocal里面的數(shù)據(jù)為1
        INTEGER_THREAD_LOCAL.set(1);
        //獲取ThreadLocal里面的數(shù)據(jù)
        Log.w(TAG, "主線程" + INTEGER_THREAD_LOCAL.get());

        new Thread(new Runnable() {
            @Override
            public void run() {
                //獲取ThreadLocal里面的數(shù)據(jù),但是需要注意的是,這里獲取的數(shù)據(jù)是子線程中數(shù)據(jù),因?yàn)闆](méi)有進(jìn)行初始化,這里獲取到的數(shù)據(jù)是null
                Log.w(TAG, "線程1 " + INTEGER_THREAD_LOCAL.get());
            }
        }, "線程1").start();

    }
}

我先在主線程中將INTEGER_THREAD_LOCAL的值設(shè)置為1(相當(dāng)于主線程中的INTEGER_THREAD_LOCAL值為1),然后再開(kāi)啟子線程并在子線程中獲取INTEGER_THREAD_LOCAL的值.因?yàn)樽泳€程中沒(méi)有給INTEGER_THREAD_LOCAL附值,所以是null.

2019-05-19 11:12:54.353 12364-12364/com.xfhy.handlerdemo W/MainActivity: 主線程1
2019-05-19 11:12:54.353 12364-12383/com.xfhy.handlerdemo W/MainActivity: 線程1 null

需要注意到的是INTEGER_THREAD_LOCALfinal static的,這里的ThreadLocal是同一個(gè)對(duì)象,但是在主線程中獲取到的數(shù)據(jù)和在子線程中獲取到的數(shù)據(jù)卻不一樣. 這里的demo也就證明了: ThreadLocal在不同的線程中存儲(chǔ)的數(shù)據(jù),互不干擾,相互獨(dú)立.

2.2 ThreadLocal源碼理解

我們從ThreadLocal的set方法開(kāi)始深入下去(一般讀源碼是從使用處的API開(kāi)始,這樣會(huì)更輕松地理清思路)

public void set(T value) {
    //1. 獲取當(dāng)前線程
    Thread t = Thread.currentThread();
    //2. 獲取當(dāng)前線程的threadLocals屬性,threadLocals是Thread類(lèi)里面的一個(gè)屬性,是ThreadLocalMap類(lèi)型的,專(zhuān)門(mén)用來(lái)存當(dāng)前線程的私有數(shù)據(jù),這些數(shù)據(jù)由ThreadLocal維護(hù)
    ThreadLocalMap map = getMap(t);
    
    //3. 第一次設(shè)置值的時(shí)候map肯定是為null的,初始化了之后map才不為null
    //第一次會(huì)去createMap()
    if (map != null)
        //4. 將當(dāng)前ThreadLocal對(duì)象和value的值存入map中
        map.set(this, value);
    else
        //4. 這里將初始化map,并且將value值放到map中.
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

ThreadLocal在設(shè)置數(shù)據(jù)的時(shí)候,首先是獲取當(dāng)前線程的threadLocals屬性,threadLocals是Thread類(lèi)里面的一個(gè)屬性,是ThreadLocalMap類(lèi)型的,專(zhuān)門(mén)用來(lái)存當(dāng)前線程的私有數(shù)據(jù),這些數(shù)據(jù)由ThreadLocal來(lái)維護(hù)的. 當(dāng)?shù)谝淮卧O(shè)置值的時(shí)候,需要初始化map,并將value值放入map中.下面來(lái)看一下這部分代碼

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//下面是ThreadLocalMap的代碼

/**
 * The table, resized as necessary.
 * table.length MUST always be a power of two.
 * table是ThreadLocalMap里面存儲(chǔ)數(shù)據(jù)的地方,如果在數(shù)組長(zhǎng)度不夠用的時(shí)候,會(huì)擴(kuò)容.
 存儲(chǔ)的方式是靠hash值為數(shù)組的索引,將value放到該索引處.
 */
private Entry[] table;

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    //初始化table數(shù)據(jù)數(shù)組
    table = new Entry[INITIAL_CAPACITY];
    //計(jì)算hash值->存儲(chǔ)數(shù)據(jù)的索引
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

//將value值存入map中,key為T(mén)hreadLocal
private void set(ThreadLocal<?> key, Object value) {
    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

可以看到createMap方法中就是初始化ThreadLocalMap,而ThreadLocalMap的底部其實(shí)是一個(gè)數(shù)組,它是利用hash值來(lái)計(jì)算索引,然后存儲(chǔ)數(shù)據(jù)到該索引處的方式.

此處需要注意的是,我們可以看到ThreadLocal是將數(shù)據(jù)存儲(chǔ)到Thread的一個(gè)threadLocals屬性上面,這個(gè)threadLocals每個(gè)線程獨(dú)有的,那么存儲(chǔ)數(shù)據(jù)肯定互不干擾啊,完美.

3. MessageQueue 消息隊(duì)列

Handler中的消息隊(duì)列,也就是MessageQueue.從名字可以看出這是一個(gè)隊(duì)列,但是它的底層卻是單鏈表結(jié)構(gòu).因?yàn)殒湵斫Y(jié)構(gòu)比較適合插入和刪除操作.這個(gè)MessageQueue的查詢(xún)就是next()方法,它的查詢(xún)伴隨著刪除.

3.1 消息隊(duì)列插入

消息隊(duì)列的插入,對(duì)應(yīng)著的是enqueueMessage方法

boolean enqueueMessage(Message msg, long when) {
    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) {
        ....

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        
        //如果  1. 鏈表為空 || 2. when是0,表示立即需要處理的消息 || 3. 當(dāng)前需要插入的消息比之前的第一個(gè)消息更緊急,在更短的時(shí)間內(nèi)就需要處理
        //滿(mǎn)足上面這3個(gè)條件中的其中一個(gè),那么就是插入在鏈表的頭部
        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;
            //從頭部開(kāi)始,直到找出列表的最后一個(gè)元素,方便鏈表插入
            for (;;) {
                prev = p;
                p = p.next;
                //找到合適的時(shí)間點(diǎn),插入到這里
                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) {
            // 激活消息隊(duì)列去獲取下一個(gè)消息  這里是一個(gè)native方法
            nativeWake(mPtr);
        }
    }
    return true;
}

核心內(nèi)容為消息列表的插入,也就是鏈表的插入,插入數(shù)據(jù)的時(shí)候是有一定規(guī)則的,當(dāng)滿(mǎn)足下面這3個(gè)條件中的其中一個(gè),那么就是插入在鏈表的頭部

  1. 鏈表為空
  2. when是0,表示立即需要處理的消息
  3. 當(dāng)前需要插入的消息比之前的第一個(gè)消息更緊急,在更短的時(shí)間內(nèi)就需要處理

其他情況則是插入在鏈表中的合適的位置,找到一個(gè)合適的時(shí)間點(diǎn).

3.2 消息隊(duì)列查詢(xún)(next)

MessageQueue的next方法,也就是獲取下一個(gè)消息,這個(gè)方法可能會(huì)阻塞,當(dāng)消息隊(duì)列沒(méi)有消息的時(shí)候.直到有消息,然后就會(huì)被喚醒,然后繼續(xù)取消息.

但是這里的阻塞是不會(huì)ANR的,真正導(dǎo)致ANR的是因?yàn)樵趆andleMessage方法中處理消息時(shí)阻塞了主線程太久的時(shí)間.這里的原因,后面再解釋.

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

        //當(dāng)消息隊(duì)列為空時(shí),這里會(huì)導(dǎo)致阻塞,直到有消息加入消息隊(duì)列,才會(huì)恢復(fù)
        //這里是native方法,利用的是linux的epoll機(jī)制阻塞
        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) {
                // 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 {
                    //這里比較關(guān)鍵  取鏈表頭部,獲取這個(gè)消息
                    // 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;
            }

            .....
        }

       ......
    }
}

核心內(nèi)容就是取消息隊(duì)列的第一個(gè)元素(即鏈表的第一個(gè)元素),然后將該Message取出來(lái)之后,將它從消息隊(duì)列中刪除.

4. Looper

Looper在消息機(jī)制中主要扮演著消息循環(huán)的角色,有消息來(lái)了,Looper就取出來(lái),分發(fā).沒(méi)有消息,Looper就阻塞在那里,直到有消息為止.

4.1 Looper初始化

先來(lái)看一下,Looper的構(gòu)造方法

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

這個(gè)構(gòu)造方法是私有化的,只能在內(nèi)部調(diào)用,直接在里面初始化了MessageQueue和獲取當(dāng)前線程.構(gòu)造方法只會(huì)在prepare方法中被調(diào)用.

public static void prepare() {
    prepare(true);
}

//sThreadLocal是用`static final`修飾的,意味著sThreadLocal只有一個(gè),但是它卻可以在不同的線程中存儲(chǔ)不同的Looper,妙啊
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

private static void prepare(boolean quitAllowed) {
    //如果說(shuō)當(dāng)前線程之前初始化過(guò)ThreadLocal,里面有Looper,那么就報(bào)錯(cuò)
    //意思就是prepare方法只能調(diào)用一次
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //初始化ThreadLocal,將一個(gè)Looper存入其中
    sThreadLocal.set(new Looper(quitAllowed));
}

private static Looper sMainLooper;
//這個(gè)方法是主線程中調(diào)用的,準(zhǔn)備主線程的Looper.也是只能調(diào)用一次.
public static void prepareMainLooper() {
    //先準(zhǔn)備一下
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        //將初始化之后的Looper賦值給sMainLooper,sMainLooper是static的,可能是為了方便使用吧
        sMainLooper = myLooper();
    }
}

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

prepare方法的職責(zé)是初始化ThreadLocal,將Looper存儲(chǔ)在其中,一個(gè)線程只能有一個(gè)Looper,不能重復(fù)初始化.sThreadLocal是用static final修飾的,意味著sThreadLocal只有一個(gè),但是它卻可以在不同的線程中存儲(chǔ)不同的Looper.而且官方還提供了主線程初始化Looper的專(zhuān)用方法prepareMainLooper.主線程就是主角,還單獨(dú)把它的Looper存到靜態(tài)的sMainLooper中.

4.2 Looper#loop

下面開(kāi)始進(jìn)入Looper的核心方法loop(),我們知道loop方法就是死循環(huán)不斷得從MessageQueue中去取數(shù)據(jù).看看方法中的一些細(xì)節(jié).

/**
 * Run the message queue in this thread. Be sure to call
 * {@link #quit()} to end the loop.
 */
public static void loop() {
    //1. 首先是獲取當(dāng)前線程的Looper  穩(wěn),不同的線程,互不干擾
    final Looper me = myLooper();
    
    //2. 如果當(dāng)前線程沒(méi)有初始化,那肯定是要報(bào)錯(cuò)的
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    
    //3. 取出當(dāng)前線程Looper中存放的MessageQueue
    final MessageQueue queue = me.mQueue;

    .....
    for (;;) {
        //4. 從MessageQueue中取消息,當(dāng)然 這里是可能被阻塞的,如果MessageQueue中沒(méi)有消息可以取的話
        Message msg = queue.next(); // might block
        
        //5. 如果消息隊(duì)列想退出,并且MessageQueue中沒(méi)有消息了,那么這里的msg肯定是null
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        .....
        //6. 注意啦,這里開(kāi)始分發(fā)當(dāng)前從消息隊(duì)列中取出來(lái)的消息
        msg.target.dispatchMessage(msg);
        ......
    }
}

loop方法非常重要,它首先取到當(dāng)前線程的Looper,再?gòu)腖ooper中獲取MessageQueue,開(kāi)啟一個(gè)死循環(huán),從MessageQueue的next方法中獲取新的Message.但是在next方法調(diào)用的過(guò)程中是可能被阻塞的,這里是利用了linux的epoll機(jī)制.取到了消息之后分發(fā)下去.分發(fā)給Handler的handleMessage方法進(jìn)行處理. 然后又開(kāi)始了一個(gè)新的輪回,繼續(xù)取新的消息(也可能是阻塞在那里等).

下面來(lái)看一下消息的分發(fā)

//Message里面的代碼

//Message里的target其實(shí)就是發(fā)送該消息的那個(gè)Handler,666
Handler target;
//下一個(gè)消息的引用
Message next;
//Handler里面的代碼
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

兄弟萌,它來(lái)啦,還是那個(gè)熟悉的handleMessage方法,在Looper的loop方法中由Message自己通過(guò)Message里面的target(handler)調(diào)用該Handler自己的handleMessage方法.完成了消息的分發(fā). 如果這里有Callback的話,就通過(guò)Callback接口分發(fā)消息.

5. Handler

Handler的作用其實(shí)就是發(fā)送消息,然后接收消息.Handler中任何的發(fā)送消息的方法最后都會(huì)調(diào)用sendMessageAtTime方法,我們仔細(xì)觀摩一下

public boolean sendMessageAtTime(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);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

sendMessageAtTime方法很簡(jiǎn)單,其實(shí)就是將消息插入MessageQueue.而在Message插入MessageQueue的過(guò)程之前,先將Handler的引用存入Message中,方便待會(huì)兒分發(fā)消息事件,機(jī)智機(jī)智!

6. 用一句話總結(jié)一下安卓的消息機(jī)制

在安卓消息機(jī)制中,ThreadLocal拿來(lái)存儲(chǔ)Looper,而MessageQueue是存儲(chǔ)在Looper中的.所以我們可以在子線程中通過(guò)主線程的Handler發(fā)送消息,而Looper(主線程中的)在主線程中取出消息,分發(fā)給主線程的Handler的handleMessage方法.

7. 消息機(jī)制在主線程中的應(yīng)用

7.1 關(guān)于主線程中的死循環(huán)

我們知道ActivityThread其實(shí)就是我們的主線程,首先我們來(lái)看一段代碼,ActivityThread的main方法:

public static void main(String[] args) {
    ......
    
    //注意看,在main方法的開(kāi)始,在主線程中就準(zhǔn)備好了主線程中的Looper,存入ThreadLocal中.所以我們平時(shí)使用Handler的時(shí)候并沒(méi)有調(diào)用prepare方法也不會(huì)報(bào)錯(cuò)
    Looper.prepareMainLooper();

    ......
    //直接在主線程中調(diào)用了loop方法,并且陷入死循環(huán)中,不斷地取消息,不斷地處理消息,無(wú)消息時(shí)就阻塞.  
    //嘿,你還別說(shuō),這里這個(gè)方法還必須要死循環(huán)下去才好,不然就會(huì)執(zhí)行到下面的throw new RuntimeException語(yǔ)句報(bào)出錯(cuò)誤
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

主線程一直處在一個(gè)Looper的loop循環(huán)中,有消息就會(huì)去處理.無(wú)消息,則阻塞.

7.2 主線程死循環(huán)到底是要接收和處理什么消息?

有什么騷東西非要進(jìn)行死循環(huán)才能處理呢?首先我們想想,既然ActivityThread開(kāi)啟了Looper的loop,那么肯定有Handler來(lái)接收和處理消息,我們一探究竟:

private class H extends Handler {
    public static final int LAUNCH_ACTIVITY = 100;
    public static final int PAUSE_ACTIVITY = 101;
    public static final int PAUSE_ACTIVITY_FINISHING = 102;
    public static final int STOP_ACTIVITY_SHOW = 103;
    public static final int STOP_ACTIVITY_HIDE = 104;
    public static final int SHOW_WINDOW = 105;
    public static final int HIDE_WINDOW = 106;
    public static final int RESUME_ACTIVITY = 107;
    public static final int SEND_RESULT = 108;
    public static final int DESTROY_ACTIVITY = 109;
    public static final int BIND_APPLICATION = 110;
    public static final int EXIT_APPLICATION = 111;
    public static final int NEW_INTENT = 112;
    public static final int RECEIVER = 113;
    public static final int CREATE_SERVICE = 114;
    public static final int SERVICE_ARGS = 115;
    public static final int STOP_SERVICE = 116;
    ...
}

名場(chǎng)面,上面就是API 28以前ActivityThread.H的老樣子,為什么是API 28以前?因?yàn)樵贏PI 28中重構(gòu)了H類(lèi),把100到109這10個(gè)用于Activity的消息,都合并為159這個(gè)消息政恍,消息名為EXECUTE_TRANSACTION(抽象為ClientTransactionItem,有興趣了解的看這里)采章。

在H類(lèi)中定義了很多消息類(lèi)型,包含了安卓四大組件的啟動(dòng)和停止.ActivityThread通過(guò)ApplicationThread與AMS進(jìn)行進(jìn)程間通信,AMS完成ActivityThread的請(qǐng)求后會(huì)回調(diào)ApplicationThread中的Binder方法,然后ApplicationThread會(huì)向H發(fā)送消息,H收到消息就開(kāi)始在主線程中執(zhí)行,開(kāi)始執(zhí)行諸如Activity的啟動(dòng)停止等動(dòng)作,以上就是主線程的消息循環(huán)模型.

既然我們知道了主線程是這樣啟動(dòng)Activity的,那么我們是不是可以搞點(diǎn)騷操作???俗稱(chēng)黑科技的插件化:我們Hook掉H類(lèi)的mCallback對(duì)象,攔截這個(gè)對(duì)象的handleMessage方法。在此之前陈症,我們把插件中的Activity替換為StubActtivty逛薇,那么現(xiàn)在狼犯,我們攔截到handleMessage方法凡纳,再把StubActivity換回為插件中的Activity.當(dāng)前這只是API 28之前的操作,更多詳情請(qǐng)看這里

8. 主線程為什么沒(méi)有被loop阻塞

既然主線程中的main方法內(nèi)調(diào)用了Looper的loop方法不斷地死循環(huán)取消息,而且當(dāng)消息隊(duì)列為空的時(shí)候還會(huì)被阻塞.那為什么主線程中當(dāng)沒(méi)有消息的時(shí)候怎么不卡呢?

此處引出一國(guó)外網(wǎng)友的回答,短小精湛.問(wèn)題回答原地址

簡(jiǎn)短版答案:
nativePollOnce方法是用來(lái)等待下一個(gè)消息可用時(shí)的,下一個(gè)消息可用則不會(huì)再繼續(xù)阻塞,如果在這個(gè)調(diào)用中花費(fèi)的時(shí)間很長(zhǎng)窃植,那你的主(UI)線程沒(méi)有真正的工作要做,并且等待下一個(gè)事件處理荐糜。沒(méi)必要擔(dān)心阻塞問(wèn)題巷怜。

完整版的答案:
因?yàn)橹骶€程負(fù)責(zé)繪制UI和處理各種事件,所以Runnable有一個(gè)處理所有這些事件的循環(huán)暴氏。循環(huán)由Looper管理延塑,其工作非常簡(jiǎn)單:它處理MessageQueue中的所有消息。消息被添加到隊(duì)列中答渔,例如響應(yīng)輸入事件关带,幀渲染回調(diào)甚至您自己的Handler.post調(diào)用。有時(shí)主線程沒(méi)有工作要做(即隊(duì)列中沒(méi)有消息)研儒,這可能發(fā)生在例如剛完成渲染單幀后(線程剛剛繪制了一幀并準(zhǔn)備好下一幀豫缨,只需等待一段時(shí)間)。 MessageQueue類(lèi)中的兩個(gè)Java方法對(duì)我們來(lái)說(shuō)很有趣:Message next()和boolean enqueueMessage(Message端朵,long)好芭。消息next(),顧名思義冲呢,接收并返回隊(duì)列中的下一條消息舍败。如果隊(duì)列為空(并且沒(méi)有任何內(nèi)容可以返回),則該方法調(diào)用native void nativePollOnce(long敬拓,int)邻薯,該塊將阻塞,直到添加新消息乘凸。此時(shí)你可能會(huì)問(wèn)nativePollOnce如何知道何時(shí)醒來(lái)厕诡。這是一個(gè)非常好的問(wèn)題。將Message添加到隊(duì)列時(shí)营勤,框架會(huì)調(diào)用enqueueMessage方法灵嫌,該方法不僅會(huì)將消息插入隊(duì)列,還會(huì)調(diào)用native static void nativeWake(long)葛作,如果需要喚醒隊(duì)列的話寿羞。 nativePollOnce和nativeWake的核心魔力發(fā)生在native(實(shí)際上是C ++)代碼中。 Native MessageQueue使用名為epoll的Linux系統(tǒng)調(diào)用赂蠢,該調(diào)用允許監(jiān)視IO事件的文件描述符绪穆。 nativePollOnce在某個(gè)文件描述符上調(diào)用epoll_wait,而nativeWake寫(xiě)入描述符虱岂,這是IO操作之一玖院,epoll_wait等待。然后內(nèi)核從等待狀態(tài)中取出epoll等待線程第岖,并且線程繼續(xù)處理新消息司恳。如果您熟悉Java的Object.wait()和Object.notify()方法,您可以想象nativePollOnce是Object.wait()和NativeWake for Object.notify()的粗略等價(jià)物绍傲,因?yàn)樗鼈兊膶?shí)現(xiàn)完全不同:nativePollOnce使用epoll扔傅,Object.wait()使用futex Linux調(diào)用。值得注意的是烫饼,nativePollOnce和Object.wait()都不會(huì)浪費(fèi)CPU周期猎塞,因?yàn)楫?dāng)線程進(jìn)入任一方法時(shí),它會(huì)因線程調(diào)度而被禁用杠纵。如果這些方法實(shí)際上浪費(fèi)了CPU周期荠耽,那么所有空閑應(yīng)用程序?qū)⑹褂?00%的CPU,加熱并降低設(shè)備的速度比藻。

翻譯的不是很好,英語(yǔ)好的同學(xué)還是看原版吧,,,,,,,,,

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末铝量,一起剝皮案震驚了整個(gè)濱河市倘屹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌慢叨,老刑警劉巖纽匙,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異拍谐,居然都是意外死亡烛缔,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)轩拨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)践瓷,“玉大人,你說(shuō)我怎么就攤上這事亡蓉≡未洌” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵砍濒,是天一觀的道長(zhǎng)崖面。 經(jīng)常有香客問(wèn)我,道長(zhǎng)梯影,這世上最難降的妖魔是什么巫员? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮甲棍,結(jié)果婚禮上简识,老公的妹妹穿的比我還像新娘。我一直安慰自己感猛,他們只是感情好七扰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著陪白,像睡著了一般颈走。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咱士,一...
    開(kāi)封第一講書(shū)人閱讀 51,541評(píng)論 1 305
  • 那天立由,我揣著相機(jī)與錄音,去河邊找鬼序厉。 笑死锐膜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的弛房。 我是一名探鬼主播道盏,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了荷逞?” 一聲冷哼從身側(cè)響起媒咳,我...
    開(kāi)封第一講書(shū)人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎种远,沒(méi)想到半個(gè)月后涩澡,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡院促,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了斧抱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片常拓。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖辉浦,靈堂內(nèi)的尸體忽然破棺而出弄抬,到底是詐尸還是另有隱情,我是刑警寧澤宪郊,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布掂恕,位于F島的核電站,受9級(jí)特大地震影響弛槐,放射性物質(zhì)發(fā)生泄漏懊亡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一乎串、第九天 我趴在偏房一處隱蔽的房頂上張望店枣。 院中可真熱鬧,春花似錦叹誉、人聲如沸鸯两。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)钧唐。三九已至,卻和暖如春匠襟,著一層夾襖步出監(jiān)牢的瞬間钝侠,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工酸舍, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留机错,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓父腕,卻偏偏與公主長(zhǎng)得像弱匪,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355

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