中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Handler的作用有哪些

發布時間:2021-10-15 11:59:42 來源:億速云 閱讀:273 作者:iii 欄目:編程語言

這篇文章主要講解了“Handler的作用有哪些”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Handler的作用有哪些”吧!

Handler被設計出來的原因?有什么用?

一種東西被設計出來肯定就有它存在的意義,而Handler的意義就是切換線程。

作為Android消息機制的主要成員,它管理著所有與界面有關的消息事件,常見的使用場景有:

  • 跨進程之后的界面消息處理。

比如Activity的啟動,就是AMS在進行進程間通信的時候,通過Binder線程 將消息發送給ApplicationThread的消息處理者Handler,然后再將消息分發給主線程中去執行。

  • 網絡交互后切換到主線程進行UI更新

當子線程網絡操作之后,需要切換到主線程進行UI更新。

總之一句話,Hanlder的存在就是為了解決在子線程中無法訪問UI的問題。 

為什么建議子線程不訪問(更新)UI?

因為Android中的UI控件不是線程安全的,如果多線程訪問UI控件那還不亂套了。

那為什么不加鎖呢?

  • 會降低UI訪問的效率。本身UI控件就是離用戶比較近的一個組件,加鎖之后自然會發生阻塞,那么UI訪問的效率會降低,最終反應到用戶端就是這個手機有點卡。
  • 太復雜了。本身UI訪問時一個比較簡單的操作邏輯,直接創建UI,修改UI即可。如果加鎖之后就讓這個UI訪問的邏輯變得很復雜,沒必要。

所以,Android設計出了 單線程模型 來處理UI操作,再搭配上Handler,是一個比較合適的解決方案。 

子線程訪問UI的 崩潰原因 和 解決辦法?

崩潰發生在ViewRootImpl類的checkThread方法中:

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }  

其實就是判斷了當前線程 是否是 ViewRootImpl創建時候的線程,如果不是,就會崩潰。

而ViewRootImpl創建的時機就是界面被繪制的時候,也就是onResume之后,所以如果在子線程進行UI更新,就會發現當前線程(子線程)和View創建的線程(主線程)不是同一個線程,發生崩潰。

解決辦法有三種:

  • 在新建視圖的線程進行這個視圖的UI更新,主線程創建View,主線程更新View。
  • 在     ViewRootImpl創建之前進行子線程的UI更新,比如onCreate方法中進行子線程更新UI。
  • 子線程切換到主線程進行UI更新,比如     Handler、view.post方法。

MessageQueue是干嘛呢?用的什么數據結構來存儲數據?

看名字應該是個隊列結構,隊列的特點是什么?先進先出,一般在隊尾增加數據,在隊首進行取數據或者刪除數據。

Hanlder中的消息似乎也滿足這樣的特點,先發的消息肯定就會先被處理。但是,Handler中還有比較特殊的情況,比如延時消息。

延時消息的存在就讓這個隊列有些特殊性了,并不能完全保證先進先出,而是需要根據時間來判斷,所以Android中采用了鏈表的形式來實現這個隊列,也方便了數據的插入。

來一起看看消息的發送過程,無論是哪種方法發送消息,都會走到sendMessageDelayed方法

    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;
        return enqueueMessage(queue, msg, uptimeMillis);
    }
 

sendMessageDelayed方法主要計算了消息需要被處理的時間,如果delayMillis為0,那么消息的處理時間就是當前時間。

然后就是關鍵方法enqueueMessage

    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) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                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; 
                prev.next = msg;
            }

            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
 

不懂得地方先不看,只看我們想看的:

  • 首先設置了     Message的when字段,也就是代表了這個消息的處理時間
  • 然后判斷當前隊列是不是為空,是不是即時消息,是不是執行時間when大于表頭的消息時間,滿足任意一個,就把當前消息msg插入到表頭。
  • 否則,就需要遍歷這個隊列,也就是     鏈表,找出when小于某個節點的when,找到后插入。

