在Andorid中其他的线程不能直接的更新UI控件,在获取数据相对较费时间的处理中通常在其他 线程中完成,在该线程加载成功之后再由主线程执行对应的显示操作,这样就减少了对用户体验的影响。
消息循环(Message loop)包含一个线程和一个looper,Looper通常是用来管理当前Thread的消息队列,因此如果是一个消息循环就必然需要一个线程和一个Looper。主线程是一个消息循环,因此包含一个Looper对象,主线程的所有执行操作都是通过Looper管理的(获取对应的消息,并执行对应的操作)。而创建的Backgroun 线程通常不会包含Looper对象。同样可以通过HandlerTask来重载一个包含了Looper的类。
HandlerTask的实现中有关于Looper的处理过程,代码如下所示:
Message包含如下的一些域:
what:主要用于定义消息的类型。
obj: 主要用于保存数据
target:是指处理这个message的Handler对象。
Message的创建和处理都是由一个handler完成,因此通常target是自动填充。
同时消息只能在某个具体的Looper上消耗,因此每个Handler都会绑定一个Looper。但是多个Handler可以绑定同一个Looper,这也是在主线程中能够创建新的Handler的原因。因此各个Handler的消息是共存在同一个消息循环中。
具体的关系图如下所示:
Handler.obtainMessage()用于获取Message的实例,此时实际上就完成了Handler与Message的绑定关系。
在获取到Message以后就可以调用sendtoTarget()发送Message到Handler。而Handler接收到消息以后将对应的消息填充到Looper的消息队列循环的尾部。
HandlerThread.onLooperPrepared()在looper第一次检查队列的过程中调用,因此可以再该接口中创建Handler的实例。通常Handler的实例还需要创建一个重载handleMessage(),在该函数中实现对具体消息的处理。
Handler可以再其他的线程中使用,但是message的处理都是在创建Handler的线程中完成,也就是消耗在对应的Looper中。
在主线程中可以传递一个handler到其他的线程中,其他的线程通过该handler就可以完成将数据更新到主线程或者发生数据到主线程的功能。关于对该数据或者handler的消息的处理可以采用如下的两种方式实现:
(1)可以在主线程中实现handler的handleMessgae,
(2)也可以其他线程中通过传递的handler调用post接口。
方式一:
主线程中传递参数到其他的线程中,其他的线程保存对应的handler实体。同时主线程中需要实现当前handler的handleMessage接口。在其他线程完成相关的处理或者需要更新时,通过保存到handler实体发送数据到主线程中sendMessage()。这种实现方式要求主线程实现不同handler的handlerMessage函数。
基本的代码可以采用如下的方式:
主线程:
...
Thread background = new BackgroundThread(mHandler );
background .start();
...
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == MESSAGE_DOWNLOAD) {
Token token = (Token)msg.obj;
Log.i(TAG, "Got a request for url: " + requestMap.get(token));
handleRequest(token);
}
}
};
而在BackgroundThread中的处理则比较简单:
class BackgroundThread extends Thread {
Handler mHandler;
...
Message message = mHandler.Handler.obtainMessage();
...
/* 填充消息,然后发送消息 */
mHandler.sendMessage(message);
}
方式二:
在其他线程的构造函数中获取一个主线程的handler,在线程类中保存对应的handler实例,并在线程类中提供一个接口,该接口的目的是让主线程注册对应的接口,并在该接口中完成数据的处理操作,该接口通过设置lister的方式实现。主线程在创建该线程的过程中直接传递一个handler,并设置对应的监听lister,通常是完成UI控制相关的操作。待线程中有数据发送到主线程的时候,可以调用post接口,在post的参数中实现调用主线程设置的监听器,关于post方法的参数为Runnable对象,其中需要实现void run()方法,可以再该方法中调用主线程注册的监听器。
其他线程:
public class ThumbnailDownloader<Token> extends HandlerThread {
....
/* 分别保存主线程传递的Handler */
Handler mResponseHandler;
/* 保存主线程设置的监听器对象 */
Listener<Token> mListener;
/* 该接口由主线程或者接收数据的线程定义 */
public interface Listener<Token> {
void onThumbnailDownloaded(Token token, Bitmap thumbnail);
}
/* 该监听器就是对应的实现过程 */
public void setListener(Listener<Token> listener) {
mListener = listener;
}
public ThumbnailDownloader(Handler responseHandler) {
super(TAG);
mResponseHandler = responseHandler;
}
...
handleRequest(token);
...
private void handleRequest(final Token token) {
...
mResponseHandler.post(new Runnable() {
@Override
public void run() {
if (requestMap.get(token) != urlString) {
return;
}
requestMap.remove(token);
/* 通过接口对象更新主线程的UI控件 */
mListener.onThumbnailDownloaded(token, bitmap);
}
});
}
}
主线程中的处理:
/* 传递参数handler到其他线程中 */
mThumbnailThread = new ThumbnailDownloader(new Handler());
/* 设置监听器,实际就是实现对具体数据的处理过程 */
mThumbnailThread.setListener(new ThumbnailDownloader.Listener<ImageView>() {
@Override
public void onThumbnailDownloaded(ImageView token, Bitmap thumbnail) {
/* 实现具体的处理 */
if (isVisible()) {
token.setImageBitmap(thumbnail);
}
}
});
mThumbnailThread.start();
...
其实关于post的方法实际上是等价于如下的代码:
Runnable myRunnable = new Runnable() {
public void run() {
/*实际的处理函数*/
}
};
Message m = mHandler.obtainMessage();
m.callback = myRunnable;
也就是在message中设置了设置了callback后不会执行handler创建过程中定义的handleMessage接口,而是直接执行传递的Runnable中的run()函数。
方法二中的实现方式与Fragment与Activity之间通信的方式一样,提供服务的一方提供接口出去,而需要使用这种服务的一方实现具体的接口实现。在Activity中采用了在Activity类中implements对应接口的方式,而在多个线程之间的通信则采用了设置监听器的方式,监听器的参数中实现了对服务的处理方式。服务提供方直接调用接口对象即可实现线程之间的通信。
因此在Android要是习惯这种接口或者监听器的实现方式,即在其中一个类中定义接口,并通过某种方式获取到这个接口的实例对象,比如在Activity之间通过onAttach(),而在这边采用了设置监听器的方式。该对象时实现了具体接口的具体实体。通过该接口的实体即可实现两个类之间的通信。
A类:定义接口,并提供了获取该接口实例的方法通常采用setLister的方式实现.
B类:实现具体的接口,并通过setLister传递具体的接口实例到对象A中。
A通过B传递过来的接口对象即可调用具体的实现,完成A和B类之间的通信。A可以作为服务提供者,而B作为服务接受者。