handler回调主线程_深入理解Android中的Handler异步通信机制

本文深入探讨了Android中的Handler异步通信机制,解释了为何不能在子线程直接修改UI,并介绍了Handler、Message Queue和Looper的工作原理。Handler在创建时会绑定到创建它的线程,主线程默认包含Looper和Message Queue,子线程需通过Looper.prepare()创建。通过Handler,子线程可以向主线程发送消息,反之亦然,实现了线程间的通信。此外,还讨论了Handler的实用API,如post系列方法在处理UI更新和延时任务中的应用。
摘要由CSDN通过智能技术生成

一、问题:在Android启动后会在新进程里创建一个主线程,也叫UI线程(非线程安全)这个线程主要负责监听屏幕点击事件与界面绘制。当Application需要进行耗时操作如网络请求等,如直接在主线程进行容易发生ANR错误。所以会创建子线程来执行耗时任务,当子线程执行完毕需要通知UI线程并修改界面时,不可以直接在子线程修改UI,怎么办?

解决方法:Message Queue机制可以实现子线程与UI线程的通信。

该机制包括Handler、Message Queue、Looper。Handler可以把消息/Runnable对象发给Looper,由它把消息放入所属线程的消息队列中,然后Looper又会自动把消息队列里的消息/Runnable对象广播到所属线程里的Handler,由Handler处理接收到的消息或Runnable对象。

1、Handler

每次创建Handler对象时,它会自动绑定到创建它的线程上。如果是主线程则默认包含一个Message Queue,否则需要自己创建一个消息队列来存储。

Handler是多个线程通信的信使。比如在线程A中创建AHandler,给它绑定一个ALooper,同时创建属于A的消息队列AMessageQueue。然后在线程B中使用AHandler发送消息给ALooper,ALooper会把消息存入到AMessageQueue,然后再把AMessageQueue广播给A线程里的AHandler,它接收到消息会进行处理。从而实现通信。

2、Message Queue

在主线程里默认包含了一个消息队列不需要手动创建。在子线程里,使用Looper.prepare()方法后,会先检查子线程是否已有一个looper对象,如果有则无法创建,因为每个线程只能拥有一个消息队列。没有的话就为子线程创建一个消息队列。

3、完整创建handler机制

Handler类包含Looper指针和MessageQueue指针,而Looper里包含实际MessageQueue与当前线程指针。

下面分别就UI线程和worker线程讲解handler创建过程:

首先,创建handler时,会自动检查当前线程是否包含looper对象,如果包含,则将handler内的消息队列指向looper内部的消息队列,否则,抛出异常请求执行looper.prepare()方法。

- 在UI线程中,系统自动创建了Looper 对象,所以,直接new一个handler即可使用该机制;

- 在worker线程中,如果直接创建handler会抛出运行时异常-即通过查‘线程-value'映射表发现当前线程无looper对象。所以需要先调用Looper.prepare()方法。在prepare方法里,利用ThreadLocal对象为当前线程创建一个Looper(利用了一个Values类,即一个Map映射表,专为thread存储value,此处为当前thread存储一个looper对象)。然后继续创建handler,让handler内部的消息队列指向该looper的消息队列(这个很重要,让handler指向looper里的消息队列,即二者共享同一个消息队列,然后handler向这个消息队列发送消息,looper从这个消息队列获取消息)。然后looper循环消息队列即可。当获取到message消息,会找出message对象里的target,即原始发送handler,从而回调handler的handleMessage() 方法进行处理。

094c799eed41fca9c4f49a5683ef0d51.png

handler机制

4、核心

- handler与looper共享消息队列,所以handler发送消息只要入列,looper直接取消息即可。

- 线程与looper映射表:一个线程最多可以映射一个looper对象。通过查表可知当前线程是否包含looper,如果已经包含则不再创建新looper。

5、基于这样的机制是怎样实现线程隔离的,即在线程中通信呢。

