子线程与主线程通信
Android系统中,线程使用的收件箱叫做消息队列(message queue)。使用消息队列的线程叫做消息循环(message loop)。消息循环会不断循环检查队列上是否有新消息。消息循环由一个线程和一个looper组成。Looper对象管理着线程的消息队列。
主线程也是一个消息循环,因此具有一个looper。主线程的所有工作都是由其looper完成的。looper不断从消息队列中抓取消息,然后完成消息指定的任务。
Message与 Message Handler
消息是Message类的一个实例,包含好几个实例变量。其中有三个须在实现时定义:
what 用户定义的int型消息代码,用来描述消息;
obj 随消息发送的用户指定对象;
target 处理消息的Handler
Message的目标是Handler的一个实例。Message在创建时会自动与一个Handler相关联。Message在准备处理状态下,Handler是负责让消息处理行为发送的对象。Handler不仅仅是Message的目标(target),也是创建和发布Message的接口。
Looper拥有Message对象收件箱,所以Message必须在Looper上发布或读取。基于Looper与Message的这种关系,为与Looper协同工作,Handler总是引用着它。
一个Handler仅与一个Looper相关联,一个Message也仅与一个目标Handler相关联。多个Handler可与一个Looper相关联,这意味着一个Handler的Message可能与另一个Handler的Message存放在同一个消息队列中。
创建并启动后台线程
ThumbnailDownloader.java
package com.example.photogallery;
import java.io.IOException;
import java.security.PublicKey;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import android.R.interpolator;
import android.annotation.SuppressLint;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.location.GpsStatus.Listener;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
/**
* ThumbnailDownloader类需要使用某些对象来标识每一次下载。因此,在类创建对话框中,通过ThumbnailDownloader<Token>
* 的命名, 为其提供一个Token泛型参数。
*/
public class ThumbnailDownloader<Token> extends HandlerThread {
private static final String TAG = "ThumbnailDownloader";
private static final int MESSAGE_DOWNLOAD = 0;
Handler mHandler;
//requestMap是一个同步HashMap,使用Token作为key,可存储或与获取特定Token相关联的URL.
Map<Token, String> requestMap = Collections
.synchronizedMap(new HashMap<Token, String>());
/**
* HandlerThread能在主线程上完成任务的一种方式是,让主线程将其自身的Handler传递给HandlerThread。
* 主线程是一个拥有Handler和Looper的消息循环。主线程创建的Handler会自动与它的Looper相关联。我们可以
* 将主线程上创建的Handler传递给另一线程。传递出去的Handler与创建它的线程Looper始终保持着联系。因此,
* 任何已传出Handler负责处理的消息都将在主线程的消息队列中处理。这看上去就像我们在使用HandlerThread的
* Handler,实现在主线程上安排后台线程上的任务。
*/
Handler mResponseHandler;
Listener<Token> mListener;
public interface Listener<Token>{
void onThumbnailDownloaded(Token token, Bitmap bitmap);
}
public void setListener(Listener<Token> listener){
mListener = listener;
}
public ThumbnailDownloader(Handler responseHandler) {
super(TAG);
mResponseHandler = responseHandler;
}
/**
* 添加@SuppressLint("HandlerLeak")注解的原因:
* 这里,Android Lint将报出Handler类相关的警告信息。Looper控制着Handler的生死,
* 因此如果Handler是匿名内部类,则隐式的对象引用很容易导致内存泄漏。不过,所有的对象都与HandlerThread
* 绑定在一起,因此这里不用担心任何内存泄漏问题。
*
*
* HandlerThread.onLooperPrepared()方法调用发生在Looper第一次检查消息队列之前,
* 所以该方法成了我们创建Handler实现的好地方。
*/
@SuppressLint("HandlerLeak")
@Override
protected void onLooperPrepared() {
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if(msg.what == MESSAGE_DOWNLOAD){
/**
* 这里必须使用@SuppressWarnings("unchecked")注解,因为Token是泛型类参数,
* 而msg.obj是一个Object。由于类型擦除(type erasure),这里的强制类型转换应该是不可以的。
*/
@SuppressWarnings("unchecked")
Token token = (Token) msg.obj;
handleRequest(token);
}
}
};
}
public void queueThumbnail(Token token, String url) {
requestMap.put(token, url);
/**
* Handler.obtainMessage(...)方法会从公共循环持里获取消息,因此比创建新实例更有效率。
* Message.sendToTarget()方法会将message发生给它的Handler,紧接着Handler会将Message
* 放置在Looper消息队列的末尾。
*/
Message message = mHandler.obtainMessage(MESSAGE_DOWNLOAD, token);
message.sendToTarget();
}
private void handleRequest(final Token token){
try {
final String url = requestMap.get(token);
if(url == null)
return;
byte[] bitmapBytes = new FlickrFetchr().getUrlBytes(url);
final Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapBytes, 0, bitmapBytes.length);
/**
* Handler.post(Runnable)方法是一个张贴Message的便利方法
*/
mResponseHandler.post(new Runnable() {
@Override
public void run() {
/**
* 因为GridView会循环使用它的视图,ThumbnailDownloader完成Bitmap下载后,
* GridView可能已经循环使用了ImageView,并继续请求一个不同的URL。该检查可保证每个
* Token都能获取到正确的图片,即使中间发生了其他请求也无妨。
*/
if(requestMap.get(token) != url)
return;
requestMap.remove(token);
mListener.onThumbnailDownloaded(token, bitmap);
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 如果用户旋转屏幕,因ImageView视图的失效,ThumbnailDownloader则可能挂起。
* 如果点击这些ImageView,就可能发生异常。
*/
public void clearQueue(){
mHandler.removeMessages(MESSAGE_DOWNLOAD);
requestMap.clear();
}
}
启动与销毁后台线程ThumbnailDownloader
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
new FetchItemsTask().execute();
/**
* 创建并启动线程
* getLooper()方法必在start()之后调用,这是一种保证线程就绪的处理方式。
*
* Handler默认与当前线程的Looper相关联。该Handler是在onCreate(...)方法中创建的,因此它将与主线程的Looper相关联。
*/
mThumbnailThread = new ThumbnailDownloader<ImageView>(new Handler());
mThumbnailThread.setListener(new ThumbnailDownloader.Listener<ImageView>() {
@Override
public void onThumbnailDownloaded(ImageView imageView, Bitmap bitmap) {
//保证不会将图片设置到无效的ImageView视图上去
if(isVisible()){
imageView.setImageBitmap(bitmap);
}
}
});
mThumbnailThread.start();
mThumbnailThread.getLooper();
}
@Override
public void onDestroyView() {
super.onDestroyView();
mThumbnailThread.clearQueue();
}
@Override
public void onDestroy() {
super.onDestroy();
mThumbnailThread.quit();//结束线程。若不终止HandlerThread,它会一直运行下去
}