Android Handler 機制 - Looper,Message,MessageQueue

来源:http://www.cnblogs.com/rustfisher/archive/2017/08/02/7272697.html
-Advertisement-
Play Games

Android Studio 2.3 API 25 從源碼角度分析Handler機制。有利於使用Handler和分析Handler的相關問題。 Handler 簡介 一個Handler允許發送和處理Message,通過關聯線程的 MessageQueue 執行 Runnable 對象。 每個Hand ...


  • Android Studio 2.3
  • API 25

從源碼角度分析Handler機制。有利於使用Handler和分析Handler的相關問題。

Handler 簡介

一個Handler允許發送和處理Message,通過關聯線程的 MessageQueue 執行 Runnable 對象。
每個Handler實例都和一個單獨的線程及其消息隊列綁定。
可以將一個任務切換到Handler所在的線程中去執行。一個用法就是子線程通過Handler更新UI。

主要有2種用法:

    1. 做出計劃,在未來某個時間點執行消息和Runnable
    1. 在其他線程規劃並執行任務

要使用好Handler,需要瞭解與其相關的 MessageQueueMessageLooper;不能孤立的看Handler
Handler就像一個操作者(或者像一個對開發者開放的視窗),利用MessageQueueLooper來實現任務調度和處理

    // 這個回調允許你使用Handler時不新建一個Handler的子類
    public interface Callback {
        public boolean handleMessage(Message msg);
    }
    final Looper mLooper; // Handler持有 Looper 的實例
    final MessageQueue mQueue; // 持有消息隊列
    final Callback mCallback;

在Handler的構造器中,我們可以看到消息隊列是相關的Looper管理的

    public Handler(Callback callback, boolean async) {
        // 處理異常
        mLooper = Looper.myLooper();
        // 處理特殊情況...
        mQueue = mLooper.mQueue; // 獲取的是Looper的消息隊列
    }

    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue; // 獲取的是Looper的消息隊列
        mCallback = callback;
        mAsynchronous = async;
    }

Android是消息驅動的,實現消息驅動有幾個要素:

  • 消息的表示:Message
  • 消息隊列:MessageQueue
  • 消息迴圈,用於迴圈取出消息進行處理:Looper
  • 消息處理,消息迴圈從消息隊列中取出消息後要對消息進行處理:Handler

初始化消息隊列

在Looper構造器中即創建了一個MessageQueue

發送消息

通過Looper.prepare初始化好消息隊列後就可以調用Looper.loop進入消息迴圈了,然後我們就可以向消息隊列發送消息,
消息迴圈就會取出消息進行處理,在看消息處理之前,先看一下消息是怎麼被添加到消息隊列的。

消息迴圈

Java層的消息都保存在了Java層MessageQueue的成員mMessages中,Native層的消息都保存在了Native Looper的
mMessageEnvelopes中,這就可以說有兩個消息隊列,而且都是按時間排列的。

為什麼要用Handler這樣的一個機制

因為在Android系統中UI操作並不是線程安全的,如果多個線程併發的去操作同一個組件,可能導致線程安全問題。
為瞭解決這一個問題,android制定了一條規則:只允許UI線程來修改UI組件的屬性等,也就是說必須單線程模型,
這樣導致如果在UI界面進行一個耗時較長的數據更新等就會形成程式假死現象 也就是ANR異常,如果20秒中沒有完成
程式就會強制關閉。所以比如另一個線程要修改UI組件的時候,就需要藉助Handler消息機制了。

Handler發送和處理消息的幾個方法

1.void handleMessage( Message msg):處理消息的方法,該方法通常被重寫。
2.final boolean hasMessage(int what):檢查消息隊列中是否包含有what屬性為指定值的消息
3.final boolean hasMessage(int what ,Object object) :檢查消息隊列中是否包含有what好object屬性指定值的消息
4.sendEmptyMessage(int what):發送空消息
5.final Boolean send EmptyMessageDelayed(int what ,long delayMillis):指定多少毫秒發送空消息
6.final boolean sendMessage(Message msg):立即發送消息
7.final boolean sendMessageDelayed(Message msg,long delayMillis):多少秒之後發送消息

與Handler工作的幾個組件Looper、MessageQueue各自的作用:

  • 1.Handler:它把消息發送給Looper管理的MessageQueue,並負責處理Looper分給它的消息
  • 2.MessageQueue:管理Message,由Looper管理
  • 3.Looper:每個線程只有一個Looper,比如UI線程中,系統會預設的初始化一個Looper對象,它負責管理MessageQueue,
    不斷的從MessageQueue中取消息,並將相對應的消息分給Handler處理