核心在于每一个线程拥有自己的handler、message queue、looper体系。而每个线程的Handler是公开的。B线程可以调用A线程的handler发送消息到A的共享消息队列去,然后A的looper会自动从共享消息队列取出消息进行处理。反之一样。

8133b7e3ef5c36a454ae9e36fd7b330f.png

子线程向主线程发送消息

cfbe150e38e0253a49d373ff31b75714.png

子线程之间通信

二、上面是基于子线程中利用主线程提供的Handler发送消息出去,然后主线程的Looper从消息队列中获取并处理。那么还有另外两种情况:

1、主线程发送消息到子线程中;

采用的方法和前面类似。要在子线程中实例化AHandler并设定处理消息的方法,同时由于子线程没有消息队列和Looper的轮询,所以要加上Looper.prepare(),Looper.loop()分别创建消息队列和开启轮询。然后在主线程中使用该AHandler去发送消息即可。

2、子线程A与子线程B之间的通信。

三、Handler里面有什么实用的API吗?

请记住:

Handler只是简单往消息队列中发送消息而已(或者使用post方式)

它们有更方便的方法可以帮助与UI线程通信。

如果你现在看看Handler的API,可以清楚看到这几个方法:

post

postDelayed

postAtTime

代码示例

这里的代码都是很基础的,不过你可以好好看看注释。

示例1:使用Handler的“post”方法

public class TestActivity extends Activity {

// ...

// all standard stuff

@Override

public void onCreate(Bundle savedInstanceState) {

// ...

// all standard stuff

// we're creating a new handler here

// and we're in the UI Thread (default)

// so this Handler is associated with the UI thread

Handler mHandler = new Handler();

// I want to start doing something really long

// which means I should run the fella in another thread.

// I do that by sending a message - in the form of another runnable object

// But first, I'm going to create a Runnable object or a message for this

Runnable mRunnableOnSeparateThread = new Runnable() {

@Override

public void run () {

// do some long operation

longOperation();

// After mRunnableOnSeparateThread is done with it's job,

// I need to tell the user that i'm done

// which means I need to send a message back to the UI thread

// who do we know that's associated with the UI thread?

mHandler.post(new Runnable(){

@Override

public void run(){

// do some UI related thing

// like update a progress bar or TextView

// ....

}

});

}

};

// Cool but I've not executed the mRunnableOnSeparateThread yet

// I've only defined the message to be sent

// When I execute it though, I want it to be in a different thread

// that was the whole point.

new Thread(mRunnableOnSeparateThread).start();

}

}

如果根本就没有Handler对象,回调post方法会比较难办。

示例2:使用postDelayed方法

近期本站新介绍的特性中,我每次都要模拟EditText的自动完成功能,每次文字改变后都会触发一个API的调用,从服务器中检索数据。

我想减少APP调用API的次数,所以决定使用Handler的postDelayed方法来实现这个功能。

本例不针对平行处理,只是关于Handler给消息队列发送消息还有安排消息在未来的某一点执行等。

// the below code is inside a TextWatcher

// which implements the onTextChanged method

// I've simplified it to only highlight the parts we're

// interested in

private long lastChange = 0;

@Override

public void onTextChanged(final CharSequence chars,

int start, int before, int count) {

// The handler is spawned from the UI thread

new Handler().postDelayed(

// argument 1 for postDelated = message to be sent

new Runnable() {

@Override

public void run() {

if (noChangeInText_InTheLastFewSeconds()) {

searchAndPopulateListView(chars.toString());

// logic

}

}

},

// argument 2 for postDelated = delay before execution

300);

lastChange = System.currentTimeMillis();

}

private boolean noChangeInText_InTheLastFewSeconds() {

return System.currentTimeMillis() - lastChange >= 300

}

最后我就把“postAtTime”这个方法作为联系留给读者们了,掌握Handler了吗?如果是的话,那么可以尽情使用线程了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值