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 Runnablerun()
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.
以上。