溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Handler的原理有哪些

發(fā)布時(shí)間:2021-10-11 22:02:38 來(lái)源:億速云 閱讀:250 作者:iii 欄目:編程語(yǔ)言

本篇內(nèi)容主要講解“Handler的原理有哪些”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Handler的原理有哪些”吧!

總流程

開(kāi)頭需要建立個(gè)handler作用的總體印象,下面畫(huà)了一個(gè)總體的流程圖

Handler的原理有哪些

從上面的流程圖可以看出,總體上是分幾個(gè)大塊的

  • Looper.prepare()、Handler()、Looper.loop() 總流程

  • 收發(fā)消息

  • 分發(fā)消息

相關(guān)知識(shí)點(diǎn)大概涉及到這些,下面詳細(xì)講解下!

  • 需要詳細(xì)的查看該思維導(dǎo)圖,請(qǐng)右鍵下載后查看

Handler的原理有哪些

使用

先來(lái)看下使用,不然源碼,原理圖搞了一大堆,一時(shí)想不起怎么用的,就尷尬了

使用很簡(jiǎn)單,此處僅做個(gè)展示,大家可以熟悉下

演示代碼盡量簡(jiǎn)單是為了演示,關(guān)于靜態(tài)內(nèi)部類持有弱引用或者銷毀回調(diào)中清空消息隊(duì)列之類,就不在此處展示了

  • 來(lái)看下消息處理的分發(fā)方法:dispatchMessage(msg)

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

從上面源碼可知,handler的使用總的來(lái)說(shuō),分倆大類,細(xì)分三小類

  • 收發(fā)消息一體

    • handleCallback(msg)

  • 收發(fā)消息分開(kāi)

    • mCallback.handleMessage(msg)

    • handleMessage(msg)

收發(fā)一體

  • handleCallback(msg)

  • 使用post形式,收發(fā)都是一體,都在post()方法中完成,此處不需要?jiǎng)?chuàng)建Message實(shí)例等,post方法已經(jīng)完成這些操作

public class MainActivity extends AppCompatActivity {
    private TextView msgTv;
    private Handler mHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        msgTv = findViewById(R.id.tv_msg);

      	//消息收發(fā)一體
        new Thread(new Runnable() {
            @Override public void run() {
                String info = "第一種方式";
                mHandler.post(new Runnable() {
                    @Override public void run() {
                        msgTv.setText(info);
                    }
                });
            }
        }).start();
    }
}

收發(fā)分開(kāi)

mCallback.handleMessage(msg)

  • 實(shí)現(xiàn)Callback接口

public class MainActivity extends AppCompatActivity {
    private TextView msgTv;
    private Handler mHandler = new Handler(new Handler.Callback() {
        //接收消息,刷新UI
        @Override public boolean handleMessage(@NonNull Message msg) {
            if (msg.what == 1) {
                msgTv.setText(msg.obj.toString());
            }
            //false 重寫(xiě)Handler類的handleMessage會(huì)被調(diào)用,  true 不會(huì)被調(diào)用
            return false;
        }
    });

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        msgTv = findViewById(R.id.tv_msg);

        //發(fā)送消息
        new Thread(new Runnable() {
            @Override public void run() {
                Message message = Message.obtain();
                message.what = 1;
                message.obj = "第二種方式 --- 1"; 
                mHandler.sendMessage(message);
            }
        }).start();
    }
}

handleMessage(msg)

  • 重寫(xiě)Handler類的handlerMessage(msg)方法

public class MainActivity extends AppCompatActivity {
    private TextView msgTv;
    private Handler mHandler = new Handler() {
        //接收消息,刷新UI
        @Override public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            if (msg.what == 1) {
                msgTv.setText(msg.obj.toString());
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        msgTv = findViewById(R.id.tv_msg);

        //發(fā)送消息
        new Thread(new Runnable() {
            @Override public void run() {
                Message message = Message.obtain();
                message.what = 1;
                message.obj = "第二種方式 --- 2";
                mHandler.sendMessage(message);
            }
        }).start();
    }
}

prepare和loop

大家肯定有印象,在子線程和子線程的通信中,就必須在子線程中初始化Handler,必須這樣寫(xiě)

  • prepare在前,loop在后,固化印象了

new Thread(new Runnable() {
    @Override public void run() {
        Looper.prepare();
        Handler handler = new Handler();
        Looper.loop();
    }
});
  • 為啥主線程不需要這樣寫(xiě),聰明你肯定想到了,在入口出肯定做了這樣的事

ActivityThread.java
...
public static void main(String[] args) {
    ...
    //主線程Looper
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    //主線程的loop開(kāi)始循環(huán)
    Looper.loop();
	...
}
...

為什么要使用prepare和loop?我畫(huà)了個(gè)圖,先讓大家有個(gè)整體印象

Handler的原理有哪些

  • 上圖的流程,鄙人感覺(jué)整體畫(huà)的還是比較清楚的

  • 總結(jié)下就是

    • Looper.prepare():生成Looper對(duì)象,set在ThreadLocal里

    • handler構(gòu)造函數(shù):通過(guò)Looper.myLooper()獲取到ThreadLocal的Looper對(duì)象

    • Looper.loop():內(nèi)部有個(gè)死循環(huán),開(kāi)始事件分發(fā)了;這也是最復(fù)雜,干活最多的方法

具體看下每個(gè)步驟的源碼,這里也會(huì)標(biāo)定好鏈接,方便大家隨時(shí)過(guò)去查看

  • Looper.prepare()

    • 可以看見(jiàn),一個(gè)線程內(nèi),只能使用一次prepare(),不然會(huì)報(bào)異常的

Looper.java
...
 public static void prepare() {
    prepare(true);
}

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

    • 這里通過(guò)Looper.myLooper() ---> sThreadLocal.get()拿到了Looper實(shí)例

Handler.java
...
@Deprecated
public Handler() {
    this(null, false);
}

public Handler(@Nullable 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();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
            + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
...
Looper.java
...
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
...
  • Looper.loop():該方法分析,在分發(fā)消息里講

    • 精簡(jiǎn)了大量源碼,詳細(xì)的可以點(diǎn)擊上面方法名

    • Message msg = queue.next():遍歷消息

    • msg.target.dispatchMessage(msg):分發(fā)消息

    • msg.recycleUnchecked():消息回收,進(jìn)入消息池

Looper.java
...
public static void loop() {
    final Looper me = myLooper();
    
    ...
    
    final MessageQueue queue = me.mQueue;

   	...

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

        ...
        
        try {
            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);
            }
        }
     
        ....

        msg.recycleUnchecked();
    }
}
...

收發(fā)消息

收發(fā)消息的操作口都在Handler里,這是我們最直觀的接觸的點(diǎn)

下方的思維導(dǎo)圖整體做了個(gè)概括

Handler的原理有哪些

前置知識(shí)

在說(shuō)發(fā)送和接受消息之前,必須要先解釋下,Message中一個(gè)很重要的屬性:when

when這個(gè)變量是Message中的,發(fā)送消息的時(shí)候,我們一般是不會(huì)設(shè)置這個(gè)屬性的,實(shí)際上也無(wú)法設(shè)置,只有內(nèi)部包才能訪問(wèn)寫(xiě)的操作;將消息加入到消息隊(duì)列的時(shí)候會(huì)給發(fā)送的消息設(shè)置該屬性。消息加入消息隊(duì)列方法:enqueueMessage(...)