好了,其他內容暫且不看,總之,插入消息就是通過消息的執行時間,也就是when字段,來找到合適的位置插入鏈表。

具體方法就是通過死循環,使用快慢指針p和prev,每次向后移動一格,直到找到某個節點p的when大于我們要插入消息的when字段,則插入到p和prev之間。或者遍歷到鏈表結束,插入到鏈表結尾。

所以,MessageQueue就是一個用于存儲消息、用鏈表實現的特殊隊列結構。 

延遲消息是怎么實現的?

總結上述內容,延遲消息的實現主要跟消息的統一存儲方法有關,也就是上文說過的enqueueMessage方法。

無論是即時消息還是延遲消息,都是計算出具體的時間,然后作為消息的when字段進程賦值。

然后在MessageQueue中找到合適的位置(安排when小到大排列),并將消息插入到MessageQueue中。

這樣,MessageQueue就是一個按照消息時間排列的一個鏈表結構。

MessageQueue的消息怎么被取出來的?

剛才說過了消息的存儲,接下來看看消息的取出,也就是queue.next方法。

    Message next() {
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }

            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) {
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        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;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
            }
        }
    }
 

奇怪,為什么取消息也是用的死循環呢?

其實死循環就是為了保證一定要返回一條消息,如果沒有可用消息,那么就阻塞在這里,一直到有新消息的到來。

其中,nativePollOnce方法就是阻塞方法,nextPollTimeoutMillis參數就是阻塞的時間。

那什么時候會阻塞呢?兩種情況:

  • 1、有消息,但是當前時間小于消息執行時間,也就是代碼中的這一句:
if (now < msg.when) {
    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
}
 

這時候阻塞時間就是消息時間減去當前時間,然后進入下一次循環,阻塞。

  • 2、沒有消息的時候,也就是上述代碼的最后一句:
if (msg != null) {} 
    else {
    // No more messages.
    nextPollTimeoutMillis = -1;
    }
 

-1就代表一直阻塞。

MessageQueue沒有消息時候會怎樣?阻塞之后怎么喚醒呢?說說pipe/epoll機制?

接著上文的邏輯,當消息不可用或者沒有消息的時候就會阻塞在next方法,而阻塞的辦法是通過pipe/epoll機制

epoll機制是一種IO多路復用的機制,具體邏輯就是一個進程可以監視多個描述符,當某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作,這個讀寫操作是阻塞的。在Android中,會創建一個Linux管道(Pipe)來處理阻塞和喚醒。

  • 當消息隊列為空,管道的讀端等待管道中有新內容可讀,就會通過     epoll機制進入阻塞狀態。
  • 當有消息要處理,就會通過管道的寫端寫入內容,喚醒主線程。

同步屏障和異步消息是怎么實現的?

其實在Handler機制中,有三種消息類型:

  • 同步消息。也就是普通的消息。
  • 異步消息。通過setAsynchronous(true)設置的消息。
  • 同步屏障消息。通過postSyncBarrier方法添加的消息,特點是target為空,也就是沒有對應的handler。

這三者之間的關系如何呢?

  • 正常情況下,同步消息和異步消息都是正常被處理,也就是根據時間when來取消息,處理消息。
  • 當遇到同步屏障消息的時候,就開始從消息隊列里面去找異步消息,找到了再根據時間決定阻塞還是返回消息。

也就是說同步屏障消息不會被返回,他只是一個標志,一個工具,遇到它就代表要去先行處理異步消息了。

所以同步屏障和異步消息的存在的意義就在于有些消息需要“加急處理”。 

同步屏障和異步消息有具體的使用場景嗎?

使用場景就很多了,比如繪制方法scheduleTraversals

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            // 同步屏障,阻塞所有的同步消息
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // 通過 Choreographer 發送繪制任務
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        }
    }


    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
    msg.arg1 = callbackType;
    msg.setAsynchronous(true);
    mHandler.sendMessageAtTime(msg, dueTime);

