理解Activity.runOnUiThread()

xzw 2019-06-26

这是一篇译文(中英对照),原文链接: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.

以上。

相关推荐