在我們使用sendMessage發(fā)送消息的時(shí)候,實(shí)際上也會(huì)調(diào)用sendMessageDelayed延時(shí)發(fā)送消息發(fā)放,不過(guò)此時(shí)傳入的延時(shí)時(shí)間會(huì)默認(rèn)為0,來(lái)看下延時(shí)方法:sendMessageDelayed

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

這地方調(diào)用了sendMessageAtTime方法,此處!做了一個(gè)時(shí)間相加的操作:SystemClock.uptimeMillis() + delayMillis

  • SystemClock.uptimeMillis():這個(gè)方法會(huì)返回一個(gè)毫秒數(shù)值,返回的是,打開(kāi)設(shè)備到此刻所消耗的毫秒時(shí)間,這很明顯是個(gè)相對(duì)時(shí)間刻!

  • delayMillis:就是我們發(fā)送的延時(shí)毫秒數(shù)值

后面會(huì)將這個(gè)時(shí)間刻賦值給when:when = SystemClock.uptimeMillis() + delayMillis

說(shuō)明when代表的是開(kāi)機(jī)到現(xiàn)在的一個(gè)時(shí)間刻,通俗的理解,when可以理解為:現(xiàn)實(shí)時(shí)間的某個(gè)現(xiàn)在或未來(lái)的時(shí)刻(實(shí)際上when是個(gè)相對(duì)時(shí)刻,相對(duì)點(diǎn)就是開(kāi)機(jī)的時(shí)間點(diǎn))

發(fā)送消息

發(fā)送消息涉及到倆個(gè)方法:post(...)和sendMessage(...)

  • post(Runnable):發(fā)送和接受消息都在post中完成

  • sendMessage(msg):需要自己傳入Message消息對(duì)象

  • 看下源碼

    • 此方法給msg的target賦值當(dāng)前handler之后,才進(jìn)行將消息添加的消息隊(duì)列的操作

    • msg.setAsynchronous(true):設(shè)置Message屬性為異步,默認(rèn)都為同步;設(shè)置為異步的條件,需要手動(dòng)在Handler構(gòu)造方法里面設(shè)置

    • 使用post會(huì)自動(dòng)會(huì)通過(guò)getPostMessage方法創(chuàng)建Message對(duì)象

    • 在enqueueMessage中將生成的Message加入消息隊(duì)列,注意

Handler.java
...
//post
public final boolean post(@NonNull Runnable r) {
    return  sendMessageDelayed(getPostMessage(r), 0);
}

//生成Message對(duì)象
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

//sendMessage方法
public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

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

