android 多线程 runnable,Android: Runnable多线程回调Callback更新UI时不工作的问题

概述

看名字有“多线程”和“更新UI”想必大家都知道问题很简单:UI只能在UI线程中更新,而不能在其他线程中。但有时候这种问题不太明显,比如在Runnable中通过Callback来更新UI,很容易就误以为是在UI线程中更新。

问题描述

简单Callback

先来看一个简单的Callback的例子。

layout:

layout中有一个TextView,用来展示信息;以及一个Button,点击此按钮后就会通过回调更新TextView的内容。

Another类

Another类定义一个回调接口,通过setCallback()设置回调,通过newMessage()进行回调。

MainActivity类

MainActivity类继承Another.Callback接口,回调即onNewMessage(),用来调用updateUI()更新UI。对Button监听点击事件,当触发时,实例化Another类,设置回调,并调用newMessage()。

这样在点击Button时,MainActivity调用Another的newMessage(),newMessage()随即回调onNewMessage(),让MainActivity更新UI。APP运行时效果就是点击按钮后Hello World!变为message from Another。这是一个简单的回调,很容易理解。

Runnable

但上面例子的实际情况是,调用Another和回调都在主线程,即UI线程完成,在调用时会阻塞主线程。而实际情况中,我们希望被调用的方法能够在新的线程中完成,不阻塞主线程,通过回调与调用者通信。看下面的例子。

Another类

Another类相比于之前,多了继承Runnable接口,run()会执行newMessage();而newThread()则会创建一个新的线程并启动这个线程,就是在新的线程中执行newMessage()了。

MainActivity类

相比于之前,只需要将

修改为

这样,当点击Button时,会调用Another的newThread(),此时newMessage()在新的线程中执行,不再阻塞主线程。而回调还是老样子。

咋一看是不是没问题?实际运行就会提示在textView.setText(string);这行出错:CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.。

原因分析

仔细想想原因很简单,当通过another.setCallback(self);设置回调时,实际上是将调用此方法的MainActivity的实例(记为mainActivity)的引用传递给了another(Another的实例),而回调实际上是在another中通过mainActivity调用onNewMessage()。注意,mainActivity是在UI线程(主线程)创建的,但回调时是在新的线程对mainActivity进行操作,那么更新UI也就在新线程中进行(因为没有出现线程切换),就出错了。

怎么样?问题是不是隐藏得有点深?(也可能是我笨没一眼看出来)上面的误区就在于以为mainActivity一直在UI线程,只要在mainActivity更新UI就不会有问题。实际情况是因为新线程和回调的存在,mainActivity不一定一直在UI线程中运行。

解决方法

最简单的解决方法就是another不要在新线程,直接在UI线程中运行就好了;但这样使得UI线程被阻塞,且回调也失去了本身的意义,不考虑。

借助于Android框架中的Handler和Message,我们可以轻松解决这个问题。

实现

我们不需要修改Another类,将MainActivity类修改为

相比于之前,增加了一个handler变量,此变量实例化了一个Handler(),并重载了handleMessage()处理接收到的Message的方法,即从Message中获取字符串信息,并调用updateUI()更新UI。对应在onNewMessage()中,每次回调都会构建一个新的Message,将回调的信息加入Message中,然后通过Handler发送此Message。

这样,APP运行时点击按钮后Hello World!就变成了message from Another。

原理

关于Handler和Message这里不再具体介绍,已经有很多文章专门解释过其工作原理。我们来看看这是怎么运行的,在MainActivity实例化时会创建handler,而此时一定在UI线程,所以handler工作在UI线程,即handleMessage()的执行一定在UI线程,所以updateUI()不会有问题。

Handler的好处在哪呢?Handler在处理接收到的Message时一定在创建Handler的线程,而向Handler加入Message,则可以在任意线程。也就是说,无论在哪个线程中,只要能够获取到handler,那么在向handler发送Message后,这个Message就一定会在创建handler的线程被取出并进行处理。

就如上的例子来看,onNewMessage()回调是在新线程中,但此时我们能够获取到handler,所以讲回调的信息包装成Message交给这个handler;而handler收到这个Message后,就会在UI线程对其进行处理,即调用handleMessage(),这时在UI线程执行updateUI(),更新UI,完成。

小结

本篇主要分析了Runnable多线程回调Callback更新UI时不工作或出错的问题,原因在于多线程的切换导致更新UI不在UI线程中执行。而Android的Handler和Message恰恰就是为应对这种情况,使用Handler不仅不会让代码逻辑混乱,反而会使代码结构更清晰。

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值