在該方法中加入了同步屏障,后續加入一個異步消息MSG_DO_SCHEDULE_CALLBACK,最后會執行到FrameDisplayEventReceiver,用于申請VSYNC信號。

Message消息被分發之后會怎么處理?消息怎么復用的?

再看看loop方法,在消息被分發之后,也就是執行了dispatchMessage方法之后,還偷偷做了一個操作——recycleUnchecked

    public static void loop() {
        for (;;) {
            Message msg = queue.next(); // might block

            try {
                msg.target.dispatchMessage(msg);
            } 

            msg.recycleUnchecked();
        }
    }

//Message.java
    private static Message sPool;
    private static final int MAX_POOL_SIZE = 50;

    void recycleUnchecked() {
        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++;
            }
        }
    }

recycleUnchecked方法中,釋放了所有資源,然后將當前的空消息插入到sPool表頭。

這里的sPool就是一個消息對象池,它也是一個鏈表結構的消息,最大長度為50。

那么Message又是怎么復用的呢?在Message的實例化方法obtain中:

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

直接復用消息池sPool中的第一條消息,然后sPool指向下一個節點,消息池數量減一。 

Looper是干嘛呢?怎么獲取當前線程的Looper?為什么不直接用Map存儲線程和對象呢?

在Handler發送消息之后,消息就被存儲到MessageQueue中,而Looper就是一個管理消息隊列的角色。Looper會從MessageQueue中不斷的查找消息,也就是loop方法,并將消息交回給Handler進行處理。

而Looper的獲取就是通過ThreadLocal機制:

    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

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

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

通過prepare方法創建Looper并且加入到sThreadLocal中,通過myLooper方法從sThreadLocal中獲取Looper。

ThreadLocal運行機制?這種機制設計的好處?

下面就具體說說ThreadLocal運行機制。

//ThreadLocal.java
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

ThreadLocal類中的get和set方法可以大致看出來,有一個ThreadLocalMap變量,這個變量存儲著鍵值對形式的數據。

  • key為this,也就是當前ThreadLocal變量。
  • value為T,也就是要存儲的值。

然后繼續看看ThreadLocalMap哪來的,也就是getMap方法:

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

    //Thread.java
    ThreadLocal.ThreadLocalMap threadLocals = null;
 

原來這個ThreadLocalMap變量是存儲在線程類Thread中的。

所以ThreadLocal的基本機制就搞清楚了:

在每個線程中都有一個threadLocals變量,這個變量存儲著ThreadLocal和對應的需要保存的對象。

這樣帶來的好處就是,在不同的線程,訪問同一個ThreadLocal對象,但是能獲取到的值卻不一樣。

挺神奇的是不是,其實就是其內部獲取到的Map不同,Map和Thread綁定,所以雖然訪問的是同一個ThreadLocal對象,但是訪問的Map卻不是同一個,所以取得值也不一樣。

這樣做有什么好處呢?為什么不直接用Map存儲線程和對象呢?

打個比方:

  • ThreadLocal就是老師。
  • Thread就是同學。
  • Looper(需要的值)就是鉛筆。

現在老師買了一批鉛筆,然后想把這些鉛筆發給同學們,怎么發呢?兩種辦法:

  • 1、老師把每個鉛筆上寫好每個同學的名字,放到一個大盒子里面去(map),用的時候就讓同學們自己來找。

這種做法就是Map里面存儲的是同學和鉛筆,然后用的時候通過同學來從這個Map里找鉛筆。

這種做法就有點像使用一個Map,存儲所有的線程和對象,不好的地方就在于會很混亂,每個線程之間有了聯系,也容易造成內存泄漏。

  • 2、老師把每個鉛筆直接發給每個同學,放到同學的口袋里(map),用的時候每個同學從口袋里面拿出鉛筆就可以了。