///將Message加入詳細(xì)隊(duì)列 
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
                               long uptimeMillis) {
    //設(shè)置target
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        //設(shè)置為異步方法
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
...
  • enqueueMessage(...):精簡(jiǎn)了一些代碼,完整代碼,可點(diǎn)擊左側(cè)方法名

    • A,B,C消息依次發(fā)送,三者分邊延時(shí):3秒,1秒,2秒 { A(3000)、B(1000)、C(2000) }

    • 這是一種理想情況:三者依次進(jìn)入,進(jìn)入之間的時(shí)間差小到忽略,這是為了方便演示和說(shuō)明

    • 這種按照時(shí)間遠(yuǎn)近的循序排列,可以保證未延時(shí)或者延時(shí)時(shí)間較小的消息,能夠被及時(shí)執(zhí)行

    • 在消息隊(duì)列中的排列為:B ---> C ---> A

    • mMessage為空,傳入的msg則為消息鏈表頭,next置空

    • mMessage不為空、消息隊(duì)列中沒(méi)有延時(shí)消息的情況:從當(dāng)前分發(fā)位置移到鏈表尾,將傳入的msg插到鏈表尾部,next置空

    • Message通過(guò)enqueueMessage加入消息隊(duì)列

    • 請(qǐng)明確:when = SystemClock.uptimeMillis() + delayMillis,when代表的是一個(gè)時(shí)間刻度,消息進(jìn)入到消息隊(duì)列,是按照時(shí)間刻度排列的,時(shí)間刻度按照從小到大排列,也就是說(shuō)消息在消息隊(duì)列中:按照從現(xiàn)在到未來(lái)的循序排隊(duì)

    • 這地方有幾種情況,記錄下:mMessage為當(dāng)前消息分發(fā)到的消息位置

    • mMessage不為空、含有延時(shí)消息的情況:舉個(gè)例子

MessageQueue.java
...
boolean enqueueMessage(Message msg, long when) {
   ...

    synchronized (this) {
        ...

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

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}
...
  • 來(lái)看下發(fā)送的消息插入消息隊(duì)列的圖示

Handler的原理有哪些

接收消息

接受消息相對(duì)而言就簡(jiǎn)單多

  • dispatchMessage(msg):關(guān)鍵方法呀

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

    • 觸發(fā)條件:Message消息中實(shí)現(xiàn)了handleCallback回調(diào)

    • 現(xiàn)在基本上只能使用post()方法了,setCallback(Runnable r) 被表明為@UnsupportedAppUsage,被hide了,沒(méi)法調(diào)用,如果使用反射倒是可以調(diào)用,但是沒(méi)必要。。。

  • mCallback.handleMessage(msg)

    • 使用sendMessage方法發(fā)送消息(必須)

    • 實(shí)現(xiàn)Handler的Callback回調(diào)

    • 觸發(fā)條件

    • 分發(fā)的消息,會(huì)在Handler中實(shí)現(xiàn)的回調(diào)中分發(fā)

  • handleMessage(msg)

    • 使用sendMessage方法發(fā)送消息(必須)

    • 未實(shí)現(xiàn)Handler的Callback回調(diào)

    • 實(shí)現(xiàn)了Handler的Callback回調(diào),返回值為false(mCallback.handleMessage(msg))

    • 觸發(fā)條件

    • 需要重寫(xiě)Handler類的handlerMessage方法

分發(fā)消息

消息分發(fā)是在loop()中完成的,來(lái)看看loop()這個(gè)重要的方法

  • Looper.loop():精簡(jiǎn)了巨量源碼,詳細(xì)的可以點(diǎn)擊左側(cè)方法名

    • Message msg = queue.next():遍歷消息

    • msg.target.dispatchMessage(msg):分發(fā)消息

    • msg.recycleUnchecked():消息回收,進(jìn)入消息池

Looper.java
...
public static void loop() {
    final Looper me = myLooper();
    ...
    final MessageQueue queue = me.mQueue;
   	...
    for (;;) {
        //遍歷消息池,獲取下一可用消息
        Message msg = queue.next(); // might block
        ...
        try {
            //分發(fā)消息
            msg.target.dispatchMessage(msg);
            ...
        } catch (Exception exception) {
            ...
        } finally {
            ...
        }
        ....
        //回收消息,進(jìn)圖消息池
        msg.recycleUnchecked();
    }
}
...

遍歷消息

遍歷消息的關(guān)鍵方法肯定是下面這個(gè)

  • Message msg = queue.next():Message類中的next()方法;當(dāng)然這必須要配合外層for(無(wú)限循環(huán))來(lái)使用,才能遍歷消息隊(duì)列

來(lái)看看這個(gè)Message中的next()方法吧

  • next():精簡(jiǎn)了一些源碼,完整的點(diǎn)擊左側(cè)方法名

MessageQueue.java
...
Message next() {
    final long ptr = mPtr;
    ...

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
  		...
        //阻塞,除非到了超時(shí)時(shí)間或者喚醒
        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;
            // 這是關(guān)于同步屏障(SyncBarrier)的知識(shí),放在同步屏障欄目講
            if (msg != null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            
            if (msg != null) {
                if (now < msg.when) {
                    //每個(gè)消息處理有耗時(shí)時(shí)間,之間存在一個(gè)時(shí)間間隔(when是將要執(zhí)行的時(shí)間點(diǎn))。
                    //如果當(dāng)前時(shí)刻還沒(méi)到執(zhí)行時(shí)刻(when),計(jì)算時(shí)間差值,傳入nativePollOnce定義喚醒阻塞的時(shí)間
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    mBlocked = false;
                    //該操作是把異步消息單獨(dú)從消息隊(duì)列里面提出來(lái),然后返回,返回之后,該異步消息就從消息隊(duì)列里面剔除了
                    //mMessage仍處于未分發(fā)的同步消息位置
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    //返回符合條件的Message
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            //這是處理調(diào)用IdleHandler的操作,有幾個(gè)條件
        	//1、當(dāng)前消息隊(duì)列為空(mMessages == null)
            //2、已經(jīng)到了可以分發(fā)下一消息的時(shí)刻(now < mMessages.when)
            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);
        }

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

總結(jié)下源碼里面表達(dá)的意思

  1. next()內(nèi)部是個(gè)死循環(huán),你可能會(huì)疑惑,只是拿下一節(jié)點(diǎn)的消息,為啥要死循環(huán)?

    • 為了執(zhí)行延時(shí)消息以及同步屏障等等,這個(gè)死循環(huán)是必要的

  2. nativePollOnce阻塞方法:到了超時(shí)時(shí)間(nextPollTimeoutMillis)或者通過(guò)喚醒方式(nativeWake),會(huì)解除阻塞狀態(tài)

    • nextPollTimeoutMillis大于等于零,會(huì)規(guī)定在此段時(shí)間內(nèi)休眠,然后喚醒

    • 消息隊(duì)列為空時(shí),nextPollTimeoutMillis為-1,進(jìn)入阻塞;重新有消息進(jìn)入隊(duì)列,插入頭結(jié)點(diǎn)的時(shí)候會(huì)觸發(fā)nativeWake喚醒方法

  3. 如果 msg.target == null為零,會(huì)進(jìn)入同步屏障狀態(tài)

    • 會(huì)將msg消息死循環(huán)到末尾節(jié)點(diǎn),除非碰到異步方法

    • 如果碰到同步屏障消息,理論上會(huì)一直死循環(huán)上面操作,并不會(huì)返回消息,除非,同步屏障消息被移除消息隊(duì)列

  4. 當(dāng)前時(shí)刻和返回消息的when判定

    • 消息when代表的時(shí)刻:一般都是發(fā)送消息的時(shí)刻,如果是延時(shí)消息,就是 發(fā)送時(shí)刻+延時(shí)時(shí)間

    • 當(dāng)前時(shí)刻小于返回消息的when:進(jìn)入阻塞,計(jì)算時(shí)間差,給nativePollOnce設(shè)置超時(shí)時(shí)間,超時(shí)時(shí)間一到,解除阻塞,重新循環(huán)取消息

    • 當(dāng)前時(shí)刻大于返回消息的when:獲取可用消息返回

  5. 消息返回后,會(huì)將mMessage賦值為返回消息的下一節(jié)點(diǎn)(只針對(duì)不涉及同步屏障的同步消息)

這里簡(jiǎn)單的畫(huà)了個(gè)流程圖

Handler的原理有哪些

分發(fā)消息

分發(fā)消息主要的代碼是: msg.target.dispatchMessage(msg);

也就是說(shuō)這是Handler類中的dispatchMessage(msg)方法

  • dispatchMessage(msg)

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

可以看到,這里的代碼,在收發(fā)消息欄目的接受消息那塊已經(jīng)說(shuō)明過(guò)了,這里就無(wú)須重復(fù)了

消息池

msg.recycleUnchecked()是處理完成分發(fā)的消息,完成分發(fā)的消息并不會(huì)被回收掉,而是會(huì)進(jìn)入消息池,等待被復(fù)用

  • recycleUnchecked():回收消息的代碼還是蠻簡(jiǎn)單的,來(lái)分析下

    • 默認(rèn)最大容量為50: MAX_POOL_SIZE = 50

    • 首先會(huì)將當(dāng)前已經(jīng)分發(fā)處理的消息,相關(guān)屬性全部重置,flags也標(biāo)志可用

    • 消息池的頭結(jié)點(diǎn)會(huì)賦值為當(dāng)前回收消息的下一節(jié)點(diǎn),當(dāng)前消息成為消息池頭結(jié)點(diǎn)

    • 簡(jiǎn)言之:回收消息插入消息池,當(dāng)做頭結(jié)點(diǎn)

    • 需要注意的是:消息池有最大的容量,如果消息池大于等于默認(rèn)設(shè)置的最大容量,將不再接受回收消息入池

Message.java
...
void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

來(lái)看下消息池回收消息圖示

Handler的原理有哪些

既然有將已使用的消息回收到消息池的操作,那肯定有獲取消息池里面消息的方法了

  • obtain():代碼很少,來(lái)看看

    • 如果消息池不為空:直接取消息池的頭結(jié)點(diǎn),被取走頭結(jié)點(diǎn)的下一節(jié)點(diǎn)成為消息池的頭結(jié)點(diǎn)

    • 如果消息池為空:直接返回新的Message實(shí)例

Message.java
...
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

來(lái)看下從消息池取一個(gè)消息的圖示

Handler的原理有哪些

IdleHandler

在MessageQueue類中的next方法里,可以發(fā)現(xiàn)有關(guān)于對(duì)IdleHandler的處理,大家可千萬(wàn)別以為它是什么Handler特殊形式之類,這玩意就是一個(gè)interface,里面抽象了一個(gè)方法,結(jié)構(gòu)非常的簡(jiǎn)單

  • next():精簡(jiǎn)了大量源碼,只保留IdleHandler處理的相關(guān)邏輯;完整的點(diǎn)擊左側(cè)方法名

MessageQueue.java
...
Message next() {
    final long ptr = mPtr;
    ...

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
  		...
        //阻塞,除非到了超時(shí)時(shí)間或者喚醒
        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;
			...
            //這是處理調(diào)用IdleHandler的操作,有幾個(gè)條件
        	//1、當(dāng)前消息隊(duì)列為空(mMessages == null)
            //2、未到到了可以分發(fā)下一消息的時(shí)刻(now < mMessages.when)
			//3、pendingIdleHandlerCount < 0表明:只會(huì)在此for循環(huán)里執(zhí)行一次處理IdleHandler操作
            if (pendingIdleHandlerCount < 0
                && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                mBlocked = true;
                continue;
            }

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

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

        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}

實(shí)際上從上面的代碼里面,可以分析出很多信息

IdleHandler相關(guān)信息

  • 調(diào)用條件

    • 當(dāng)前消息隊(duì)列為空(mMessages == null) 或 未到分發(fā)返回消息的時(shí)刻

    • 在每次獲取可用消息的死循環(huán)中,IdleHandler只會(huì)被處理一次:處理一次后pendingIdleHandlerCount為0,其循環(huán)不可再被執(zhí)行

  • 實(shí)現(xiàn)了IdleHandler中的queueIdle方法

    • 返回false,執(zhí)行后,IdleHandler將會(huì)從IdleHandler列表中移除,只能執(zhí)行一次:默認(rèn)false

    • 返回true,每次分發(fā)返回消息的時(shí)候,都有機(jī)會(huì)被執(zhí)行:處于?;?/code>狀態(tài)

  • IdleHandler代碼

    MessageQueue.java
    ...
    /**
     * Callback interface for discovering when a thread is going to block
     * waiting for more messages.
     */
    public static interface IdleHandler {
        /**
         * Called when the message queue has run out of messages and will now
         * wait for more.  Return true to keep your idle handler active, false
         * to have it removed.  This may be called if there are still messages
         * pending in the queue, but they are all scheduled to be dispatched
         * after the current time.
         */
        boolean queueIdle();
    }
    
    public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }
    
    public void removeIdleHandler(@NonNull IdleHandler handler) {
        synchronized (this) {
            mIdleHandlers.remove(handler);
        }
    }


  • 怎么使用IdleHandler呢?

    public class MainActivity extends AppCompatActivity {
        private TextView msgTv;
        private Handler mHandler = new Handler();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            msgTv = findViewById(R.id.tv_msg);
            //添加IdleHandler實(shí)現(xiàn)類
            mHandler.getLooper().getQueue().addIdleHandler(new InfoIdleHandler("我是IdleHandler"));
            mHandler.getLooper().getQueue().addIdleHandler(new InfoIdleHandler("我是大帥比"));
    
            //消息收發(fā)一體
            new Thread(new Runnable() {
                @Override public void run() {
                    String info = "第一種方式";
                    mHandler.post(new Runnable() {
                        @Override public void run() {
                            msgTv.setText(info);
                        }
                    });
                }
            }).start();
        }
    
        //實(shí)現(xiàn)IdleHandler類
        class InfoIdleHandler implements MessageQueue.IdleHandler {
            private String msg;
    
            InfoIdleHandler(String msg) {
                this.msg = msg;
            }
    
            @Override
            public boolean queueIdle() {
                msgTv.setText(msg);
                return false;
            }
        }
    }


    • 這里簡(jiǎn)單寫(xiě)下用法,可以看看,留個(gè)印象

總結(jié)

  • 通俗的講:當(dāng)所有消息處理完了 或者 你發(fā)送了延遲消息,在這倆種空閑時(shí)間里,都滿足執(zhí)行IdleHandler的條件

    • 這地方需要說(shuō)明下,如果延遲消息時(shí)間設(shè)置過(guò)短的;IdleHandler可能會(huì)在發(fā)送消息后執(zhí)行,畢竟運(yùn)行到next這步也需要一點(diǎn)時(shí)間,延遲時(shí)間設(shè)置長(zhǎng)點(diǎn),你就可以很明顯得發(fā)現(xiàn),IdleHandler在延遲的空隙間執(zhí)行了!

  • 從其源碼上,可以看出來(lái),IdlerHandler是在消息分發(fā)的空閑時(shí)刻,專門(mén)用來(lái)處理相關(guān)事物的

同步屏障

來(lái)到最復(fù)雜的模塊了

在理解同步屏障的概念前,我們需要先搞懂幾個(gè)前置知識(shí)

前置知識(shí)

同步和異步消息

什么是同步消息?什么是異步消息?

  • 講真的,異步消息和同步消息界定,完成是通過(guò)一個(gè)方法去界定的

  • isAsynchronous():來(lái)分析下

    • flags為2:異步消息

    • flags為0,1:同步消息

    • FLAG_ASYNCHRONOUS = 1 << 1:所以FLAG_ASYNCHRONOUS為2

    • 同步消息:flags為0或者1的時(shí)候,isAsynchronous返回false,此時(shí)該消息標(biāo)定為同步消息

    • 異步消息:理論上只要按照位操作,右往左,第二位為1的數(shù),isAsynchronous返回true;但是,Message里面基本只使用了:0,1,2,可得出結(jié)論

public boolean isAsynchronous() {
    return (flags & FLAG_ASYNCHRONOUS) != 0;
}
  • setAsynchronous(boolean async):這個(gè)方法會(huì)影響flags的值

    • 因?yàn)閒lags是int類型,沒(méi)有賦初值,故其初始值為0

    • setAsynchronous傳入true的話,或等于操作,會(huì)將flags數(shù)值改成2

msg.setAsynchronous(true);

public void setAsynchronous(boolean async) {
    if (async) {
        flags |= FLAG_ASYNCHRONOUS;
    } else {
        flags &= ~FLAG_ASYNCHRONOUS;
    }
}
  • 怎么生成異步消息?so easy

Message msg = Message.obtain();
//設(shè)置異步消息標(biāo)記
msg.setAsynchronous(true);
  • 一般來(lái)說(shuō):默認(rèn)消息不做設(shè)置,flags都為0,故默認(rèn)為同步消息,下面欄目將分析下setAsynchronous在何處使用了

默認(rèn)消息類型

我們正常情況下,很少會(huì)使用setAsynchronous方法的,那么在不使用該方法的時(shí)候,消息的默認(rèn)類型是什么呢?

  • 在生成消息,然后發(fā)送消息的時(shí)候,都會(huì)經(jīng)過(guò)下述方法

  • enqueueMessage:正常發(fā)送消息(post、延遲和非延遲之類),都會(huì)經(jīng)過(guò)此方法

    • 只要mAsynchronous為true的話,我們的消息都會(huì)異步消息

    • 只要mAsynchronous為false的話,我們的消息都會(huì)同步消息

    • 這地方給Message類的target賦值了!

    • 說(shuō)明:只要使用post或sendMessage之類發(fā)送消息,其消息就絕不可能是同步屏障消息!

    • 因?yàn)榘l(fā)送的所有消息都會(huì)經(jīng)過(guò)enqueueMessage方法,然后加入消息隊(duì)列,可以看見(jiàn)所有的消息都被處理過(guò)

    • msg.target = this

    • 關(guān)于同步異步,可以看見(jiàn)和mAsynchronous息息相關(guān)

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
  • mAsynchronous在哪設(shè)置的呢?

    • 這是在構(gòu)造方法里面給mAsynchronous賦值了