Handler.java (frameworks/base/core/java/android/os)

    // 將消息添加到隊列前,先判斷隊列是否為null
    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);
    }
// ......
    // 將消息添加到隊列中
    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this; // 將自己指定為Message的Handler
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

從這裡也不難看出,每個Message都持有Handler。如果Handler持有Activity的引用,Activity onDestroy後Message卻仍然在隊列中,
因為Handler與Activity的強關聯,會造成Activity無法被GC回收,導致記憶體泄露。
因此在Activity onDestroy 時,與Activity關聯的Handler應清除它的隊列由Activity產生的任務,避免記憶體泄露。

消息隊列 MessageQueue.java (frameworks/base/core/java/android/os)

    // 添加消息
    boolean enqueueMessage(Message msg, long when) {
        // 判斷並添加消息...
        return true;
    }

Handler.sendEmptyMessage(int what) 流程解析

獲取一個Message實例,並立即將Message實例添加到消息隊列中去。

簡要流程如下

// Handler.java
// 立刻發送一個empty消息
sendEmptyMessage(int what) 

// 發送延遲為0的empty消息  這個方法里通過Message.obtain()獲取一個Message實例
sendEmptyMessageDelayed(what, 0) 

// 計算消息的計劃執行時間,進入下一階段
sendMessageDelayed(Message msg, long delayMillis)

// 在這裡判斷隊列是否為null  若為null則直接返回false
sendMessageAtTime(Message msg, long uptimeMillis)

// 將消息添加到隊列中
enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)

// 接下來是MessageQueue添加消息
// MessageQueue.java
boolean enqueueMessage(Message msg, long when)

部分源碼如下

    public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

    public final boolean sendEmptyMessage(int what)
    {
        return sendEmptyMessageDelayed(what, 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);
    }

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

Handler 取消任務 removeCallbacksAndMessages

要取消任務時,調用下麵這個方法

public final void removeCallbacksAndMessages(Object token) {
    mQueue.removeCallbacksAndMessages(this, token);
}

通過調用Message.recycleUnchecked()方法,取消掉與此Handler相關聯的Message。

相關的消息隊列會執行取消指令

void removeCallbacksAndMessages(Handler h, Object object)

Message 和 MessageQueue 簡介

Message

Message 屬於被傳遞,被使用的角色
Message 是包含描述和任意數據對象的“消息”,能被髮送給Handler
包含2個int屬性和一個額外的對象
雖然構造器是公開的,但獲取實例最好的辦法是調用Message.obtain()Handler.obtainMessage()
這樣可以從他們的可回收對象池中獲取到消息實例

一般來說,每個Message實例握有一個Handler

部分屬性值

    /*package*/ Handler target; // 指定的Handler
    
    /*package*/ Runnable callback;
    
    // 可以組成鏈表
    // sometimes we store linked lists of these things
    /*package*/ Message next;

重置自身的方法,將屬性全部重置

public void recycle()
void recycleUnchecked()

獲取Message實例的常用方法,得到的實例與傳入的Handler綁定

    /**
     * Same as {@link #obtain()}, but sets the value for the <em>target</em> member on the Message returned.
     * @param h  Handler to assign to the returned Message object's <em>target</em> member.
     * @return A Message object from the global pool.
     */
    public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;

        return m;
    }

將消息發送給Handler

    /**
     * Sends this Message to the Handler specified by {@link #getTarget}.
     * Throws a null pointer exception if this field has not been set.
     */
    public void sendToTarget() {
        target.sendMessage(this); // target 就是與消息綁定的Handler
    }

調用這個方法後,Handler會將消息添加進它的消息隊列MessageQueue

MessageQueue

持有一列可以被Looper分發的Message。
一般來說由Handler將Message添加到MessageQueue中。

獲取當前線程的MessageQueue方法是Looper.myQueue()

Looper 簡介

Looper與MessageQueue緊密關聯

在一個線程中運行的消息迴圈。線程預設情況下是沒有與之管理的消息迴圈的。
要創建一個消息迴圈,線上程中調用prepare,然後調用loop。即開始處理消息,直到迴圈停止。

大多數情況下通過Handler來與消息迴圈互動。

Handler與Looper線上程中交互的典型例子

