• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • 安卓Loop機制剖析之Looper,handler

    標簽: 源碼  android  java  鏈表  隊列  面試  

    Looper是什么

    用于為線程運行消息循環的類。默認情況下,線程沒有與之關聯的消息循環。要創建一個,在要運行循環的線程中調用 prepare(),然后調用loop()讓它處理消息,直到循環停止為止。與消息循環的大多數交互是通過 Handler類進行的。
    意思大概就是讓線程有處理消息的能力,并且這種能力是無限循環的,直到被停止為止。

    簡單使用

     public Handler handler;
     public void looperThread(){
         new Thread(new Runnable() {
             @Override
             public void run() {
                 Looper.prepare();
                 handler = new Handler(Looper.myLooper(),new Handler.Callback() {
                     @Override
                     public boolean handleMessage(Message msg) {
                         Log.e(TAG,"收到發送過來的消息:"+msg.obj.toString());
                         return false;
                     }
                 });
                 Looper.loop();
             }
         }).start();
     }
     
     @Override
     public void onClick(View view) {
         Message message = Message.obtain();
         message.obj = "點擊事件消息時間戳:"+System.currentTimeMillis()%10000;
         handler.sendMessage(message);
     }
    

    創建一個具有消息循環的線程,該線程中創建一個和該looper綁定的handler對象,然后點擊事件中不斷的去發送消息給looper循環,看下最后的效果如下:

    18:17:45.459 12495-12538/com.example.myapplication E/[MainActivity]: 收到發送過來的消息:點擊事件消息時間戳:5458
    18:17:45.690 12495-12538/com.example.myapplication E/[MainActivity]: 收到發送過來的消息:點擊事件消息時間戳:5690
    18:17:45.887 12495-12538/com.example.myapplication E/[MainActivity]: 收到發送過來的消息:點擊事件消息時間戳:5886
    ...省略
    18:18:40.010 12495-12538/com.example.myapplication E/[MainActivity]: 收到發送過來的消息:點擊事件消息時間戳:9
    18:18:40.840 12495-12538/com.example.myapplication E/[MainActivity]: 收到發送過來的消息:點擊事件消息時間戳:839
    18:18:41.559 12495-12538/com.example.myapplication E/[MainActivity]: 收到發送過來的消息:點擊事件消息時間戳:1558
    

    可以看到我一直點擊,一直有消息可以被處理,那么說明我創建的線程是一直運行的,并沒有結束。那么looper具體是怎么實現的這樣的功能的呢?

    從源碼了解loop原理

    在分析源碼之前,先看下整體的類圖關系:
    在這里插入圖片描述

    loop分析

    我們從Looper.prepare();這句代碼開始分析:

      Looper.prepare();`
    
    public final class Looper {
        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
        private static Looper sMainLooper;  // guarded by Looper.class
    
        final MessageQueue mQueue;
        final Thread mThread;
        ...省略
        public static void prepare() {
            prepare(true);
        }
      	...省略
    

    可以看到調用了prepare()方法后,接著調用了有參函數prepare:

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

    sThreadLocal的泛型參數是Looper,那么知道Looper保存在了線程所持有的map容器中,首先就是判斷sThreadLocal.get()是否為空,這個方法在上一章說過,是根據當前線程來獲取的,如果這個prepare方法在ui線程中調用那么返回的就是ui線程中的Looper,如果調用的是子線程中,那么返回的就是子線程的Looper了,如果不為空,拋出異常,意思就是一個線程只能持有一個Looper對象;如果為空的話,那么調用sThreadLocal的set方法將創建的Looper對象存放到對應線程的map容器中。

    接著調用了loop函數:

       Looper.loop();
    
     
        public static void loop() {
            final Looper me = myLooper();
            ...省略
            final MessageQueue queue = me.mQueue;
      
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) { 
                    return;
                }  
          		...省略
                try {
                    msg.target.dispatchMessage(msg);
                } finally {
             	   ...省略
                } 
     			...省略 
                msg.recycleUnchecked();
            }
        }
    

    大概是這樣的,其中去掉了一些和業務無關的代碼。

    myLooper()

    第一步調用myLooper()方法:

    
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    

    獲取當前線程的sThreadLocal中的Looper對象。從Looper對象獲取隊列。

    第二步開始for循環,Message msg = queue.next(); // might block 在循環中不斷的從queue中取Message消息,
    獲取msg判斷是否為空,空的話直接返回,不為空的話,調用msg的Target的dispatchMessage方法。最后msg使用完畢之后就回收msg對象。

    首先來看下

      Message msg = queue.next(); // might block
    

    next()

    調用的是MessageQueue的next方法,代碼如下:

     Message next() {
       		 ...省略
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            int nextPollTimeoutMillis = 0;
            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) {
                        // Stalled by a barrier.  Find the next asynchronous message in the queue.
                        do {
                            prevMsg = msg;
                            msg = msg.next;
                        } while (msg != null && !msg.isAsynchronous());
                    }
                    if (msg != null) {
                        if (now < msg.when) {
                            // Next message is not ready.  Set a timeout to wake up when it is ready.
                            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                        } else {
                            // Got a message.
                            mBlocked = false;
                            if (prevMsg != null) {
                                prevMsg.next = msg.next;
                            } else {
                                mMessages = msg.next;
                            }
                            msg.next = null;
                            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                            msg.markInUse();
                            return msg;
                        }
                    } else {
                        // No more messages.
                        nextPollTimeoutMillis = -1;
                    }
     				...省略
                } 
                ...省略
            }
        }
    

    首先調用nativePollOnce(ptr, nextPollTimeoutMillis); 這個方法是調用的native方法,意思就是阻塞當前線程,在延遲nextPollTimeoutMillis時長后喚醒當前線程。
    接著調用:

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

    其中的判斷是msg.target == null這個條件,這個條件說明當前的msg是沒有設置Target的,msg的Target一般是handler,如果這里是空的話,那么這個msg就是同步屏障消息,用于攔截同步消息的,讓異步消息有優先處理權。如果當前是同步屏障的話,那么while循環,一直向后遍歷msg節點,條件是這個msg非空和非異步消息,所以這里能夠跳出循環的情況就是msg到了尾部為空了,要么就是向后遍歷發現了異步消息。接著往下看:

    if (msg != null) {
        if (now < msg.when) {
            // Next message is not ready.  Set a timeout to wake up when it is ready.
            nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
        } else {
            // Got a message.
            mBlocked = false;
            if (prevMsg != null) {
                prevMsg.next = msg.next;
            } else {
                mMessages = msg.next;
            }
            msg.next = null;
            if (DEBUG) Log.v(TAG, "Returning message: " + msg);
            msg.markInUse();
            return msg;
        }
    } else {
        // No more messages.
        nextPollTimeoutMillis = -1;
    }
    

    分為兩種情況:
    (1)如果msg為空的話,先設置延遲時長nextPollTimeoutMillis = -1;接著這趟for循環結束,回到起點的位置,又開始執行nativePollOnce(ptr, nextPollTimeoutMillis);延遲時間是-1那么線程就會阻塞下去,直到被喚醒,不會執行for循環了(msg在進入隊列的時候會去喚醒線程的,所以這里不會一直阻塞的)。
    (2)如果msg不為空的話,假設消息設置的時間點大于現在的時間點,那么設置nextPollTimeoutMillis 為時間差和整數最大值中的最小值。這樣的話,線程在下次循環中的開頭就會阻塞到可以執行該消息的when時間節點再次運行(線程在阻塞的時候不會去輪轉cpu時間片所以可以節約cpu資源,同樣的,如果阻塞期間有消息進來可以馬上運行,那么還是會被喚醒的);假設消息設置的時間點小于現在的時間點,那么從msg消息鏈中把該消息摘取出來,msg標記為使用中,將msg返回。

    思考:隊列中頭部msg是同步屏障的話,那么優先從前往后去查找異步消息進行處理,所以在同步屏障消息之后的同步消息不會被執行,直到被移除為止。隊列頭部是普通的消息的時候,是根據when時間節點來判斷,是直接返回msg,還是等待when-now時間差在去循環一遍查找頭結點msg。

    handler.dispatchMessage

    handler = new Handler(Looper.myLooper(),new Handler.Callback() {
    	           @Override
    	           public boolean handleMessage(Message msg) {
    	               Log.e(TAG,"收到發送過來的消息:"+msg.obj.toString());
    	               return false;
    	           }
    	       });
    

    handler在創建的參數是Looper和Callback,接著再來看下dispatchMessage是如何實現的:

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

    如果msg存在callback的話,直接調用callbakc的run方法,這里不存在我們傳遞msg沒有設置callback,那么走下面的那個邏輯,我們給handler設置了mCallback,那么就直接回調handler的mCallback.handleMessage的方法:

        @Override
        public boolean handleMessage(Message msg) {
            Log.e(TAG,"收到發送過來的消息:"+msg.obj.toString());
            return false;
        }
    

    這樣也就出現了我們開頭demo中的打印消息了。

    handler分析

    我們通過上面的next方法分析了如何從隊列中獲取消息,那么我們還沒有分析消息是如何入隊的,接下來我們來分析下handler的幾個關鍵的問題,(1)handler的消息一個分為幾種;(2)handler發送消息到哪去了。
    我們從handler的構造函數入手:

    handler = new Handler(Looper.myLooper(),new Handler.Callback() {
                   @Override
                   public boolean handleMessage(Message msg) {
                       Log.e(TAG,"收到發送過來的消息:"+msg.obj.toString());
                       return false;
                   }
               });
               
    public Handler(Looper looper, Callback callback) {
        this(looper, callback, false);
    }
    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    

    我們可以看到,handler一共持有四個關鍵變量,Looper循環(和looper關聯,handler發送的消息只會發到這個隊列中),mQueue 持有Looper的隊列,mCallback 用于處理消息的回調函數,mAsynchronous 標志這個handler發送的消息是同步的還是異步的。

    我們再來看一下消息是怎么發送的:

    Message message = Message.obtain();
    message.obj = "點擊事件消息時間戳:"+System.currentTimeMillis()%10000;
    handler.sendMessage(message);
    

    首先從Message中獲取一個message,這個Message其實里面保存著msg的鏈表,遍歷鏈表,返回的是回收的msg,其中flags整數變量標志著msg是否正在使用中,是否是異步消息等等狀態。

    handler.sendMessage(message);
    

    然后使用handler去發送一個msg對象、接著進去看下:

    public final boolean sendMessage(Message msg) {
        return sendMessageDelayed(msg, 0);
    }
    
    public final boolean sendMessageDelayed(Message msg, long delayMillis){
    	if (delayMillis < 0) {
    	    delayMillis = 0;
    	}
    	return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
    
    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);
    }
    

    msg初始狀態下是同步消息,sendMessage方法發送出去的消息delayMillis 延遲時間是0;

     private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
         msg.target = this;
         if (mAsynchronous) {
             msg.setAsynchronous(true);
         }
         return queue.enqueueMessage(msg, uptimeMillis);
     }
    

    在入隊列之前,將msg的Target設置為當前handler,然后根據handler是否是異步的,設置msg是否是異步的,然后調用隊列的入隊函數,將消息入隊。
    這里先回答第二個問題,如何入隊的:

    消息入隊

      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; // invariant: p == prev.next
                    prev.next = msg;
                }
                if (needWake) {
                    nativeWake(mPtr);
                }
            }
            return true;
        }
    

    首先判斷當前入隊msg的when時間是否比隊列中的頭結點的when時間節點靠前,靠前的話,就將入隊的msg加入到隊列的頭部,并且調用nativeWake(mPtr);方法喚醒looper所在的線程,那么next()開始執行了,可以馬上遍歷隊列,消耗msg消息。如果當前消息msg的時間節點when大于頭部節點,首先設置needWake標志, 是否需要喚醒分為:如果隊列頭部是同步屏障,并且入隊消息msg是異步消息,那么就需要喚醒線程,其他情況不需要喚醒;接著執行for循環,循環里面尋找隊列中第一個節點時間是大于msg消息的時間節點的(這意味著隊列中消息是按照時間節點排序的),循環結束后,將入隊的msg插入到隊列中,最后根據需要是否喚醒線程。

    同步屏障

    同步屏障功能是讓隊列中的同步消息暫時不執行,直到同步屏障被移除,異步消息可以不受影響的被執行,相當于排隊買票的隊列中頭部有個人一直卡著不走,只有vip的人才能正常在窗口中買票,其他普通人買不了票,如果那個頭部卡著的那個人不走的話。這個同步屏障非常有用,用于優先執行某些任務。
    同步屏障我們使用的比較少,但是安卓frame層代碼有使用這個同步屏障的功能,例如ViewRootImp中:

    ViewRootImp中:
        void scheduleTraversals() { 
       			...省略
                mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                mChoreographer.postCallback(
                        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
                ...省略
        }
    
    Choreographer中:
       	private void postCallbackDelayedInternal(int callbackType, Object action, Object token, long delayMillis) {  
            	...省略
                mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token); 
                if (dueTime <= now) {
                    scheduleFrameLocked(now);
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                    msg.arg1 = callbackType;
                    msg.setAsynchronous(true);
                    mHandler.sendMessageAtTime(msg, dueTime);
                }
            }
        }         
    

    向隊列中發送一個同步屏障getQueue().postSyncBarrier();看下源碼如何實現的:

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

    同步屏障的時間節點是當前時間,還可以知道同步屏障消息的Target是空的,成員變量arg1保存的是同步屏障的自增值。接下來就是找到隊列中第一個時間節點比自己大的節點位置,然后插入到隊列中,所以屏障也是按照時間來排列的,沒有特殊待遇。

    接著使用handler向Looper中發送了一個異步消息:

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

    可以看到異步消息需要設置msg.setAsynchronous(true);
    執行ui的任務使用異步消息去執行,為啥要用異步,因為在5.0以上的安卓系統中已經開始使用了垂直同步技術了,所以重繪頁面的操作需要按照屏幕刷新率來執行,假如一個16ms里面有多次重繪請求,最終也只會拋棄掉,只保留一個重繪消息,所以,為了保證重繪操作能夠在收到同步信號的時間節點馬上執行,必須使用同步屏障,這樣前面排隊的同步消息暫時不執行,優先執行我們的重繪界面的異步消息,這樣可以保證我們的界面盡量能夠及時刷新,避免丟幀。、

    再來看下handler.post()方法:

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

    可以看到,其實也是封裝了一個msg對象,將callback傳遞給它,我們在dispatchMessge函數中也知道,如果msg如果有自己的callback 就會調用這個回調處理消息,不會使用handler自己的callback 來處理消息。

    總結

    根據以上所說的關系,畫一張圖:
    在這里插入圖片描述

    結論:
    handler的消息分為:同步消息,異步消息,屏障消息。
    handler的消息發送:消息都發送到了和它綁定的Looper的隊列中去了。
    那么queue一對一looper,looper一對多handler,looper對象保存在所在線程的ThreadLocal中。

    版權聲明:本文為u012345683原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/u012345683/article/details/108297844

    智能推薦

    Android消息機制三劍客之Handler、Looper、Message源碼分析(二)

    Android消息機制: Android消息機制三劍客之Handler、Looper、Message源碼分析(一) Android消息機制三劍客之Handler、Looper、Message源碼分析(二) 消息通信機制的運行原理     上一篇中,單獨分析了Handler、Looper、MessageQueue,本篇就分析一下這三者是如何協同工作,實現線程間...

    android消息分發機制之Java層Handler,Looper,Message及MessageQueue

    先看android提供的一個關于Handler、Looper使用的最簡單范例: 第一部分:Looper.prepare() 每個線程只能有一個Looper對象,所以只能調用一次Looper.prepare。創建的Looper對象保存在線程間各自私有的本地存儲區(TLS:Thread Local Stroage)。代碼中體現在Looper有個sThreadLocal成員變量,創建后通過它獲取該線程的...

    【Android開發】線程與消息處理-Handler消息傳遞機制之Looper

    在前面已經介紹了在Android中如何創建、開啟、休眠和中斷線程。不過,此時并沒有在新創建的子線程中對UI界面上的內容進行操作,如果應用前面介紹的方法對UI界面進行操作,將拋出異常。 為此,Android中引入了Handler消息傳遞機制,來實現在新創建的線程中操作UI界面。下面將對Handler消息傳遞機制進行介紹。 1.循環者Looper介紹 在介紹Looper之前,需要先了解一下Messag...

    HTML中常用操作關于:頁面跳轉,空格

    1.頁面跳轉 2.空格的代替符...

    freemarker + ItextRender 根據模板生成PDF文件

    1. 制作模板 2. 獲取模板,并將所獲取的數據加載生成html文件 2. 生成PDF文件 其中由兩個地方需要注意,都是關于獲取文件路徑的問題,由于項目部署的時候是打包成jar包形式,所以在開發過程中時直接安照傳統的獲取方法沒有一點文件,但是當打包后部署,總是出錯。于是參考網上文章,先將文件讀出來到項目的臨時目錄下,然后再按正常方式加載該臨時文件; 還有一個問題至今沒有解決,就是關于生成PDF文件...

    猜你喜歡

    電腦空間不夠了?教你一個小秒招快速清理 Docker 占用的磁盤空間!

    Docker 很占用空間,每當我們運行容器、拉取鏡像、部署應用、構建自己的鏡像時,我們的磁盤空間會被大量占用。 如果你也被這個問題所困擾,咱們就一起看一下 Docker 是如何使用磁盤空間的,以及如何回收。 docker 占用的空間可以通過下面的命令查看: TYPE 列出了docker 使用磁盤的 4 種類型: Images:所有鏡像占用的空間,包括拉取下來的鏡像,和本地構建的。 Con...

    requests實現全自動PPT模板

    http://www.1ppt.com/moban/ 可以免費的下載PPT模板,當然如果要人工一個個下,還是挺麻煩的,我們可以利用requests輕松下載 訪問這個主頁,我們可以看到下面的樣式 點每一個PPT模板的圖片,我們可以進入到詳細的信息頁面,翻到下面,我們可以看到對應的下載地址 點擊這個下載的按鈕,我們便可以下載對應的PPT壓縮包 那我們就開始做吧 首先,查看網頁的源代碼,我們可以看到每一...

    Linux C系統編程-線程互斥鎖(四)

    互斥鎖 互斥鎖也是屬于線程之間處理同步互斥方式,有上鎖/解鎖兩種狀態。 互斥鎖函數接口 1)初始化互斥鎖 pthread_mutex_init() man 3 pthread_mutex_init (找不到的情況下首先 sudo apt-get install glibc-doc sudo apt-get install manpages-posix-dev) 動態初始化 int pthread_...

    統計學習方法 - 樸素貝葉斯

    引入問題:一機器在良好狀態生產合格產品幾率是 90%,在故障狀態生產合格產品幾率是 30%,機器良好的概率是 75%。若一日第一件產品是合格品,那么此日機器良好的概率是多少。 貝葉斯模型 生成模型與判別模型 判別模型,即要判斷這個東西到底是哪一類,也就是要求y,那就用給定的x去預測。 生成模型,是要生成一個模型,那就是誰根據什么生成了模型,誰就是類別y,根據的內容就是x 以上述例子,判斷一個生產出...

    精品国产乱码久久久久久蜜桃不卡