public Handler(@Nullable 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();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
            + " that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}
  • 看看一些通用的構(gòu)造方法

public Handler() {
    this(null, false);
}

public Handler(@NonNull Looper looper) {
    this(looper, null, false);
}

public Handler(@NonNull Looper looper, @Nullable Callback callback) {
    this(looper, callback, false);
}
  • 總結(jié)下

    • 這下清楚了!如果不做特殊設(shè)置的話:默認(rèn)消息都是同步消息

    • 默認(rèn)消息都會(huì)給其target變量賦值:默認(rèn)消息都不是同步屏障消息

生成同步屏障消息

在next方法中發(fā)現(xiàn),target為null的消息被稱為同步屏障消息,那他為啥叫同步屏障消息呢?

  • postSyncBarrier(long when)

    • sync:同步 barrier:屏障,障礙物 ---> 同步屏障

    • 同步屏障實(shí)際挺能代表其含義的,它能屏蔽消息隊(duì)列中后續(xù)所有的同步方法分發(fā)

MessageQueue.java
...
@UnsupportedAppUsage
@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;
    }
}
  • mMessage這個(gè)變量,表明是將要被處理的消息,將要被返回的消息,也可以認(rèn)為,他是未處理消息隊(duì)列的頭結(jié)點(diǎn)消息

  • 關(guān)于同步屏障消息

    • 不為空:因?yàn)樯厦娴难h(huán)操作,會(huì)讓p節(jié)點(diǎn)的消息,肯定是剛好大于當(dāng)前時(shí)間刻,p節(jié)點(diǎn)的上一節(jié)點(diǎn)消息為當(dāng)前時(shí)刻過(guò)去時(shí)刻的消息,此時(shí)!咱們的同步屏障消息msg,就插在這倆者之間!

    • 為空:成為頭結(jié)點(diǎn)

    • 從消息池取一個(gè)可用消息

    • 這地方有個(gè)很有意思的循環(huán)操作,這while操作的,會(huì)將mMessages頭結(jié)點(diǎn)賦值給p變量,將p節(jié)點(diǎn)移到當(dāng)前時(shí)刻消息的下一節(jié)點(diǎn)

    • 頭結(jié)點(diǎn)(mMessage)是否為空

  • 同步屏障消息是直接插到消息隊(duì)列,他沒(méi)有設(shè)置target屬性且不經(jīng)過(guò)enqueueMessage方法,故其target屬性為null