這種做法就是Map里面存儲的是老師和鉛筆,然后用的時候老師說一聲,同學只需要從口袋里拿出來就行了。

很明顯這種做法更科學,這也就是ThreadLocal的做法,因為鉛筆本身就是同學自己在用,所以一開始就把鉛筆交給同學自己保管是最好的,每個同學之間進行隔離。

還有哪些地方運用到了ThreadLocal機制?

比如:Choreographer。

public final class Choreographer {

    // Thread local storage for the choreographer.
    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue() {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            return choreographer;
        }
    };

    private static volatile Choreographer mMainInstance;

Choreographer主要是主線程用的,用于配合 VSYNC中斷信號。

所以這里使用ThreadLocal更多的意義在于完成線程單例的功能。

可以多次創建Looper嗎?

Looper的創建是通過Looper.prepare方法實現的,而在prepare方法中就判斷了,當前線程是否存在Looper對象,如果有,就會直接拋出異常:

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

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

所以同一個線程,只能創建一個Looper,多次創建會報錯。 

Looper中的quitAllowed字段是啥?有什么用?

按照字面意思就是是否允許退出,我們看看他都在哪些地方用到了:

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

哦,就是這個quit方法用到了,如果這個字段為false,代表不允許退出,就會報錯。

但是這個quit方法又是干嘛的呢?從來沒用過呢。還有這個safe又是啥呢?

其實看名字就差不多能了解了,quit方法就是退出消息隊列,終止消息循環。

  • 首先設置了     mQuitting字段為true。
  • 然后判斷是否安全退出,如果安全退出,就執行     removeAllFutureMessagesLocked方法,它內部的邏輯是清空所有的延遲消息,之前沒處理的非延遲消息還是需要取處理,然后設置非延遲消息的下一個節點為空(p.next=null)。
  • 如果不是安全退出,就執行     removeAllMessagesLocked方法,直接清空所有的消息,然后設置消息隊列指向空(mMessages = null)

然后看看當調用quit方法之后,消息的發送和處理:

//消息發送
    boolean enqueueMessage(Message msg, long when) {
        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w(TAG, e.getMessage(), e);
                msg.recycle();
                return false;
            }
        }

當調用了quit方法之后,mQuitting為true,消息就發不出去了,會報錯。

再看看消息的處理,loop和next方法:

    Message next() {
        for (;;) {
            synchronized (this) {
                if (mQuitting) {
                    dispose();
                    return null;
                } 
            }  
        }
    }


    public static void loop() {
        for (;;) {
            Message msg = queue.next();
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
        }
    }

很明顯,當mQuitting為true的時候,next方法返回null,那么loop方法中就會退出死循環。

那么這個quit方法一般是什么時候使用呢?

  • 主線程中,一般情況下肯定不能退出,因為退出后主線程就停止了。所以是當APP需要退出的時候,就會調用quit方法,涉及到的消息是EXIT_APPLICATION,大家可以搜索下。
  • 子線程中,如果消息都處理完了,就需要調用quit方法停止消息循環。 

Looper.loop方法是死循環,為什么不會卡死(ANR)?

關于這個問題,強烈建議看看Gityuan的回答:https://www.zhihu.com/question/34652589

我大致總結下:

  • 1、主線程本身就是需要一只運行的,因為要處理各個View,界面變化。所以需要這個死循環來保證主線程一直執行下去,不會被退出。
  • 2、真正會卡死的操作是在某個消息處理的時候操作時間過長,導致掉幀、ANR,而不是loop方法本身。
  • 3、在主線程以外,會有其他的線程來處理接受其他進程的事件,比如     Binder線程(ApplicationThread),會接受AMS發送來的事件
  • 4、在收到跨進程消息后,會交給主線程的     Hanlder再進行消息分發。所以Activity的生命周期都是依靠主線程的     Looper.loop,當收到不同Message時則采用相應措施,比如收到     msg=H.LAUNCH_ACTIVITY,則調用     ActivityThread.handleLaunchActivity()方法,最終執行到onCreate方法。
  • 5、當沒有消息的時候,會阻塞在loop的     queue.next()中的     nativePollOnce()方法里,此時主線程會釋放CPU資源進入休眠狀態,直到下個消息到達或者有事務發生。所以死循環也不會特別消耗CPU資源。 

