1. 程式人生 > >Android開發Handler原始碼分析

Android開發Handler原始碼分析

為什麼使用Handler

Android中UI控制元件的訪問是執行緒不安全的,加鎖同步訪問會影響效能,因此設定只能一個執行緒更新UI,就是主執行緒,或者說是UI執行緒。在UI執行緒中不能進行耗時的操作,耗時操作需要開啟一個新的工作執行緒,工作執行緒不能更新UI,因此工作執行緒通過Handler通知UI執行緒更新UI

Handler構造器分析

建立匿名Handler或者內部Handler的時候,IDE會提示記憶體洩露的風險,因為內部類持有外部類引用,修改為靜態內部類即可

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

Handler內部幾個重要的屬性

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;

通過建構函式對屬性進行賦值。類屬性的命名規則以小寫字母m開頭,命名的規範值得學習。
其他幾個過載的建構函式功能類似,對類屬性的賦值。

通過Handler傳送訊息

Handler有多個傳送訊息的方法,但最終都呼叫了

public boolean sendMessageAtTime(Message msg, long uptimeMillis)

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

傳送訊息就是把訊息插入到訊息佇列,該訊息在訊息佇列的位置由該訊息處理的時間when決定

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

在這裡將msg的target屬性設定為this,也就是當前的Handler,這點很重要,之後的訊息處理會用到。最終是呼叫了MessageQueue類的enqueueMessage方法

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }

        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w("MessageQueue", e.getMessage(), e);
                msg.recycle();
                return false;
            }

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

首先檢查了改msg的target屬性是不是null,也就是是否設定了處理該msg的Handler,沒有設定Handler就丟擲異常,同一個msg不能進佇列兩次,否則丟擲異常
通過了上邊兩項檢查後,設定msg已使用,設定msg的處理事件,為msg的when屬性賦值

msg.markInUse();
msg.when = when;

然後根據msg的when屬性和Message佇列的情況,將該msg插入到佇列合適的位置

Handler訊息的輪詢處理

訊息通過Handler的post和send方法加入了handler的訊息佇列MessageQueue,那麼訊息佇列中的訊息是如何被依次處理的呢?是通過Looper,Handler有兩個重要的屬性

final MessageQueue mQueue;
final Looper mLooper;

在上邊建構函式的分析中,看到mQueue是如何被賦值的

mQueue = mLooper.mQueue;

即Handler的訊息佇列就是Looper的訊息佇列,因此訊息的輪詢處理是交給了Looper。一個Looper和一個執行緒繫結,要想使一個執行緒具備Looper的能力(輪詢訊息佇列),需要線上程run方法開頭呼叫

private static void prepare(boolean quitAllowed)

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

在prepare中通過ThreadLocal執行緒本地變數將Looper繫結到了呼叫prepare的執行緒。然後要想使執行緒具有輪詢能力,需要線上程run方法末尾呼叫

public static void loop()

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

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

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

            // This must be in a local variable, in case a UI event sets the logger
            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);
            }

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

            msg.recycleUnchecked();
        }
    }

可見在loop方法中通過死迴圈處理訊息佇列中的訊息

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

從訊息佇列中依次取出訊息,然後呼叫msg的target屬性的dispatchMessage方法。
而target是如何被賦值的呢?是在Handler呼叫post或者send傳送訊息的時候被賦值的,被賦值為傳送此訊息的handler

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

因此訊息的處理又回到了Handler執行緒,通過Handler的dispatchMessage方法回撥處理訊息

Handler處理訊息

在Handler所線上程,通過類似責任鏈模式的方式,處理該訊息

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

相關推薦

Android開發Handler原始碼分析

為什麼使用Handler Android中UI控制元件的訪問是執行緒不安全的,加鎖同步訪問會影響效能,因此設定只能一個執行緒更新UI,就是主執行緒,或者說是UI執行緒。在UI執行緒中不能進行耗時的操作,耗時操作需要開啟一個新的工作執行緒,工作執行緒不能更新UI

Android開發-從原始碼分析Fragment巢狀PagerAdapter生命週期,解決重建問題

介紹 眾所周知在Android開發中Fragment的生命週期非常複雜,複雜得甚至讓Square公司提出了我為什麼主張反對使用Android Fragment轉而提倡使用自定義View組合替代Fragment。但是沒辦法公司專案還是使用了很多Fragment巢狀

Android開發-Handler引起的記憶體洩漏-實驗、分析、總結。

介紹 最近在惡補Handler的知識,其中就涉及到了Handler引起的記憶體洩露問題,網路上有很多的分析文章。我就按照這些文章的思路,寫程式碼驗證,主要是驗證和記錄。  使用的記憶體檢測工具是:LeakCanary 中文使用說明 英文原文: http://www

Android 屬性動畫原始碼分析 && Handler Epoll

根據屬性和Value 設定關鍵幀,然後通過AnimationHandler 呼叫 Chroeographor 去監聽VSYNC 訊號,收到Vsync訊號後,呼叫doFrame,最終回撥到AnimationHandler 設定當前時間 對應的 value。 https://www.jianshu.