總結(jié)下:

同步屏障消息插入消息隊(duì)列的規(guī)律,和上面正常發(fā)送消息插入基本是一致的;如果消息隊(duì)列有延時(shí)消息,延時(shí)消息的時(shí)刻大于目前的時(shí)刻,同步消息會(huì)在這些延時(shí)消息之前。

OK,同步屏障消息插入,基本可以理解為:正常的非延時(shí)消息插入消息隊(duì)列!

  • 同步屏障消息插入消息隊(duì)列流程圖

Handler的原理有哪些

同步屏障流程

  • next():精簡(jiǎn)了大量源碼碼,只保留和同步屏障有關(guān)的代碼;完整的點(diǎn)擊左側(cè)方法名

MessageQueue.java
...
Message next() {
    final long ptr = mPtr;
    ...

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
  		...
        //阻塞,除非到了超時(shí)時(shí)間或者喚醒
        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;
            // 這是關(guān)于同步屏障(SyncBarrier)的邏輯塊
            if (msg != null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            
            if (msg != null) {
                if (now < msg.when) {
                    //每個(gè)消息處理有耗時(shí)時(shí)間,之間存在一個(gè)時(shí)間間隔(when是將要執(zhí)行的時(shí)間點(diǎn))。
                    //如果當(dāng)前時(shí)刻還沒(méi)到執(zhí)行時(shí)刻(when),計(jì)算時(shí)間差值,傳入nativePollOnce定義喚醒阻塞的時(shí)間
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    mBlocked = false;
                    //該操作是把異步消息單獨(dú)從消息隊(duì)列里面提出來(lái),然后返回,返回之后,該異步消息就從消息隊(duì)列里面剔除了
                    //mMessage仍處于未分發(fā)的同步消息位置
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    //返回符合條件的Message
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }
            ...
        }
        ...
    }
}

去掉大量我們無(wú)需關(guān)注的代碼,發(fā)現(xiàn)這也沒(méi)啥嘛,就是一堆if eles for之類的,來(lái)分析分析

  1. Message msg = mMessages:這步賦值是非常重要的,表示即使我們對(duì)msg一頓操作,mMessage還是保留消息隊(duì)列頭結(jié)點(diǎn)消息的位置

  2. msg.target == null:遇到同步屏障消息

    • msg移到尾結(jié)點(diǎn),也就是移到了消息隊(duì)列尾結(jié)點(diǎn),將自身賦值為null(尾結(jié)點(diǎn)的next)

    • 遇上標(biāo)記為異步的消息,放行該消息進(jìn)行后續(xù)分發(fā)

    1. 首先是一個(gè)while循環(huán),內(nèi)部邏輯,不斷將msg節(jié)點(diǎn)的位置后移

    2. 結(jié)束while的倆個(gè)條件

  3. 分析下,倆個(gè)放行條件產(chǎn)生的不同影響

    • 消息隊(duì)列中如果有異步消息,同步屏障的邏輯會(huì)放行異步消息

    • 同步屏障里面堆prevMsg賦值了!請(qǐng)記住在整個(gè)方法里面,只有同步屏障邏輯里面堆prevMsg賦值了!這個(gè)參數(shù)為null與否,對(duì)消息隊(duì)列節(jié)點(diǎn)影響很大

    • prevMsg為空:會(huì)直接將msg的next賦值給mMessage;說(shuō)明分發(fā)完消息后,會(huì)直接移除頭結(jié)點(diǎn),將頭結(jié)點(diǎn)的下一節(jié)點(diǎn)賦值為頭結(jié)點(diǎn)

    • prevMsg不為空:不會(huì)對(duì)mMessage投節(jié)點(diǎn)操作;會(huì)將分發(fā)消息的上一節(jié)點(diǎn)的下一節(jié)點(diǎn)位置,換成分發(fā)節(jié)點(diǎn)的下一節(jié)點(diǎn),有點(diǎn)繞

    • 通過(guò)上面分析,可知;異步消息分發(fā)完后,會(huì)將其直接從消息隊(duì)列中移除,頭結(jié)點(diǎn)位置不變

    • 當(dāng)我們?cè)谕狡琳线壿嬂锩妫瑢sg自身移到尾結(jié)點(diǎn),并賦值為null(尾結(jié)點(diǎn)的next)

    • msg為null,是無(wú)法進(jìn)行后續(xù)分發(fā)操作,會(huì)重新進(jìn)行循環(huán)流程

    • mMessage頭結(jié)點(diǎn)重新將自身位置賦值給msg,繼續(xù)上述的重復(fù)過(guò)程

    • 可以發(fā)現(xiàn),上述邏輯確實(shí)起到了同步屏障的作用,屏蔽了其所有后續(xù)同步消息的分發(fā);只有移除消息隊(duì)列中的該條同步屏障消息,才能繼續(xù)進(jìn)行同步消息的分發(fā)

    1. 消息隊(duì)列不含異步消息

    2. 消息隊(duì)列含有異步消息

文字寫(xiě)了一大堆,我也是盡可能詳細(xì)描述,同步屏障邏輯代碼塊會(huì)產(chǎn)生的影響,整個(gè)圖,加深下印象!

Handler的原理有哪些

同步屏障作用

那么這個(gè)同步屏障有什么作用呢?

