Handle的原理(Looper、Handler、Message三者關係)

来源:http://www.cnblogs.com/yishaochu/archive/2017/05/20/6882387.html
-Advertisement-
Play Games

轉載請註明出處:http://blog.csdn.net/lowprofile_coding/article/details/72580044 介紹 "前面的內容對Handler做了介紹,也講解瞭如何使用handler" ,但是我們並不知道他的實現原理。本文從源碼的角度來分析如何實現的。 首先我們得 ...


轉載請註明出處:http://blog.csdn.net/lowprofile_coding/article/details/72580044

介紹

前面的內容對Handler做了介紹,也講解瞭如何使用handler,但是我們並不知道他的實現原理。本文從源碼的角度來分析如何實現的。

首先我們得知道Handler,Looper,Message Queue三者之間的關係

  • Handler封裝了消息的發送,也負責接收消。內部會跟Looper關聯。
  • Looper 消息封裝的載,內部包含了MessageQueue,負責從MessageQueue取出消息,然後交給Handler處理
  • MessageQueue 就是一個消息隊列,負責存儲消息,有消息過來就存儲起來,Looper會迴圈的從MessageQueue讀取消息。

源碼分析

當我們new一個Handler對象的時候,看看他的構造方法裡面做了什麼.

public Handler(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 that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

從源碼中我們看到他會調用Looper.myLooper方法獲取一個Looper對象,然後從Looper對象獲取到MessageQueue對象。

Looper myLooper()

跟進去看看Looper.myLooper()方法做了什麼。這是一個靜態方法,可以類名.方法名直接調用。

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

這個方法裡面就一行代碼,從sThreadLocal中獲取一個Looper對象,sThreadLocal是一個ThreadLocal對象,可以在一個線程中存儲變數。底層是ThreadLocalMap,既然是Map類型那肯定得先set一個Looper對象,然後我們才能從sThreadLocal對象裡面get一個Looper對象。

ActivityThread main()

說到這裡得給大家介紹一個新的類ActivityThread,ActivityThread類是Android APP進程的初始類,它的main函數是這個APP進程的入口。我們看看這個main函數幹了什麼事情。

public static final void main(String[] args) {
        ------
        Looper.prepareMainLooper();
        if (sMainThreadHandler == null) {
            sMainThreadHandler = new Handler();
        }

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        Looper.loop();
        -----
}

在第二行代碼調用Looper.prepareMainLooper()方法,第13行調用了Looper.loop()方法。

Looper prepareMainLooper()

繼續跟進Looper.prepareMainLooper()方法,在這個方法中第一行代碼調用了內部的prepare方法。prepareMainLooper有點像單例模式中的getInstance方法,只不過getInstance會當時返回一個對象,而prepareMainLooper會新建一個Looper對象,存儲在sThreadLocal中。

public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

Looper prepare()

繼續跟進prepare方法,看第5行代碼,新建了一個Looper對象,調用sThreadLocal.set方法把Looper對象保存起來。看到這裡我想聰明的你們肯定明白了為什麼new Handler對象的時候調用Looper.myLooper()方法能從sThreadLocal對象中取到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));
}

Looper 構造方法

文章開頭我們就講到Looper內部包含了MessageQueue,其實就是在new Looper對象的時候就new了一個MessageQueue對象。

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

Looper loop()

ActivityThread類main方法中調用了Looper的兩個方法,前面我們解釋了prepareMainLooper(),現在來看第二個方法loop()。

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

    // 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 (;;) {//死迴圈  一直從MessageQueue中遍歷消息
        Message msg = queue.next(); // might block
        if (msg == null) {
            return;
        }

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

        final long traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
            Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }
        try {
            //調用handler的dispatchMessage方法,把消息交給handler處理
            msg.target.dispatchMessage(msg);
        } finally {
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }

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

這個方法的代碼呢比較多。我都給代碼加上了註釋。其實就是一個死迴圈,一直會從MessageQueue中取消息,如果取到了消息呢,會執行msg.target.dispatchMessage(msg)這行代碼,msg.target就是handler,其實就是調用handler的dispatchMessage方法,然後把從MessageQueue中取到的message傳入進去。

Handler dispatchMessage()

public void dispatchMessage(Message msg) {
    //如果callback不為空,說明發送消息的時候是post一個Runnable對象
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {//這個是用來攔截消息的
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);//最終調用我們重寫的handleMessage方法
    }
}

這個方法對消息做最後處理,如果是post類型調用handleCallback方法處理,如果是sendMessage發送的消息。看我們有沒有攔截消息,如果沒有最終調用handleMessage方法處理。

Handler handleCallback()

看到這裡我們知道為什麼post一個Runnable對象,run方法執行的代碼在主線程了吧,因為底層根本就沒有開啟線程,就只是調用了run方法而已。

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

前面我們從創建handler對象開始,以及創建Looper,創建MessageQueue的整個流程,現在來分析下,當我們調用post以及sendMessage方法時,怎麼把Message添加到MessageQueue。

Handler post()

調用了getPostMessage方法,把Runnable傳遞進去。

public final boolean post(Runnable r)
{
   return  sendMessageDelayed(getPostMessage(r), 0);
}

Handler getPostMessage()

首先調用Message.obtain()方法,取出一個Message對象,這個方法之前有講過,然後把Runnable對象賦值了Message對象的callback屬性。看到這裡我們也明白了dispatchMessage方法為什麼要先判斷callback是否為空了吧。

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}

Handler enqueueMessage()

在post方法裡面調用了sendMessageDelayed方法,其實最終調用的是enqueueMessage方法,所以我這裡就直接看enqueueMessage方法源碼。第一行代碼就把handler自己賦值給messgae對象的target屬性。然後調用MessageQueue的enqueueMessage方法把當前的Messgae添加進去。

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

總結

總結:handler負責發送消息,Looper負責接收Handler發送的消息,並直接把消息回傳給Handler自己。MessageQueue就是一個存儲消息的容器。

如果你想第一時間看我們的後期文章,掃碼關註公眾號,每周不定期推送Android開發實戰教程文章,你還等什麼,趕快關註吧,學好技術,出任ceo,贏取白富美。。。。

      Android開發666 - 安卓開發技術分享
            掃描二維碼加關註

Android開發666


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 求數組的最小值和最大值 寫一個函數,功能就是求參數中的平均數,裡面涉及到arguments這個類數組 並且把這個類數組轉換成數組 然後利用數組的方法進行求平均數 再寫一種方法 更加對call的用法的理解 以及對類數組轉換成數組的理解 再寫點註意的事情:在IE6至IE8中 對 arguments 這個 ...
  • 需求分析 簡單的分析一下,要實現N級菜單,首先從佈局入手,即判斷是否有下級菜單 圖:1 初步實現 1.實現是否存在 > 註意: 下麵凡是擔憂 xxx yyy ? xxx : xxx都是利用三元表達式,來表達思路。 HTML結構如下: 圖:2 要實現圖一的效果,我們只需要判斷li標簽裡面的childr ...
  • 不做前端很久了,今天從重構師那裡瞭解到CSS3已經可以實現很多以往必須通過JS才能實現的效果,如漸變,陰影,自動截斷文本展示省略號等等強大效果,而且這些功能日漸成熟,已經大量用於生產環境。H5真的日漸成熟了,得惡補下了。 以下分享實現指定文本超出部分以省略號展示的Demo: 如下圖: 文章分享自ph ...
  • 1. 獲取openid 1.1 獲取code 調用介面獲取登錄憑證(code)進而換取用戶登錄態信息,包括用戶的唯一標識(openid) 及本次登錄的會話密鑰(session_key)。用戶數據的加解密通訊需要依賴會話密鑰完成。 1.2 獲取openid 拿到上一步獲取的code,結合小程式 app ...
  • 移動開發基礎 1、基礎 移動開發:是指以PDA、UMPC等便攜終端為基礎進行的開發工作。 PDA:Person Digital Assistant 個人數位助理 UMPC:Ultra-mobile Personal Computer 超級移動個人電腦,攜帶型筆記本 三大主流移動開發平臺: Andr ...
  • 其實一切都是扯,看看有沒有主活動吧 癥狀:能部署安裝,沒有快捷方式,不啟動調試。XARAMIN不能在XML中配置主活動,會自動根據[Activity(Label = "AA",MainLauncher = true)]配置生成 參考 https://xamarinhelp.com/debugging... ...
  • 有一些Android很小的知識點,不值得單獨寫出來做為一篇博客。都在這個博客裡面進行總結 1、ImageButton控制項,中間圖片的放置效果可以用scaleType來設置,如下: ...
  • 原因是連接不上Google的更新伺服器: 解決方法: 選擇第三個Options: 修改Http Proxy Server: mirrors.neusoft.edu.cn Http Proxy Port: 80 選中 Others 下麵的 Force htto://....選項 重啟SDK Manag ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...