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

以上,不再神奇。

理解Activity.runOnUiThread()的更多相关文章

  1. 理解 Activity.runOnUiThread

    在开发 Android 应用的时候我们总是要记住应用主线程. 主线程非常繁忙,因为它要处理绘制UI,响应用户的交互,默认情况下执行我们写下的大部分代码. 好的开发者知道他/她需要将重负荷的任务移除到工 ...

  2. android Activity runOnUiThread() 方法的使用

    利用Activity.runOnUiThread(Runnable)把更新ui的代码创建在Runnable中,然后在需要更新ui时,把这个Runnable对象传给Activity.runOnUiThr ...

  3. android Activity runOnUiThread() 方法使用

    在android 中我们一般用 Handler 做主线程 和 子线程 之间的通信 . 现在有了一种更为简洁的写法,就是 Activity 里面的 runOnUiThread( Runnable )方法 ...

  4. Handler具体解释系列(七)——Activity.runOnUiThread()方法具体解释

    MainActivity例如以下: package cc.testui3; import android.os.Bundle; import android.view.View; import and ...

  5. 《转》深入理解Activity启动流程(四)–Activity Task的调度算法

    本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--Activity启 ...

  6. 《转》深入理解Activity启动流程(三)–Activity启动的详细流程2

    本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--Activity启 ...

  7. 《转》深入理解Activity启动流程(三)–Activity启动的详细流程1

    本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 深入理解Activity启动流程(一)--Activity启 ...

  8. 《转》深入理解Activity启动流程(二)–Activity启动相关类的类图

    本文原创作者:Cloud Chou. 出处:本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 在介绍Activity的详细启动流程之前,先为大家介绍Act ...

  9. 《转》深入理解Activity启动流程(一)–Activity启动的概要流程

    本文原创作者:Cloud Chou. 原文地址:http://www.cloudchou.com/android/post-788.html Android中启动某个Activity,将先启动Acti ...

随机推荐

  1. phpstorm修改创建文件时的默认注释

    之前也修改过,过了一段时间又忘了,记个笔记.下次好找 工具版本 工具设置里,File>>Settings...如下

  2. Log4j源码解析--Appender接口解析

    本文转自上善若水的博客,原文出处:http://www.blogjava.net/DLevin/archive/2012/07/10/382676.html.感谢作者的无私的分享. Appender负 ...

  3. fscanf和fprintf

    fscanf和fprintf fscanf的字符串是在键盘的缓冲区,fprintf是在显示器的缓冲区. 1.函数原型: int fprintf(FILE *fp, const char *format ...

  4. 文本与二进制关于\n的问题

    文本文件中: text = open(path, "r");windows中的换行符\n,在文件中windows在存储的时候会将它看成\r\n存储,用r在读取大小时会忽略\r的大小 ...

  5. AppScan 工作原理

    Rational AppScan(简称 AppScan)其实是一个产品家族,包括众多的应用安全扫描产品,从开发阶段的源代码扫描的 AppScan source edition,到针对 Web 应用进行 ...

  6. 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte

    'utf-8' codec can't decode byte 0xff in position 0: invalid start byte 觉得有用的话,欢迎一起讨论相互学习~Follow Me 今 ...

  7. Ubuntu14.04 安装vmware虚拟机

    下载VMware 链接:VMware 14  密码:5okh 移动VMware14 到 /opt #mv VMware-Workstation-Full-14.0.0-6661328.x86_64.b ...

  8. [转载]完全版线段树 by notonlysuccess大牛

    原文出处:http://www.notonlysuccess.com/ (好像现在这个博客已经挂掉了,在网上找到的全部都是转载) 今天在清北学堂听课,听到了一些很令人吃惊的消息.至于这消息具体是啥,等 ...

  9. CF 570D. Tree Requests [dsu on tree]

    传送门 题意: 一棵树,询问某棵子树指定深度的点能否构成回文 当然不用dsu on tree也可以做 dsu on tree的话,维护当前每一个深度每种字母出现次数和字母数,我直接用了二进制.... ...

  10. BZOJ 3744: Gty的妹子序列 [分块]

    传送门 题意:询问区间内逆序对数 感觉这种题都成套路题了 两个预处理$f[i][j]$块i到j的逆序对数,$s[i][j]$前i块$\le j$的有多少个 f我直接处理成到元素j,方便一点 用个树状数 ...