有個(gè)急需的問(wèn)題,就是什么地方用到了postSyncBarrier(long when)方法,這個(gè)方法對(duì)外是不暴露的,只有內(nèi)部包能夠調(diào)用

搜索了整個(gè)源碼包,發(fā)現(xiàn)只有幾個(gè)地方使用了它,剔除測(cè)試類,MessageQueue類,有作用的就是:ViewRootImpl類和Device類

Device類

  • pauseEvents():Device內(nèi)部涉及的是打開(kāi)設(shè)備的時(shí)候,會(huì)添加一個(gè)同步屏障消息,屏蔽后續(xù)所有的同步消息處理

    • 這說(shuō)明,我們無(wú)法調(diào)用這個(gè)方法;事實(shí)上,我們連Device類都無(wú)法調(diào)用,Device屬于被隱藏的類,和他同一目錄的還有Event和Hid,這些類系統(tǒng)都不想對(duì)外暴露

    • 這就很雞賊了,說(shuō)明插入同步屏障的消息的方法,系統(tǒng)確實(shí)不想對(duì)外暴露;當(dāng)然不包括非常規(guī)方法:反射

    • pauseEvents()是Device類中私有內(nèi)部類DeviceHandler的方法

  • 同步屏障添加:開(kāi)機(jī)時(shí),添加同步屏障

    Device.java
    ...
    private class DeviceHandler extends Handler {
        ...
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_OPEN_DEVICE:
                    ...
                    pauseEvents();
                    break;
                ...
            }
        }
    
        public void pauseEvents() {
            mBarrierToken = getLooper().myQueue().postSyncBarrier();
        }
    
        public void resumeEvents() {
            getLooper().myQueue().removeSyncBarrier(mBarrierToken);
            mBarrierToken = 0;
        }
    }


  • 同步屏障移除:完成開(kāi)機(jī)后,移除同步屏障

    Device.java
    ...
    private class DeviceHandler extends Handler {
        ...
        public void pauseEvents() {
            mBarrierToken = getLooper().myQueue().postSyncBarrier();
        }
    
        public void resumeEvents() {
            getLooper().myQueue().removeSyncBarrier(mBarrierToken);
            mBarrierToken = 0;
        }
    }
    
    private class DeviceCallback {
        public void onDeviceOpen() {
            mHandler.resumeEvents();
        }
        ....
    }


  • Device中使用同步屏障整體過(guò)程比較簡(jiǎn)單,這里簡(jiǎn)單描述下

    • 打開(kāi)設(shè)備時(shí),會(huì)發(fā)送一個(gè)同步屏障消息,屏蔽后續(xù)所有同步消息

    • 完成開(kāi)機(jī)后,移除同步屏障消息

    • 總結(jié):很明顯,這是盡量的提升打開(kāi)設(shè)備速度,不被其它次等重要的事件干擾

ViewRootImpl類

該欄目的分析,必須引用一個(gè)非常重要的結(jié)論,給出該結(jié)論的文章:源碼分析_Android UI何時(shí)刷新_Choreographer

  • scheduleTraversals():非常重要的方法

    ViewRootImpl.java
    ...
    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }


  • 結(jié)論:源碼分析_Android UI何時(shí)刷新_Choreographer

    我們調(diào)用View的requestLayout或者invalidate時(shí),最終都會(huì)觸發(fā)ViewRootImp執(zhí)行scheduleTraversals()方法。這個(gè)方法中ViewRootImp會(huì)通過(guò)Choreographer來(lái)注冊(cè)個(gè)接收Vsync的監(jiān)聽(tīng),當(dāng)接收到系統(tǒng)體層發(fā)送來(lái)的Vsync后我們就執(zhí)行doTraversal()來(lái)重新繪制界面。通過(guò)上面的分析我們調(diào)用invalidate等刷新操作時(shí),系統(tǒng)并不會(huì)立即刷新界面,而是等到Vsync消息后才會(huì)刷新頁(yè)面。

    • 關(guān)于上面的方法的分析,整體流程比較麻煩,涉及到整個(gè)刷新過(guò)程的分析

    • 這邊前輩的文章分析完UI刷新流程,給出了一個(gè)非常重要的結(jié)論

我們這邊已經(jīng)有了前輩給出的結(jié)論,我們知道了界面刷新(requestLayout或者invalidate)的過(guò)程一定會(huì)觸發(fā)scheduleTraversals()方法,這說(shuō)明會(huì)添加同步屏障消息,那肯定有移除同步屏障消息的步驟,這個(gè)步驟很有可能存在doTraversal()方法中,來(lái)看下這個(gè)方法

  • doTraversal():removeSyncBarrier!我giao!果然在這地方!

    • 這地方做了倆件事:移除同步屏障(removeSyncBarrier)、繪制界面(performTraversals)

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

        if (mProfile) {
            Debug.startMethodTracing("ViewAncestor");
        }

        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}
  • doTraversal()是怎么被調(diào)用呢?

    • 調(diào)用:mTraversalRunnable在scheduleTraversals()中使用了

      final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
      
      void scheduleTraversals() {
          if (!mTraversalScheduled) {
              ...
              mChoreographer.postCallback(
                  Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            ...
          }
      }
      
      final class TraversalRunnable implements Runnable {
          @Override
          public void run() {
              doTraversal();
          }
      }


  • postCallback是Choreographer類中方法,該類涉及巨多的消息傳遞,而且都是使用了異步消息setAsynchronous(true),這些都是和界面刷新相關(guān),所以都是優(yōu)先處理,完整的流程可以看上面貼的文章

  • postCallback的核心就是讓DisplayEventReceiver注冊(cè)了個(gè)Vsync的通知,后期收到送來(lái)的Vsync后,我們就執(zhí)行doTraversal()來(lái)重新繪制界面

總結(jié)

  • 通過(guò)上面的對(duì)ViewRootImpl說(shuō)明,需要來(lái)總結(jié)下同步屏障對(duì)界面繪制過(guò)程的影響

  • 詳細(xì)版總結(jié)(不講人話版)

調(diào)用View的requestLayout或者invalidate時(shí),最終都會(huì)執(zhí)行scheduleTraversals(),此時(shí)會(huì)在主線程消息隊(duì)列中插入一個(gè)同步屏障消息(停止所有同步消息分發(fā)),會(huì)將mTraversalRunnable添加到mCallbackQueues中,并注冊(cè)接收Vsync的監(jiān)聽(tīng),當(dāng)接受到Vsync通知后,會(huì)發(fā)送一個(gè)異步消息,觸發(fā)遍歷執(zhí)行mCallbackQueues的方法,這會(huì)執(zhí)行我們添加的回調(diào)mTraversalRunnable,從而執(zhí)行doTraversal(),此時(shí)會(huì)移除主線程消息隊(duì)列中同步屏障消息,最后執(zhí)行繪制操作

  • 通俗版總結(jié)

調(diào)用requestLayout或者invalidate時(shí),會(huì)在主線程消息隊(duì)列中插入一個(gè)同步屏障消息,同時(shí)注冊(cè)接收Vsync的監(jiān)聽(tīng);當(dāng)接受到Vsync通知,會(huì)發(fā)送一個(gè)異步消息,執(zhí)行真正的繪制事件:此時(shí)會(huì)移除消息隊(duì)列中的同步屏障消息,然后才會(huì)執(zhí)行繪制操作

  • 下面給不講人話版畫(huà)了個(gè)流轉(zhuǎn)圖示

Handler的原理有哪些

總結(jié)

消息插入對(duì)比

  • 有個(gè)很重要的事情,我們?cè)賮?lái)看下:正常發(fā)送消息和同步屏障消息插入消息隊(duì)列直接的區(qū)別,見(jiàn)下圖

    Handler的原理有哪些

    • 取消息:關(guān)于取消息,都是取的mMessage,可以理解為,取消息隊(duì)列的頭結(jié)點(diǎn)

    • 非延時(shí)消息在同步屏障消息之前發(fā)送,都會(huì)排在同步屏障消息之前

    • 延時(shí)消息,如果時(shí)刻大于發(fā)送同步屏障消息的時(shí)刻,會(huì)排在同步屏障消息之后