Message是怎么找到它所屬的Handler然后進行分發的?

在loop方法中,找到要處理的Message,然后調用了這么一句代碼處理消息:

msg.target.dispatchMessage(msg); 

所以是將消息交給了msg.target來處理,那么這個target是啥呢?

找找它的來頭:

//Handler
    private boolean enqueueMessage(MessageQueue queue,Message msg,long uptimeMillis) {
        msg.target = this;
       
        return queue.enqueueMessage(msg, uptimeMillis);
    }
 

在使用Hanlder發送消息的時候,會設置msg.target = this,所以target就是當初把消息加到消息隊列的那個Handler。

Handler 的 post(Runnable) 與 sendMessage 有什么區別

Hanlder中主要的發送消息可以分為兩種:

  • post(Runnable)
  • sendMessage
    public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }
 

通過post的源碼可知,其實post和sendMessage的區別就在于:

post方法給Message設置了一個callback

那么這個callback有什么用呢?我們再轉到消息處理的方法dispatchMessage中看看:

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

    private static void handleCallback(Message message) {
        message.callback.run();
    }
 

這段代碼可以分為三部分看:

  • 1、如果     msg.callback不為空,也就是通過post方法發送消息的時候,會把消息交給這個msg.callback進行處理,然后就沒有后續了。
  • 2、如果     msg.callback為空,也就是通過sendMessage發送消息的時候,會判斷Handler當前的mCallback是否為空,如果不為空就交給Handler.Callback.handleMessage處理。
  • 3、如果     mCallback.handleMessage返回true,則無后續了。
  • 4、如果     mCallback.handleMessage返回false,則調用handler類重寫的handleMessage方法。

所以post(Runnable) 與 sendMessage的區別就在于后續消息的處理方式,是交給msg.callback還是 Handler.Callback或者Handler.handleMessage。 

Handler.Callback.handleMessage 和 Handler.handleMessage 有什么不一樣?為什么這么設計?

接著上面的代碼說,這兩個處理方法的區別在于Handler.Callback.handleMessage方法是否返回true:

  • 如果為     true,則不再執行Handler.handleMessage
  • 如果為     false,則兩個方法都要執行。

那么什么時候有Callback,什么時候沒有呢?這涉及到兩種Hanlder的 創建方式:

    val handler1= object : Handler(){
        override fun handleMessage(msg: Message) {
            super.handleMessage(msg)
        }
    }

    val handler2 = Handler(object : Handler.Callback {
        override fun handleMessage(msg: Message): Boolean {
            return true
        }
    })
 

常用的方法就是第1種,派生一個Handler的子類并重寫handleMessage方法。而第2種就是系統給我們提供了一種不需要派生子類的使用方法,只需要傳入一個Callback即可。 

Handler、Looper、MessageQueue、線程是一一對應關系嗎?

  • 一個線程只會有一個     Looper對象,所以線程和Looper是一一對應的。
  • MessageQueue對象是在new Looper的時候創建的,所以Looper和MessageQueue是一一對應的。
  • Handler的作用只是將消息加到MessageQueue中,并后續取出消息后,根據消息的target字段分發給當初的那個handler,所以Handler對于Looper是可以多對一的,也就是多個Hanlder對象都可以用同一個線程、同一個Looper、同一個MessageQueue。

總結:Looper、MessageQueue、線程是一一對應關系,而他們與Handler是可以一對多的。 

ActivityThread中做了哪些關于Handler的工作?(為什么主線程不需要單獨創建Looper)

主要做了兩件事:

  • 1、在main方法中,創建了主線程的     Looper和     MessageQueue,并且調用loop方法開啟了主線程的消息循環。
public static void main(String[] args) {

        Looper.prepareMainLooper();

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
 
  • 2、創建了一個Handler來進行四大組件的啟動停止等事件處理
final H mH = new H();

class H extends Handler {
        public static final int BIND_APPLICATION        = 110;
        public static final int EXIT_APPLICATION        = 111;
        public static final int RECEIVER                = 113;
        public static final int CREATE_SERVICE          = 114;
        public static final int STOP_SERVICE            = 116;
        public static final int BIND_SERVICE            = 121;
   

IdleHandler是啥?有什么使用場景?

之前說過,當MessageQueue沒有消息的時候,就會阻塞在next方法中,其實在阻塞之前,MessageQueue還會做一件事,就是檢查是否存在IdleHandler,如果有,就會去執行它的queueIdle方法。

    private IdleHandler[] mPendingIdleHandlers;

    Message next() {
        int pendingIdleHandlerCount = -1;
        for (;;) {
            synchronized (this) {
                //當消息執行完畢,就設置pendingIdleHandlerCount
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                
                //初始化mPendingIdleHandlers
                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                //mIdleHandlers轉為數組
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // 遍歷數組,處理每個IdleHandler
            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);
                }

                //如果queueIdle方法返回false,則處理完就刪除這個IdleHandler
                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;
        }
    }

當沒有消息處理的時候,就會去處理這個mIdleHandlers集合里面的每個IdleHandler對象,并調用其queueIdle方法。最后根據queueIdle返回值判斷是否用完刪除當前的IdleHandler

然后看看IdleHandler是怎么加進去的:

Looper.myQueue().addIdleHandler(new IdleHandler() {  
    @Override  
    public boolean queueIdle() {  
        //做事情
        return false;    
    }  
});

    public void addIdleHandler(@NonNull IdleHandler handler) {
        if (handler == null) {
            throw new NullPointerException("Can't add a null IdleHandler");
        }
        synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }
 

ok,綜上所述,IdleHandler就是當消息隊列里面沒有當前要處理的消息了,需要堵塞之前,可以做一些空閑任務的處理。

常見的使用場景有:啟動優化

我們一般會把一些事件(比如界面view的繪制、賦值)放到onCreate方法或者onResume方法中。但是這兩個方法其實都是在界面繪制之前調用的,也就是說一定程度上這兩個方法的耗時會影響到啟動時間。

所以我們可以把一些操作放到IdleHandler中,也就是界面繪制完成之后才去調用,這樣就能減少啟動時間了。

但是,這里需要注意下可能會有坑。

如果使用不當,IdleHandler會一直不執行,比如在View的onDraw方法里面無限制的直接或者間接調用View的invalidate方法

其原因就在于onDraw方法中執行invalidate,會添加一個同步屏障消息,在等到異步消息之前,會阻塞在next方法,而等到FrameDisplayEventReceiver異步任務之后又會執行onDraw方法,從而無限循環。 

HandlerThread是啥?有什么使用場景?

直接看源碼:

public class HandlerThread extends Thread {
    @Override
    public void run() {
        Looper.prepare();
        synchronized (this) {
            mLooper = Looper.myLooper();
            notifyAll();
        }
        Process.setThreadPriority(mPriority);
        onLooperPrepared();
        Looper.loop();
    }

哦,原來如此。HandlerThread就是一個封裝了Looper的Thread類。

就是為了讓我們在子線程里面更方便的使用Handler。

這里的加鎖就是為了保證線程安全,獲取當前線程的Looper對象,獲取成功之后再通過notifyAll方法喚醒其他線程,那哪里調用了wait方法呢?

    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        
        // If the thread has been started, wait until the looper has been created.
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
 

就是getLooper方法,所以wait的意思就是等待Looper創建好,那邊創建好之后再通知這邊正確返回Looper。

IntentService是啥?有什么使用場景?

老規矩,直接看源碼:

public abstract class IntentService extends Service {


    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }

    @Override
    public void onCreate() {
        super.onCreate();
        HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
        thread.start();

        mServiceLooper = thread.getLooper();
        mServiceHandler = new ServiceHandler(mServiceLooper);
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        Message msg = mServiceHandler.obtainMessage();
        msg.arg1 = startId;
        msg.obj = intent;
        mServiceHandler.sendMessage(msg);
    }
 

理一下這個源碼:

  • 首先,這是一個     Service
  • 并且內部維護了一個     HandlerThread,也就是有完整的Looper在運行。
  • 還維護了一個子線程的     ServiceHandler
  • 啟動Service后,會通過Handler執行     onHandleIntent方法。
  • 完成任務后,會自動執行     stopSelf停止當前Service。

所以,這就是一個可以在子線程進行耗時任務,并且在任務執行后自動停止的Service。 

BlockCanary使用過嗎?說說原理

BlockCanary是一個用來檢測應用卡頓耗時的三方庫。

上文說過,View的繪制也是通過Handler來執行的,所以如果能知道每次Handler處理消息的時間,就能知道每次繪制的耗時了?那Handler消息的處理時間怎么獲取呢?

再去loop方法中找找細節:

public static void loop() {
    for (;;) {
        // This must be in a local variable, in case a UI event sets the logger
        Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }

        msg.target.dispatchMessage(msg);

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

可以發現,loop方法內有一個Printer類,在dispatchMessage處理消息的前后分別打印了兩次日志。

那我們把這個日志類Printer替換成我們自己的Printer,然后統計兩次打印日志的時間不就相當于處理消息的時間了?

    Looper.getMainLooper().setMessageLogging(mainLooperPrinter);

    public void setMessageLogging(@Nullable Printer printer) {
        mLogging = printer;
    }
 

這就是BlockCanary的原理。

說說Hanlder內存泄露問題。

這也是常常被問的一個問題,Handler內存泄露的原因是什么?

"內部類持有了外部類的引用,也就是Hanlder持有了Activity的引用,從而導致無法被回收唄。"

其實這樣回答是錯誤的,或者說沒回答到點子上。

我們必須找到那個最終的引用者,不會被回收的引用者,其實就是主線程,這條完整引用鏈應該是這樣:

主線程 —> threadlocal —> Looper —> MessageQueue —> Message —> Handler —> Activity

具體分析可以看看我之前寫的這篇文章:https://juejin.cn/post/6909362503898595342

利用Handler機制設計一個不崩潰的App?

主線程崩潰,其實都是發生在消息的處理內,包括生命周期、界面繪制。

所以如果我們能控制這個過程,并且在發生崩潰后重新開啟消息循環,那么主線程就能繼續運行。

Handler(Looper.getMainLooper()).post {
        while (true) {
            //主線程異常攔截
            try {
                Looper.loop()
            } catch (e: Throwable) {
            }
        }
    }

感謝各位的閱讀,以上就是“Handler的作用有哪些”的內容了,經過本文的學習后,相信大家對Handler的作用有哪些這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

齐齐哈尔市| 锡林浩特市| 嘉义县| 靖宇县| 安乡县| 临澧县| 包头市| 密山市| 临沧市| 灵武市| 江永县| 漠河县| 西充县| 正定县| 通许县| 隆安县| 张掖市| 迁安市| 调兵山市| 高青县| 邵武市| 珠海市| 贡嘎县| 台江县| 盘山县| 博客| 治多县| 商都县| 陇西县| 正宁县| 沂南县| 神池县| 孟连| 耒阳市| 汤原县| 乌兰察布市| 家居| 广水市| 盘锦市| 赤壁市| 蒲城县|