class LooperThread extends Thread {
    public Handler mHandler;
    public void run() {
        Looper.prepare(); // 為當前線程準備一個Looper
        // 創建Handler實例,Handler會獲取當前線程的Looper
        // 如果實例化Handler時當前線程沒有Looper,會報異常 RuntimeException
        mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
            }
        };
        Looper.loop(); // Looper開始運行
    }
}

Looper中的屬性

Looper持有MessageQueue;唯一的主線程Looper sMainLooper;Looper當前線程 mThread
存儲Looper的sThreadLocal

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    final MessageQueue mQueue; // Handler會獲取這個消息隊列實例(參考Handler構造器)
    final Thread mThread; // Looper當前線程

ThreadLocal並不是線程,它的作用是可以在每個線程中存儲數據。

Looper 方法

準備方法,將當前線程初始化為Looper。退出時要調用quit

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)); // Looper實例存入了sThreadLocal
}

prepare方法新建 Looper 並存入 sThreadLocal sThreadLocal.set(new Looper(quitAllowed))
ThreadLocal<T>

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

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

當要獲取Looper對象時,從sThreadLocal獲取

    // 獲取與當前線程關聯的Looper,返回可以為null
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

在當前線程運行一個消息隊列。結束後要調用退出方法quit()

public static void loop()

準備主線程Looper。Android環境會創建主線程Looper,開發者不應該自己調用這個方法。
UI線程,它就是ActivityThread,ActivityThread被創建時就會初始化Looper,這也是在主線程中預設可以使用Handler的原因。

public static void prepareMainLooper() {
    prepare(false); // 這裡表示了主線程Looper不能由開發者來退出
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}

獲取主線程的Looper。我們開發者想操作主線程時,可調用此方法

public static Looper getMainLooper()

請參考: http://rustfisher.github.io/2017/06/07/Android_note/Android-Handler/


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

-Advertisement-
Play Games
更多相關文章
  • 在 Update ueditor.config.js 文件中,xssFilter導致插入視頻異常,編輯器在切換源碼的過程中過濾掉img的_url屬性(用來存儲視頻url)_src/plugins/video.js里處理的是_url,而不是_src。 修改ueditor.config.js: img: ...
  • Ajax type這個裡面填什麼呢?通常大家都會說get和post。那麼還有2個是什麼呢 (預設: "GET") 請求方式 ("POST" 或 "GET"), 預設為 "GET"。註意:其它 HTTP 請求方法,如 PUT 和 DELETE 也可以使用,但僅部分瀏覽器支持。 此處是網上找來的解釋 1 ...
  • [1]引入 [2]代理和反射 [3]陷阱代理 [4]可撤銷代理 [5]模仿數組 [6]將代理用作原型 ...
  • 第一次在博客園寫博客,想把自己每一天學習到的知識點記錄下來,心裡有點緊張(PS:不知道自己能不能寫好......嘿嘿)。言歸正傳,咱們先來說說“ECMAScript”這到底是啥玩意兒?它和javascript的關係又是如何的?首先,在1996年11月的時候,javascript的創造者(網景公司Ne ...
  • 一、Canvas標簽: 1、HTML5<canvas>元素用於圖形的繪製,通過腳本(通常是javascript)來完成。 2、<canvas>標簽只是圖形容器,必須使用腳本來繪製圖形。 3、可以通過多種方法通過Canvas繪製路徑、盒、圓、字元以及添加圖像。 二、Canvas繪製圖形 1、繪製矩形 ...
  • (1)表格佈局 表格佈局容易掌握,佈局方便。但表格佈局需要通過表格的間距或者使用透明的gif圖片來填充佈局板塊間的間距,這樣佈局的網頁中表格會生成大量難以閱讀和維護的代碼;而且表格佈局的網頁要等整個表格下載完畢後才能顯示所有內容,所有表格佈局瀏覽速度較慢[2]。 (2)CSS+DIV佈局 通常要實現 ...
  • 介紹 來自API(backbone能做什麼?) 當我們開發含有大量Javascript的web應用程式時,首先你需要做的事情之一便是停止向DOM對象附加數據。 通過複雜多變的jQuery選擇符和回調函數很容易創建Javascript應用程式,包括在HTML UI,Javascript邏輯和數據之間保 ...
  • ObservableCollection<Evaluate> EvaluateList = new ObservableCollection<Evaluate>();//評論集合,這個集合中包含了一個圖片集合public ObservableCollection<PictureInfo> Pictu ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...