Vsync

  • 關(guān)于Vsync

    • Vsync 信號(hào)一般是由硬件產(chǎn)生的,現(xiàn)在手機(jī)一般為60hz~120hz,每秒刷新60到120次,一個(gè)時(shí)間片算一幀

    • 每個(gè) Vsync 信號(hào)之間的時(shí)間就是一幀的時(shí)間段

  • 來(lái)看下執(zhí)行同步消息時(shí)間片:這圖真吉兒不好畫(huà),吐血

Handler的原理有哪些

  • 由上圖可知:某種極端情況,你所發(fā)送的消息,在分發(fā)的時(shí)候,可能存在一幀的延時(shí)

總結(jié)

相關(guān)總結(jié)

  • 同步屏障能確保消息隊(duì)列中的異步消息,會(huì)被優(yōu)先執(zhí)行

  • 鑒于正常消息和同步屏障消息插入消息隊(duì)列的區(qū)別:同步屏障能夠及時(shí)的屏障隊(duì)列中的同步消息

  • 某些極端場(chǎng)景:發(fā)送的消息,在分發(fā)的時(shí)候,可能會(huì)存一幀延時(shí)

    • 極端場(chǎng)景:Vsync信號(hào)到來(lái)之后,立馬執(zhí)行了RequestLayout等操作

  • 同步屏障能確保在UI刷新中:Vsync信號(hào)到來(lái)后,能夠立馬執(zhí)行真正的繪制頁(yè)面操作

同步消息和異步消息使用建議

在正常的情況,肯定不建議使用異步消息,此處假設(shè)一個(gè)場(chǎng)景:因?yàn)槟撤N需求,你發(fā)送了大量的異步消息,由于消息進(jìn)入消息隊(duì)列的特殊性,系統(tǒng)發(fā)送的異步消息,也只能乖乖的排在你的異步消息后面,假設(shè)你的異步消息占據(jù)了大量的時(shí)間片,甚至占用了幾幀,導(dǎo)致系統(tǒng)UI刷新的異步消息無(wú)法被及時(shí)執(zhí)行,此時(shí)很有可能發(fā)生掉幀

當(dāng)然,如果你能看明白這個(gè)同步屏障欄目所寫(xiě)的東西,相信什么時(shí)候設(shè)置消息為異步,心中肯定有數(shù)

  • 正常情況,請(qǐng)繼續(xù)使用同步消息

  • 特殊情況,需要自己發(fā)送的消息被優(yōu)先處理:可以使用異步消息

考點(diǎn)

上面源碼基本就分析到這邊了,咱們看看能根據(jù)這些知識(shí)點(diǎn),能提一些什么問(wèn)題呢?

一個(gè)小知識(shí)

我逛一些論壇的時(shí)候,發(fā)現(xiàn)有人:對(duì)Handler怎么在主線程和子線程進(jìn)行數(shù)據(jù)交互的原理,感到迷惑。

  • 如果看完這整篇,或許你的心里已經(jīng)有了答案,為了更加明確這個(gè)知識(shí),我還是在這里總結(jié)下吧!

    • 主線程和子線程通過(guò)handler交互,交互的載體是通過(guò)Message這個(gè)對(duì)象,實(shí)際上我們?cè)谧泳€程發(fā)送的所有消息,都會(huì)加入到主線程的消息隊(duì)列中,然后主線程分發(fā)這些消息,這個(gè)就很容易做到倆個(gè)線程信息的交互。

  • 看到這里,你可能有疑問(wèn)了,我從子線程發(fā)送的消息,怎么就加到了主線程的消息隊(duì)列里呢???

    • 大家可以看看你自己的代碼,你的handler對(duì)象是不是在主線程初始的?子線程發(fā)送消息,是不是通過(guò)這個(gè)handler發(fā)送的?

    • 這就很簡(jiǎn)單了,handler只需要把發(fā)送的消息,加到自身持有的Looper對(duì)象的MessageQueue里面(mLooper變量)就ok了

  • 所以,你在哪個(gè)線程里面初始化Handler對(duì)象,在不同的線程中,使用這個(gè)對(duì)象發(fā)送消息;都會(huì)在你初始化Handler對(duì)象的線程里分發(fā)消息

1、先來(lái)個(gè)自己想的問(wèn)題:Handler中主線程的消息隊(duì)列是否有數(shù)量上限?為什么?

這問(wèn)題整的有點(diǎn)雞賊,可能會(huì)讓你想到,是否有上限這方面?而不是直接想到到上限數(shù)量是多少?

解答:Handler主線程的消息隊(duì)列肯定是有上限的,每個(gè)線程只能實(shí)例化一個(gè)Looper實(shí)例(上面講了,Looper.prepare只能使用一次),不然會(huì)拋異常,消息隊(duì)列是存在Looper()中的,且僅維護(hù)一個(gè)消息隊(duì)列

重點(diǎn):每個(gè)線程只能實(shí)例化一次Looper()實(shí)例、消息隊(duì)列存在Looper中

拓展:MessageQueue類,其實(shí)都是在維護(hù)mMessage,只需要維護(hù)這個(gè)頭結(jié)點(diǎn),就能維護(hù)整個(gè)消息鏈表

2、Handler中有Loop死循環(huán),為什么沒(méi)有卡死?為什么沒(méi)有發(fā)生ANR?

先說(shuō)下ANR:5秒內(nèi)無(wú)法響應(yīng)屏幕觸摸事件或鍵盤(pán)輸入事件;廣播的onReceive()函數(shù)時(shí)10秒沒(méi)有處理完成;前臺(tái)服務(wù)20秒內(nèi),后臺(tái)服務(wù)在200秒內(nèi)沒(méi)有執(zhí)行完畢;ContentProvider的publish在10s內(nèi)沒(méi)進(jìn)行完。所以大致上Loop死循環(huán)和ANR聯(lián)系不大,問(wèn)了個(gè)正確的廢話,所以觸發(fā)事件后,耗時(shí)操作還是要放在子線程處理,handler將數(shù)據(jù)通訊到主線程,進(jìn)行相關(guān)處理。

線程實(shí)質(zhì)上是一段可運(yùn)行的代碼片,運(yùn)行完之后,線程就會(huì)自動(dòng)銷毀。當(dāng)然,我們肯定不希望主線程被over,所以整一個(gè)死循環(huán)讓線程保活。

