理解Activity.runOnUiThread()

来源:https://www.cnblogs.com/vhow/archive/2018/03/03/8496551.html
-Advertisement-
Play Games

UI線程很忙,忙著繪製界面,忙著響應用戶操作,忙著執行App程式員書寫的**多數**代碼 ...


這是一篇譯文(中英對照),原文鏈接:Understanding Activity.runOnUiThread()

When developing Android applications we always have to be mindful about our application Main Thread.

在開發Android應用時,經常需要對UI線程倍加留意。

The Main Thread is busy dealing with everyday stuff such as drawing our UI, responding to user interactions and generally, by default, executing (most) of the code we write.

UI線程很忙,忙著繪製界面,忙著響應用戶操作,忙著執行App程式員編寫的多數代碼。

譯註:多數——App程式員打交道最多的Activity、Service等組件的回調函數都在UI線程中運行。

A good developer knows she/he needs to off load heavy tasks to a worker Thread to avoid clogging the Main Thread and allow a smoother user experience and avoid ANR.

一個優秀的開發者,需要知道如何創建工作線程來完成耗時操作——不能事事都麻煩UI線程——這樣做既能讓用戶獲得更流暢的體驗,也能避免ANR。

譯註:流暢——UI線程的負擔輕了,才能夠專心繪製UI,才能夠及時處理用戶的輸入事件。

But, when the time comes to update the UI we must “return” to the Main Thread, as only he’s allowed to touch and update the application UI.

但是,還是得從工作線程返回到UI線程,畢竟只有UI線程才能更新UI。

A common way to achieve this is to call the Activity’s runOnUiThread() method:

如何返回呢?——常用方法是調用Activity的runOnUiThread()

runOnUiThread(new Runnable() {
    @Override
    public void run() {
        // 在這裡更新UI
    }
});

This will magically cause the Runnable code to get executed on the Main Thread.

上面的代碼很神奇,它能把Runnable中的代碼放到UI線程之中去執行。

Magical things are great… but only outside of our app source code.

神奇的東西好啊,但代碼可不能神奇。

譯註:“神奇”的代碼,意味著不瞭解,不靠譜,不值得依賴。

In this blog post, I will try to shed some light on what is actually going on inside runOnUiThread() and (hopefully) ruin the magic.

這篇博客,我會儘力解開runOnUiThread的面紗,讓它不再神奇。

打破神奇

Let‘s peek at the relevant parts of the Activity source code:

Activity中的相關源碼如下:

// android.app.Activity

final Handler mHandler = new Handler();

private Thread mUiThread;

@Override
public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

Seems pretty straightforward, first we check if the current Thread we’re running on is the Main Thread.
If it is the Main Thread — Great! Just invoke the Runnable run() method.

看起來挺直觀的。首先,檢查當前線程是否是UI線程。如果是,直接調用其run()方法。

But what if it’s not the Main Thread? In that case, we call mHandler.post() and pass in our Runnable.

但如果不是呢?就調用mHandler.post(),並傳入Runnable對象。

So what is actually happening here? Before we can answer that we really should talk about something called a Looper.

具體發生了什麼?在回答這個問題之前,我們需要先來看一下Looper

Looper

When we create a new Java Thread we override its run() method. A simple Thread implementation could look like that:

在Java中,可以創建一個Thread對象並覆寫其run()方法。像這樣:

public class MyThread extends Thread {
    @Override
    public void run() {
        // Do stuff
    }
}

Take a good look at that run() method, when the Thread finishes executing every statement inside of it, the Thread is Done. Finished. Useless.

看看run()方法,當其中指令執行完畢,線程也就終止了。除非…

If we want to reuse a Thread (a good reason would be to avoid spawning new Threads and reduce our memory footprint) we have to keep him alive and waiting for new instructions. A common way to achieve this is to create a loop inside the Thread’s run() method:

如果想重用線程(既可以避免切換線程所需的開銷,又能節省記憶體),就得讓線程保持存活,並等待和執行新的指令。常見的做法是在run()方法中創建一個迴圈:

public class MyThread extends Thread {
    @Override
    public void run() {
        while (running) {
            // Do stuff
        }
    }
}

As long as the while loop is running (ie: The run() method hasn’t finished yet) — that Thread is staying alive.

只要迴圈尚未終止,線程就保持存活。

That’s exactly what a Looper is doing: The Looper is.. well, LOOPING, and keeping its Thread alive.

這就是Looper所做的事:Looper,迴圈器,迴圈,保持線程存活。

Some things about the Looper worth mentioning:

  • Threads don’t get a Looper by default.
  • You can create and attach a Looper to a Thread.
  • There can only be one Looper per Thread.

關於Looper,請註意:

  • 預設情況下,線程沒有Looper
  • 你可以為線程創建一個與之關聯的Looper
  • 每個線程最多只能有一個Looper

So, Let’s go ahead and replace the while loop with a Looper implementation:

現在,用Looper替換上面的for迴圈方案:

public class MyThread extends Thread {
    @Override
    public void run() {
        Looper.prepare();
        Looper.loop();
    }
}

This is actually really simple:
上面的代碼很簡單:

Calling Looper.prepare() checks if there is no Looper already attached to our Thread (remember, only one Looper per Thread) and then creating and attaching a Looper.

調用Looper.prepare(),如果尚未有Looper附著於此線程,就創建一個Looper,並且和此線程相關聯。(提醒:每個線程只能有一個Looper)

Calling Looper.loop() cause our Looper to start looping.

調用Looper.loop(),開啟迴圈。

So, Now the Looper is looping and keeping our Thread alive, but there is no point in keeping a Thread alive without passing in instructions, work, things for our Thread to actually do…

目前,Looper持續迴圈並保持線程存活。但是,如果不能接受指令,不做事情,這又是何必呢。

Luckily, the Looper isn’t just looping. When we created the Looper a work queue was created with him. That queue is called the MessageQueue because he holds Message objects.

幸好,Looper不止是迴圈運行。當創建Looper對象的時候,一個與之關聯的隊列也被創建了。因為這個隊列是用來持有消息對象的,所以叫做消息隊列

消息

These Message objects are actually sets of instructions. They can hold data such as Strings and integers or they can hold tasks AKA Runnables.

這些消息對象是一些指令,它們能夠攜帶數據(例如字元串、整數等),也能夠攜帶Runnable任務。

So, when a Message enters Thread’s Looper Message queue, and when the Message turn (it is a queue after all) has come — The Message instructions are executed on that very Thread. Which means that…

所以,當一個消息對象被加到了線程Looper的消息隊列之後,等它出隊的時候,線程就會執行/處理這個消息。也就是說…

If we want a Runnable to be executed on a specific Thread, all we have to do is to put that Runnable into a Message and add that Message to the Thread’s Looper Message queue!

如果想讓一個Runnable任務在特定的線程中執行,我們需要做的就是:

  • Runnable封裝到一個消息對象里
  • 把消息加入到線程Looper消息隊列之中

Great! How do we do that? That’s Easy. We use a Handler. (You can see where this is going, right?)

好,具體怎麼做呢?——用Handler

Handler

The Handler is doing all the hard work.

Handler做了所有的重活兒。

He is responsible for adding Messages to a Looper’s queue and when their time has come he is responsible for executing the same Messages on the Looper’s Thread.

他(Handler)負責:

  • 添加消息。向消息隊列中添加消息。
  • 執行消息。在Looper線程中處理/執行這條消息。

When a Handler is created he is pointed toward a specific Looper. (ie: pointed toward a specific Thread)

當Handler被創建的時候,他指向一個特定的Looper。(也就指向一個特定的線程)

譯註:Handler → 線程 → Looper → 消息隊列

There are two ways to create a Handler:

有兩種創建Handler的方式:

(1) Specify its Looper in the constructor:

Handler handler = new Handler(Looper looper);

Now the handler is pointed toward the Looper (actually, the Looper MessageQueue) we provided.

一、在構造函數中指定Looper

Handler handler = new Handler(Looper looper); 

這個handler對象就指向傳入的looper(其實是looper的消息隊列)。

(2) Use empty constructor:

Handler handler = new Handler();

When using the empty constructor the Handler will automatically point toward the Looper attached to the current Thread. How convenient!

二、使用空構造函數

Handler handler = new Handler();

此時,這個handler自動指向和關聯到當前的線程。多方便啊!

譯註:誰創建的歸誰。例如,如果是在Activity中new Handler(),那構建出來的Handler對象就和UI線程相關聯。

The Handler has convenience methods to create Messages and automatically add them to its Looper queue.

Handler提供了一些便捷的方法,可以創建消息對象,並自動把它們加入到消息隊列之中。

For example, the post() method creates a Message and add it to the end of the Looper’s queue.

例如,post()方法會創建一個消息,並把它追加到消息隊列(隊尾)。

If we want this Message to hold a task (a Runnable) we simply pass the Runnable into the post() call:

如果你希望這個消息攜帶一個任務(Runnable),只傳入Runnable:

handler.post(new Runnable() {
    @Override
    public void run() {
        // Do stuff...
    }
});

面熟不?

再看Activity源碼

Now we can take a slightly more educated look at runOnUiThread():

經過之前的學習,再來看runOnUiThread()源碼:

// android.app.Activity

final Handler mHandler = new Handler();

private Thread mUiThread;

@Override
public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}

First, a Handler is created with an empty constructor.
Remember: this code is executed on the Main Thread.
That means mHandler is pointed toward the Main Thread Looper.

首先,通過空構造函數創建了一個Handler對象。註意,這個代碼是在UI線程上執行的,所以mHandler指向UI線程的Looper。

Yes, The application Main Thread is the only Thread we get with a Looper attached to him by default.

是的,UI線程自帶Looper。

So… when this line is getting executed:

所以,當這行代碼執行的時候:

mHandler.post(action);

The Handler is creating a Message that holds our Runnable, and that Message is then added to the Main Thread Looper’s queue, Where it will stay until the Handler will execute it on its Looper Thread — The Main Thread.

Handler會:

  • 創建一個消息(攜帶著傳入的Runnable)
  • 把這個消息添加到UI線程的Looper的消息隊列
  • 等輪到這個消息時,UI線程會執行/處理它

That’s it! No more magic.

以上。


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

-Advertisement-
Play Games
更多相關文章
  • pkill -kill -t 用戶的TTY pkill -9 用戶的TTY ...
  • 前言 前幾天剛學了Hadoop的安裝,幾乎把Hadoop的雷都踩了一個遍,雖然Hadoop的相關的配置文件以及原理還沒有完全完成,但是現在先總結分享一下筆者因為需要所整理的一些關於Hbase的東西。 一、Hbase概述 1.1什麼是Hbase? 首先我們還是來看看Hbase在百度上面是怎麼解釋的: ...
  • ##用例1:查詢數據 #01.查詢QQ號碼為54789625的所有好友信息,包括QQ號碼,昵稱,年齡 #02.查詢當前線上用戶的信息 #03.查詢北京的、年齡在18至45歲之間的線上用戶的信息 #04.查詢昵稱為青青草的用戶信息 #05.查詢QQ號碼為54789625的用戶的好友中每個省份的總人數, ...
  • 之前看源碼都是在Windows下用SourceInsight看,雖然達到了研究源碼的效果,但終究還是有遺憾。。。趁著周末,準備在Ubuntu虛擬機上下載編譯源碼。 之前下源碼時,有瞭解一些Android源碼的情況。網上的教程很多也是從谷歌官網下源碼,但是最近藍燈不好用,翻牆效率有點低,而且翻牆的網速 ...
  • 接上篇《Android進階之光》--Android新特性 No1: 組件: 1)底部工作條-Bottom Sheets 2)卡片-Cards 3)提示框-Dialogs 4)菜單-Menus 5)選擇器 6)滑塊控制項-Sliders 7)進度和動態 8)Snackbar(底部可操作彈出框)與Toas ...
  • Android 5.0新特性 1)全新的Material Design設計風格 2)支持多種設備 3)全新的通知中心設計--按照優先順序顯示 4)支持64位ART虛擬機 5)多任務視窗Overview 6)設備識別解鎖--比如附近信任設備 7)Ok Google語音指令 8)Face unlock面部 ...
  • 阿裡推薦原因:使用線程池可以減少創建和銷毀線程上所花的時間以及系統資源的開銷,然後之所以不用Executors自定義線程池,用ThreadPoolExecutor是為了規範線程池的使用,還有讓其他人更好懂線程池的運行規則。先說一下關於線程的概念任務:線程需要執行的代碼,也就是Runnable任務隊列 ...
  • 1. 什麼是面向對象? 以下一段話是我在百度上找的解釋: 面向對象(Object Oriented,OO)是軟體開發方法。面向對象的概念和應用已超越了程式設計和軟體開發,擴展到如資料庫系統、互動式界面、應用結構、應用平臺、分散式系統、網路管理結構、CAD技術、人工智慧等領域。面向對象是一種對現實世界 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...