在开发 Android 应用的时候我们总是要记住应用主线程。

主线程非常繁忙,因为它要处理绘制UI,响应用户的交互,默认情况下执行我们写下的大部分代码。

好的开发者知道他/她需要将重负荷的任务移除到工作线程避免主线程阻塞,同时获得更流畅的用户体验,避免ANR的发生。

但是,当需要更新UI的时候我们需要“返回”到主线程,因为只有它才可以更新应用 UI。

最常用的方式是调用 Activity 的 runOnUiThread() 方法:

runOnUiThread(new Runnable() {void run() {// Do stuff…}
});

这样就可以神奇的将 Runnable 任务放到主线程中执行。

魔法是很棒。。。但是它存在与我们的应用源码之外。在本文中,我将尝试阐述runOnUiThread()中发生的一切,并且(希望)能够破解魔法。

破解魔法

我们一起来看看 Activity 源码中的相关部分:

final Handler mHandler = new Handler();
private Thread mUiThread;
// ...
public final void runOnUiThread(Runnable action) {if (Thread.currentThread() != mUiThread) {mHandler.post(action);} else {action.run();}
// ...
}

看起来非常简单,首先我们检查当前运行的线程是否是主线线程。

如果是主线程--很棒!只需要调用 Runnable 的 run() 方法。

但是如果不是主线程呢?
在这种情况下,我们会调用 mHandler.post() 并将我们的 Runnable 传递过去。所以究竟发生了什么事情?

在回答这个问题之前我们真的需要讨论一下一个称为 Looper 的东西。

一切都从 Looper 开始

当我们创建一个新的 Java 线程时,我们重写它的 run() 方法。一个简单的线程实现看起来应该是这样的:

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

好好的看一下 run() 方法,当线程执行完该方法中所有的语句后,线程就完成了。结束了。没用了。

如我我们想重复使用一个线程(一个很好的理由就是避免新线程创建以及减少内存消耗)我们必须让它保持存活状态并且等待接收新的指令。一个常用的方式就是在线程的 run() 方法里创建一个循环:

public class MyThread extends Thread {private boolean running;@Overridepublic void run() {while (running) {// Do stuff...}}
}

只要 while 循环还在执行(即 run() 方法还没有执行完毕)--这个线程就保持存活状态。

这就是 Looper 所做的事情:

Looper。。。就是 LOOPING,并保持它的线程处于存活状态

关于 Looper 以下几点值得注意:

  • 线程默认没有 Looper
  • 你可创建一个 Looper 并将它绑定到一个线程
  • 每一个线程只能绑定一个 Looper

所以,我们将线程中的 while 循环用 Looper 实现来替换:

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

真的很简单:

调用 Lopper.prepare() 是检查当前线程是否还没有绑定 Lopper(记住,每一个线程只能绑定一个 Looper),如果没有就创建一个 Looper 并和当前线程绑定。

调用 Looper.loop() 触发我们的 Looper 开始循环。

所以,现在 Looper 开始循环并保持线程处于存活状态,但是如果不能传递指令、任务或者其他事情让线程执行实际的任务,那么保持线程存活没有任何意义。。。

幸好,Looper 不仅仅是循环。
当我们创建 Looper 的时候,会一并创建一个工作队列。这个队列称为消息队列因为它持有消息(Message)对象。

消息是什么?

这些消息对象实际上就是一系列指令
他们可以持有数据比如字符串、整数等,也可以只有任务比如 Runnables。

所以,当一个消息进入线程的 Looper消息队列,并且轮到它(毕竟它是一个队列)的时候--消息指令就会在队列所在的线程执行。这意味着。。。。:

如果我们希望一个 Runnable 在指定的线程运行,我们只需要将它放到一个消息里,并将这个消息放到对应线程的 Looper 消息队列就可以了!

很棒!我们怎么实现呢?
很简单。我们使用 Handler

Handler

Handler 干了所有的活。

它负责向 Looper 的队列添加消息,当轮到消息执行时,它负责在 Looper 所在的线程中执行同一条消息。

当一个 Handler 被创建的时候,会被指向一个指定的 Looper(即,指向一个指定的线程)

创建 Handler 有两种方法:

  • 1、在构造函数中指定 Looper:
    Handler handler = new Handler(Looper looper);

    现在 handler指向了我们提供的Looper(实际上是 Looper 的消息队列)

  • 2、使用空的构造函数:
    Handler handler = new Handler();

    当我们使用空构造函数的时候,Handler 会自动指向和当前线程绑定的 Looper。真方便!
    Handler 提供了很方便的方法用于创建消息并自动将它们添加到 Looper 消息队列。

例如,post() 方法就创建一条消息并将它添加到 Looper 队列的尾部。

如果我们希望消息持有一个任务(一个 Runnable),我们简单的将 Runnable 对象传递给 post()方法就可以:

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

看起来很熟悉?

再来看看 Activity 的源码

现在我们再仔细的看一看runOnUiThread():

final Handler mHandler = new Handler();
private Thread mUiThread;
// ...
public final void runOnUiThread(Runnable  action) {if (Thread.currentThread() != mUiThread) {mHandler.post(action);} else {action.run();}
// ...
}

首先,mHandler 是使用空构造函数创建。

记住:这段代码是在主线程中执行,这意味着 mHandler 指向主线程的 Looper。

是的,应用主线程是唯一一个默认绑定了 Looper 线程。

所以。。。当这一行代码执行的时候:

mHandler.post(action);

Handler 会创建一条持有我们传入的 Runnable 的消息,这条消息随后被添加到主线程的消息队列,然后等待 Handler 在它的Looper线程(主线程)中执行。

就是这样!魔力不再。

理解 Activity.runOnUiThread相关推荐

  1. 深入理解Activity启动流程(三)–Activity启动的详细流程2

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

  2. android Activity runOnUiThread() 方法使用

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

  3. 深入理解Activity启动流程(二)–Activity启动相关类的类图

    本文原创作者:Cloud Chou. 欢迎转载,请注明出处和本文链接 本系列博客将详细阐述Activity的启动流程,这些博客基于Cm 10.1源码研究. 在介绍Activity的详细启动流程之前,先 ...

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

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

  5. 深入理解Activity启动模式之launchMode

    Android每个Application都是由若干个四大组件组成的.每个页面都是一个Activity,当需要打开相应页面(Activity)时系统会创建他们的实例并把他们一一放入栈中进行管理.任务栈是 ...

  6. 深入理解Activity启动模式之大结局

    谈起Activity的启动模式必不可少的要是launchMode.Flags.taskAffinity这三块知识点,上一篇文章 深入理解Activity启动模式之launchMode 看过的同学都知道 ...

  7. 深入理解Activity的生命周期

    当用户在系统与你的应用之间切换的过程中,你的应用中的Activity实例也会在自己的不同生命周期中切换.例如,用户第一次打开你的应用,应用展现在用户的手机桌面,获取用户的输入焦点.在这个过程中,And ...

  8. 学习笔记之一:深入理解Activity的生命周期

    Activity作为安卓的四大组件之一,我们可以理解成界面.正常情况下,除了Window,Dialog和Toast,我们能看见的界面的确只有Activity. Activity的生命周期可分为两种情况 ...

  9. 实验3:理解Activity 的生命周期

    代码清单3-1 新增一个TAG常量(QuizActivity.java) 代码清单3-2 为onCreate(...)方法添加日志输出代码(QuizActivity.java) 代码清单3-3 覆盖更 ...

最新文章

  1. webservice 测试窗体只能用于来自本地计算机的请求
  2. mysql语句中事务可靠性_MySql的事务使用与示例详解
  3. Python:Selenium 1:浏览器驱动
  4. TDSQL在巴黎ICDE上设立展台,掌声送给它!
  5. eclipse如何给main函数传参数
  6. jquery 滚动条插件
  7. Python数据类型-----列表
  8. python二叉树最大深度的计算_Python学习笔记24(二叉树遍历、最大深度、最大宽度)...
  9. Takeown 实现解析
  10. python -----class(类)中的object是什么意思?
  11. jmeter的http cookies管理器使用
  12. excel文件修复工具_免费的PDF转Word软件有哪些?各种文件格式转换工具推荐
  13. 1586 - Molar mass
  14. mysql计算连续天数,mysql连续登录天数,连续天数统计
  15. 初识Python,我想你应该了解这些...
  16. 长租公寓全员「戒掉」租金贷会怎样?
  17. pom文件配置阿里云仓库 转
  18. 逐梦旅程(著:毛星云)---学习笔记第三章
  19. html img设置形状,图片img直接设置样式
  20. linux7 seinfo,SELinux+

热门文章

  1. C++知识点9——函数重载,默认实参,内联函数
  2. matlab中实时脚本与纯代码脚本
  3. VS中stack around the variable ‘****‘ was corrupted堆栈被破坏
  4. 做数据科学领域的「召唤师」,组织一场人人可参与的数据科学比赛
  5. Facebook高管:我们是科技公司 不是媒体公司
  6. Nexenta和ParaScale发布开源存储产品
  7. hdfs.DFSClient: Exception in createBlockOutputStre
  8. HTML的base href = “” /
  9. 最短路算法总结(入门版)
  10. 论场景在研发中的重要性