孙简

孙简 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

程序员,熟悉Java、Android应用程序开发。

个人动态

孙简 发布了文章 · 2018-03-03

理解Activity.runOnUiThread()

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

以上。

查看原文

赞 1 收藏 1 评论 0

孙简 回答了问题 · 2018-02-11

用`git reset --hard v1.0`进行版本跳转的报错

如果只是想"切换"到v1.0(一个tag),可以使用如下命令:

$ git checkout v1.0 -b v1.0-branch

上述命令会创建一个新的分支:v1.0-branch,其状态如下:

$ git status
On branch v1.0-branch
nothing to commit, working tree clean

其提交记录如下:

$ git log --oneline
981efb8 (HEAD -> v1.0-branch, tag: v1.0, dev) 阅读器基本完工,v1.0
b4e4305 完成页面结构和样式代码(html css)

P.S.

使用git reset命令是可以重置的:

$ git reset --hard v1.0
HEAD is now at 981efb8 阅读器基本完工,v1.0

如果你的本机环境依旧报错,可以尝试到其它文件夹进行git clone

关注 2 回答 1

孙简 回答了问题 · 2018-02-10

js写的鼠标覆盖,更改图片,为什么只在chrome中有效呀,360跟搜狗都没反应?

IE 11 报错:

SCRIPT438: 对象不支持“forEach”属性或方法

循环替换forEach是一个可行的解决方案:

for (let i = 0; i < _this.imgList.length; i++) {
    let img = _this.imgList[i];
    img.addEventListener("mouseenter", function () {
        _this.changeSrc(img);
    });
    img.addEventListener("mouseleave", function () {
        _this.changeSrc(img);
    })
}

关注 3 回答 2

孙简 回答了问题 · 2018-01-20

java函式自己调用自己

把你的代码调整成如下格式:

public static void main(String[] args) {
    f(3);
}

private static int f(int value) {
    if (value == 1) {
        return 1;
    } else {
        int ret = f(value - 1) + 1;
        return ret;
    }
}

f(3)最终返回3,函数的调用关系如下:

main
    → f(3)                // f(3) 等于 f(2) + 1 
        → f(2) + 1        // 递进
            → f(1) + 1    // 递进
                → f(1)    // 遇到临界值,即 value == 1
                ← 1       // 返回
            ← 1 + 1       // 回归
        ← 2 + 1           // 回归
    ← 3                   // f(3)返回3

关注 5 回答 4

孙简 回答了问题 · 2018-01-18

android 多个activity 是否需要销毁?

Android系统会自动对无用的activity实例对象进行GC,应用程序员需要注意的是:

  • 要在onDestroy()里释放相关资源,做好清理工作(e.g.关闭数据库连接,停掉线程等)
  • 不要在onDestroy()之后还持有其它对象的引用(否则会妨碍GC,造成memory leak)

https://developer.android.goo...

关注 5 回答 4

孙简 回答了问题 · 2018-01-18

java怎么控制多线程的事务??

我先用中文描述一下思路(假设有3个线程):

  • 设置一个共享变量,记录成功个数

    • successCounter,对于单个线程来说,如果成功了就对这个变量++;否则,已失败,不必再等。
  • 等待

    • 等3个线程都执行完
  • 判断结果

    • 如果successCounter等于3,整体成功。否则,失败。

代码:

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

public class App {
    public static void main(String[] args) {
        // 线程个数
        final int SIZE = 3;

        // 交给单个线程处理,成功了就加1
        final AtomicInteger successCounter = new AtomicInteger();

        final CountDownLatch latch = new CountDownLatch(SIZE);

        final ExecutorService pool = Executors.newCachedThreadPool();

        for (int i = 0; i < SIZE; i++) {
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    final boolean success = Math.random() > 0.1;
                    System.out.println(
                            Thread.currentThread().getName() +
                                    ", success = " + success);
                    if (success) {
                        // 单个成功
                        successCounter.getAndIncrement();
                        latch.countDown();
                    } else {
                        // 单个失败,亦整体失败
                        while (latch.getCount() > 0) {
                            latch.countDown();
                        }
                    }
                }
            });
        }

        pool.shutdown();

        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("成功的线程个数: " + successCounter);

        if (successCounter.get() == SIZE) {
            System.out.println("整体成功");
        } else {
            System.out.println("整体失败");
        }
    }
}

成功的case:

pool-1-thread-1, success = true
pool-1-thread-3, success = true
pool-1-thread-2, success = true
成功的线程个数: 3
整体成功

失败的case:

pool-1-thread-1, success = true
pool-1-thread-2, success = false
pool-1-thread-3, success = true
成功的线程个数: 2
整体失败

关注 9 回答 8

孙简 赞了回答 · 2016-07-16

解决单元格中元素捕获click,如何得到该单元格所在表格对象?

$(this).closest('table')

关注 4 回答 4

孙简 关注了问题 · 2016-07-16

在一个activity或者fragment中新建一个线程,activity销毁后,新建的线程也会销毁吗

在一个activity或者fragment中新建一个线程,activity销毁后,新建的线程也会销毁吗

关注 6 回答 4

孙简 回答了问题 · 2016-07-16

在一个activity或者fragment中新建一个线程,activity销毁后,新建的线程也会销毁吗

不会


线程何时销毁?

进程销毁的时候,它才会销毁。所以这个问题就变成——进程何时销毁。

进程何时销毁?

为了节省下次启动的时间,Android会尽可能的将进程驻留于内存中——按HOME键、返回键都不会销毁进程,只有在系统需要回收内存的时候才销毁进程。

用户的以下操作会造成进程被销毁:

  • 将应用从“最近应用”列表移除

  • 到“设置”里停止该应用

程序员如何让线程随Activity销毁

一个可行的方案是:

  • 在线程的run方法里处理InterruptedException异常

  • 在Activity的onStop()函数里interrupt线程

代码如下:

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private Thread thread;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        this.thread = new Thread() {
            @Override
            public void run() {
                while (true) {
                    try {
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
                        // 跳出循环,线程会完成运行
                        break;
                    }
                    Log.d(TAG, "run: t " + System.currentTimeMillis());
                }
            }
        };
        this.thread.start();
    }

    @Override
    protected void onStop() {
        super.onStop();
        if (this.thread != null) {
            this.thread.interrupt();
        }
    }

}

关注 6 回答 4

孙简 关注了问题 · 2016-07-10

上传图片问题

上传图片时,前端提交的图片JavaWeb是如何对接上传的呢?是怎样的实现代码呢?

关注 3 回答 0

认证与成就

  • 获得 9 次点赞
  • 获得 3 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 3 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2016-03-06
个人主页被 303 人浏览