aNDROID開發盈利模式分析

aid 盈利 ive andro .com and roi android開發 music RELaTIVELaYOUT%E5%B1%9E%E6%80%A7%E5%A4%A7%E5%85%A8 http://music.baidu.com/songlist/4956847

【輸出文件】 Android 加密 模組原始碼分析

                                   Android6.0 加密模組解析

Android】Retrofit原始碼分析

Retrofit簡介 retrofit n. 式樣翻新,花樣翻新 vt. 給機器裝置裝配(新部件),翻新,改型 Retrofit 是一個 RESTful 的 HTTP 網路請求框架的封裝。注意這裡並沒有說它是網路請求框架,主要原因在於網路請求的工作並不是 Retrofit

Android】OkHttp原始碼分析

Android為我們提供了兩種HTTP互動的方式:HttpURLConnection 和 Apache HttpClient,雖然兩者都支援HTTPS,流的上傳和下載,配置超時,IPv6和連線池,已足夠滿足我們各種HTTP請求的需求。但更高效的使用HTTP 可以讓您的應用執行更快、更節省

Android Doze模式原始碼分析

轉自:https://www.cnblogs.com/l2rf/p/6373794.html   科技的仿生學無處不在,給予我們啟發。為了延長電池是使用壽命,google從蛇的冬眠中得到體會,那就是在某種情況下也讓手機進入類冬眠的情況,從而引入了今天的主題,Doze模式,Doze中

Android開發 Handler Runnable和Thread之間的區別和聯絡 應用----------------

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Handler原始碼分析

1、介紹 Handler是用來結合線程的訊息佇列來發送、處理Message物件和Runnable物件的工具。每一個Handler例項之後會關聯一個執行緒和該執行緒的訊息佇列。當你建立一個Handler的時候,從這時開始,它就會自動關聯到所在的執行緒/訊息佇列,

火狐Firefox ios版本二次開發 (三) 原始碼分析

Firefox 的原始碼採用的swift 2.3來進行開發的,實際測試的時候發現,Firefox 5.x版本使用xcode7 進行編譯是十分順利的,使用xcode 8編譯不太順利,也不太想折騰,後續開發以Firefox 6 beta 4為基礎來進行開發。 Fi

Android Wi-Fi原始碼分析之WifiService操作Wi-Fi(一):分析Wifi.c中的wifi_load_driver()函式

Wi-Fi原始碼分析之WifiService操作Wi-Fi(一) 分析Wifi.c中的wifi_load_driver()函式 int wifi_load_driver() { AL

Android Alarm驅動原始碼分析(Alarm.c)

  前言: Android在Linux Kernel的基礎上增加了很多的驅動程式,Alarm驅動是其中最簡單的一個,整個檔案只有500多行。作為驅動程式碼分析的一系列文章的開始,我試圖仔細的分析此驅動的幾乎所有函式程式碼,希望籍此作為溫習Android驅動原始碼一個良好的開

Android Wi-Fi原始碼分析之wpa_supplicant初始化(四):wpa_supplicant_init_iface函式分析

wpa_config_read分析 路徑為:external\wpa_supplicant_8\wpa_supplicant\config_file.c struct wpa_config * wpa_config_read(const char *na

Android資源載入原始碼分析

這篇文章主要介紹Android載入資源的主要流程 我們從一個簡單的函式入口進行分析 getResources().getString(resId);看下原始碼是怎麼實現的(Resources.java) public String getString(int

Android Init程序原始碼分析

Init 程序原始碼分析 基於Linux核心的android系統,在核心啟動完成後將建立一個Init使用者程序,實現了核心空間到使用者空間的轉變。在Android 啟動過程介紹一文中介紹了Android系統的各個啟動階段,init程序啟動後會讀取init.rc配置檔案,通

Android藍芽原始碼分析——BTA層訊息分發

這裡BTA是Bluetooth Application的縮寫,從上層下來的請求都要經過這個BTA層,通過傳送訊息的方式將請求丟到BTA層中處理。這個傳送訊息的函式定義在bta_sys_main.c中,如下: void bta_sys_sendmsg(voi

Android Wi-Fi原始碼分析之wpa_supplicant初始化(一)

一. wpa_supplicant配置編譯 將對應的平臺的wpa_supplicant包解壓改名為wpa_supplicant_8替換掉external下的wpa_supplicant_8目錄 執行: source build/envsetup.sh

Android(2.3+)原始碼分析MediaPlayer之RTSP

在前面的部落格中有簡單介紹MediaPlayer,最近又開始研究這塊東西,在此把閱讀程式碼的理解記錄下來方便以後快速查閱。 播放普通檔案傳入的url是一個本地的絕對路徑,但是流媒體的話傳入的就是一個網路地址如以"http://“開頭的流媒體和以"rtsp://"開頭的流媒體