為什么沒(méi)被卡死:在事件分發(fā)里面分析了,在獲取消息的next()方法中,如果沒(méi)有消息,會(huì)觸發(fā)nativePollOnce方法進(jìn)入線程休眠狀態(tài),釋放CPU資源,MessageQueue中有個(gè)原生方法nativeWake方法,可以解除nativePollOnce的休眠狀態(tài),ok,咱們?cè)谶@倆個(gè)方法的基礎(chǔ)上來(lái)給出答案

  • 當(dāng)消息隊(duì)列中消息為空時(shí),觸發(fā)MessageQueue中的nativePollOnce方法,線程休眠,釋放CPU資源

  • 消息插入消息隊(duì)列,會(huì)觸發(fā)nativeWake喚醒方法,解除主線程的休眠狀態(tài)

    • 當(dāng)插入消息到消息隊(duì)列中,為消息隊(duì)列頭結(jié)點(diǎn)的時(shí)候,會(huì)觸發(fā)喚醒方法

    • 當(dāng)插入消息到消息隊(duì)列中,在頭結(jié)點(diǎn)之后,鏈中位置的時(shí)候,不會(huì)觸發(fā)喚醒方法

  • 綜上:消息隊(duì)列為空,會(huì)阻塞主線程,釋放資源;消息隊(duì)列為空,插入消息時(shí)候,會(huì)觸發(fā)喚醒機(jī)制

    • 這套邏輯能保證主線程最大程度利用CPU資源,且能及時(shí)休眠自身,不會(huì)造成資源浪費(fèi)

  • 本質(zhì)上,主線程的運(yùn)行,整體上都是以事件(Message)為驅(qū)動(dòng)的

3、為什么不建議在子線程中更新UI?

多線程操作,在UI的繪制方法表示這不安全,不穩(wěn)定。

假設(shè)一種場(chǎng)景:我會(huì)需要對(duì)一個(gè)圓進(jìn)行改變,A線程將圓增大倆倍,B改變圓顏色。A線程增加了圓三分之一體積的時(shí)候,B線程此時(shí),讀取了圓此時(shí)的數(shù)據(jù),進(jìn)行改變顏色的操作;最后的結(jié)果,可能會(huì)導(dǎo)致,大小顏色都不對(duì)。。。

4、可以讓自己發(fā)送的消息優(yōu)先被執(zhí)行嗎?原理是什么?

這個(gè)問(wèn)題,我感覺(jué)只能說(shuō):在有同步屏障的情況下是可以的。

同步屏障作用:在含有同步屏障的消息隊(duì)列,會(huì)及時(shí)的屏蔽消息隊(duì)列中所有同步消息的分發(fā),放行異步消息的分發(fā)。

在含有同步屏障的情況,我可以將自己的消息設(shè)置為異步消息,可以起到優(yōu)先被執(zhí)行的效果。

5、子線程和子線程使用Handler進(jìn)行通信,存在什么弊端?

子線程和子線程使用Handler通信,某個(gè)接受消息的子線程肯定使用實(shí)例化handler,肯定會(huì)有Looper操作,Looper.loop()內(nèi)部含有一個(gè)死循環(huán),會(huì)導(dǎo)致線程的代碼塊無(wú)法被執(zhí)行完,該線程始終存在。

如果在完成通信操作,我們一般可以使用: mHandler.getLooper().quit() 來(lái)結(jié)束分發(fā)操作

  • 說(shuō)明下:quit()方法會(huì)進(jìn)行幾項(xiàng)操作

    • 清空消息隊(duì)列(未分發(fā)的消息,不再分發(fā)了)

    • 調(diào)用了原生的銷毀方法 nativeDestroy(猜測(cè)下:可能是一些資源的釋放和銷毀)

    • 拒絕新消息進(jìn)入消息隊(duì)列

    • 它可以起到結(jié)束loop()死循環(huán)分發(fā)消息的操作

  • 拓展:quitSafely() 可以確保所有未完成的事情完成后,再結(jié)束消息分發(fā)

6、Handler中的阻塞喚醒機(jī)制?

這個(gè)阻塞喚醒機(jī)制是基于 Linux 的 I/O 多路復(fù)用機(jī)制 epoll 實(shí)現(xiàn)的,它可以同時(shí)監(jiān)控多個(gè)文件描述符,當(dāng)某個(gè)文件描述符就緒時(shí),會(huì)通知對(duì)應(yīng)程序進(jìn)行讀/寫(xiě)操作.

MessageQueue 創(chuàng)建時(shí)會(huì)調(diào)用到 nativeInit,創(chuàng)建新的 epoll 描述符,然后進(jìn)行一些初始化并監(jiān)聽(tīng)相應(yīng)的文件描述符,調(diào)用了epoll_wait方法后,會(huì)進(jìn)入阻塞狀態(tài);nativeWake觸發(fā)對(duì)操作符的 write 方法,監(jiān)聽(tīng)該操作符被回調(diào),結(jié)束阻塞狀態(tài)

詳細(xì)請(qǐng)查看:同步屏障?阻塞喚醒?和我一起重讀 Handler 源碼

7、什么是IdleHandler?什么條件下觸發(fā)IdleHandler?

IdleHandler的本質(zhì)就是接口,為了在消息分發(fā)空閑的時(shí)候,能處理一些事情而設(shè)計(jì)出來(lái)的

具體條件:消息隊(duì)列為空的時(shí)候、發(fā)送延時(shí)消息的時(shí)候

8、消息處理完后,是直接銷毀嗎?還是被回收?如果被回收,有最大容量嗎?

Handler存在消息池的概念,處理完的消息會(huì)被重置數(shù)據(jù),采用頭插法進(jìn)入消息池,取的話也直接取頭結(jié)點(diǎn),這樣會(huì)節(jié)省時(shí)間

消息池最大容量為50,達(dá)到最大容量后,不再接受消息進(jìn)入

9、不當(dāng)?shù)氖褂肏andler,為什么會(huì)出現(xiàn)內(nèi)存泄漏?怎么解決?

先說(shuō)明下,Looper對(duì)象在主線程中,整個(gè)生命周期都是存在的,MessageQueue是在Looper對(duì)象中,也就是消息隊(duì)列也是存在在整個(gè)主線程中;我們知道Message是需要持有Handler實(shí)例的,Handler又是和Activity存在強(qiáng)引用關(guān)系

存在某種場(chǎng)景:我們關(guān)閉當(dāng)前Activity的時(shí)候,當(dāng)前Activity發(fā)送的Message,在消息隊(duì)列還未被處理,Looper間接持有當(dāng)前activity引用,因?yàn)閭z者直接是強(qiáng)引用,無(wú)法斷開(kāi),會(huì)導(dǎo)致當(dāng)前Activity無(wú)法被回收

思路:斷開(kāi)倆者之間的引用、處理完分發(fā)的消息,消息被處理后,之間的引用會(huì)被重置斷開(kāi)

解決:使用靜態(tài)內(nèi)部類弱引Activity、清空消息隊(duì)列

最后

寫(xiě)這篇文章加上思維導(dǎo)圖,也大概整了十三來(lái)張圖,我真的盡力了!

Handler的原理有哪些

到此,相信大家對(duì)“Handler的原理有哪些”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI