前言

上次我们讲到了Android里面的AsyncTask以及它的用法。AsyncTask作为Android特有的一个异步类,对于简单的异步操作来说是非常方便的。但是对于一些操作比较多,或者是十分耗时的,长连接之类的,我们就无法使用AsyncTask来处理这些问题,这时候第二个法宝就来了:Message和message Handler。

介绍

消息

在进入下面的学习之前,我们需要先了解一下什么是Message,以及它和Handler(或者说是message handler)之间的关系。

消息是Message类的一个实例,包含有好多个实例变量。其中三个需要在实现时定义:

what 用户定义的int型消息代码,用来描述消息;

obj 随消息发送的用户指定对象;

target 处理消息的Handler。

Message的目标是Handler类的一个实例。Handler可看作是“message handler”的简称。Message在创建时,会自动与一个Handler相关联。Message在准备处理状态下,Handler是负责让消息处理行为发生的对象。

Handler

要处理消息以及消息指定的任务,首先需要一个消息Handler实例。Handler不仅仅是处理Message的目标(target),也是创建和发布Message的接口。

Looper拥有Message对象的收件箱,所以Message必须在Looper上发布或读取。基于Looper和Message的这种关系,为与Looper协同工作,Handler总是引用着它。

一个Handler仅与一个Looper相关联,一个Message也仅与一个目标Handler(也称作Message目标)相关联。Looper拥有整个Message队列。多个Handler可与一个Looper相关联,这意味着一个Handler的Message可能与另一个Handler的Message存放在同一个消息队列中。

使用Handler

消息的目标Handler通常不需要手动设置。一个比较理想的方式是,我们调用Handler.obtainMessage(...)方法创建消息并传入其他消息字段,然后该方法自动完成目标Handler的设置。

为了避免创建新的Message对象,Handler.obtainMessage(...)方法会从公共循环池里获取消息。因此相比创建新实例,这样有效率多了。

一旦取得Message,我们就调用sendToTarget()方法将其发送给它的Handler。紧接着Handler会将Message放置在Looper消息队列的尾部。

Looper取得消息队列中的特定消息后,会将它发送给消息目标去处理。消息一般是在目标的Handler.handleMessage(...)方法实现中进行处理的。

下面以一个通过url下载图片字节再转换成位图的例子来讲一下,先看一下代码:

public class ThumbnailDownloader extends HandlerThread {private static final String TAG = "ThumbnailDownloader";private static final int MESSAGE_DOWNLOAD = 0;Handler mHandler;Map requestMap = Collections.synchronizedMap(new HashMap());//3Handler mResponseHandler;//7Listener mListener;public interface Listener {void onThumbnailDownloaded(Token token, Bitmap thumbnail);}public void setListener(Listener listener) {mListener = listener;}public ThumbnailDownloader(Handler responseHandler) {super(TAG);mResponseHandler = responseHandler;}@SuppressLint("HandlerLeak")//1@Overrideprotected void onLooperPrepared() {mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {//5if (msg.what == MESSAGE_DOWNLOAD) {@SuppressWarnings("unchecked")//2Token token = (Token) msg.obj;Log.i(TAG,"Got a request for url: " + requestMap.get(token));handleRequest(token);}}};}public void queueThumbnail(Token token, String url) {Log.i(TAG, "Got an URL: " + url);requestMap.put(token, url);//4mHandler.obtainMessage(MESSAGE_DOWNLOAD, token).sendToTarget();}public void clearQueue() {//10mHandler.removeMessages(MESSAGE_DOWNLOAD);requestMap.clear();}private void handleRequest(final Token token) {try {final String url = requestMap.get(token);if (url == null)return;byte[] bitmapBytes = new FlickrFetchr().getUrlBytes(url);//6final Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapBytes, 0,bitmapBytes.length);Log.i(TAG, "Bitmap created");mResponseHandler.post(new Runnable() {//9public void run() {if (requestMap.get(token) != url)return;requestMap.remove(token);mListener.onThumbnailDownloaded(token, bitmap);}});} catch (IOException ioe) {Log.e(TAG, "Error downloading image", ioe);}}}
//1 首先先说一下onLooperPreprepared()方法上面的@SuppressLint("HandlerLeak")注解。这里,Android Lint将报出Handler类相关的警告信息。Looper控制着Handler的生死,因此如果Handler是匿名内部类,则隐式的对象引用很容易导致内存泄漏。不过,所有对象都与HandlerThread绑定在一起,因此这里不用担心任何内存泄漏的问题。

//2 至于了里面还有一个@SuppressWarnings("unchecked")的注解,是常见的普通注解。这里必须使用该注解,因为Token是泛型类参数,而Message.obj是一个Object。由于类型擦除,这里的强制类型转换是不行的。

//3 变量requestMap是一个同步HashMap。这里,使用Token作为key,可以存储或获取与特定Token相关连的URL。

//4 在queueThumbnail()方法中,将传入的Token-URL键值对放入map中。然后以Token为obj获取一条信息,并发送出去以存放到消息队列中。

//5 在onLooperPrepared()方法中,我们在Handler子类中实现了Handler.handleMessage(...)方法。因为HandlerThread。onooperPrepared()方法的调用发生在Looper第一次检查消息队列之前,所以该方法成了我们创建Handler实现的好地方。

在Handler。handleMessage(...)方法中,检查消息类型,获取Token,然后将其传递给handleRequest(...)方法。

//6 handleMessage(...)方法是下载动作发生的地方。我们可以在这里做一些消息传递的处理,例如上面的FlickrFetchr.getUrlBytes(...)方法。再用BitmapFactory将getUrlBytes(...)返回的字节数组转换为位图。

传递handler

HandlerThread能在主线程上完成任务的一种方式是,让主线程将其自身的Handler传递给HandlerThread。

主线程是一个拥有handler和Looper的消息循环。主线程上创建的Handler会自动与它的Looper相关联。我们可以将主线程上创建的Handler传递给另一个线程。传递出去的Handler与创建它的线程Looper始终保持着联系。因此,任何已经传出去的Handler负责处理的消息都将在主线程的消息队列中处理。这看上去就像我们在使用ThumbnailDownloader的Handler,实现在主线程上安排的后台线程上的任务。反过来,我们也可以从后台线程使用与主线程关联的Handler,安排要在主线程上完成的任务。

//7 在上面的ThumbnailDownloader中,变量mResponseHandler用来存放主线程的Handler。然后以一个接受Handler的构造方法替换原有构造方法,并设置变量的值,最后新增一个用来通信的监听器接口。

//8 修改你的UI线程所在的类,将Handler传递给ThumbnailDownloader,并设置Listener,将返回的BitMap设置给ImageView。记住,Handler默认与当前线程的Looper相关联。该Handler是在onCreate(...)方法中创建的,因此它将与主线程的Looper相关联。

@Overridepublic void onCreate(Bundle savedInstanceState) {...mThumbnailThread = new ThumbnailDownloader(new Handler());//8mThumbnailThread.setListener(new ThumbnailDownloader.Listener() {public void onThumbnailDownloaded(ImageView imageView,Bitmap thumbnail) {if (isVisible()) {imageView.setImageBitmap(thumbnail);}}});mThumbnailThread.start();mThumbnailThread.getLooper();Log.i(TAG, "Background thread started");}

通过mResponseHandler,ThumbnailDownloader能够访问与主线程Looper绑定的Handler。同时,它的Listener会使用返回的Bitmap执行UI更新操作。在调用imageView.setImageBitmap(Bitmap)方法之前,应首先调用Fragment.isVisible()检查方法,以保证不会将图片设置到无效的ImageView视图上去。

我们也可以返回定制Message到主线程上。不过这需要用到另一个Handler子类,以及一个handleMessage(...)覆盖方法。这里,我们使用的是另一种方便的Handler方法——post(Runable)。Handler.post(Runable)是一个张贴Message的便利方法。具体使用如下:

Runnable myRunnable = new Runable(){public void run(){/*Your code*/}};
//9 Message具有回调方法时,使用回调方法中的Runnable,而非其Handler目标来实现运行。因为mResponseHandler与主线程的Looper相关联,所以UI更新代码也是在主线程中完成的。

刚才的//9 的代码再次检查了一遍requestMap,这是因为GridView会循环使用它的视图。这个检查可以保证每个Token都能获得正确的图片,即使中间发生其他请求也没有关系。最后从requestMap中删除Token,然后将bitmap设置到Token上。

//10 如果用户旋转屏幕,因为ImageView视图的失效,ThumbnailDownloader则有可能会挂起。如果点击这些ImageView,就可能发生异常,所以要有清理队列外的所有请求。

既然视图已经销毁,那么在UI视图上也要有下载器清除代码

@Overridepublic void onDestroyView(){super.onDestroyView();mThumbnailThread.clearQueue();}
这一套流程也就到这里结束了,大家可以对比一下代码,看看它们是怎么联系起来的。

AsyncTask与Thread

看篇幅就知道AsyncTask比Handler和Looper简单很多,但是因为总总原因,下载图片这种场景还是不用AsyncTask。一个是因为AsyncTask的工作方式主要用于那些短暂且较多重复的任务。如果创建了大量的AsyncTask,或者长时间运行了AsyncTask,那么很可能是做了错误的选择。

在Android3.2版本起,AsyncTask不再为没一个AsyncTask实例单独创建一个线程。相反,它使用一个Executor在单一的后台线程上运行所有AsyncTask的后台服务。这意味着每一个AsyncTask都需要排队一个个来运行。那么长时间运行的AsyncTask就会把后面的AsyncTask阻塞。使用一个线程池executor虽然可以安全地并发运行多个AsyncTask,但不推荐这么做。如果真的要这么做,最好自己处理线程相关的工作,必要时使用Handler与主线程通信。




更多相关文章

  1. Android(安卓)Looper和Handler分析 .
  2. 初识Android的ReactiveX
  3. Android--Dialog
  4. 游走Android系列之handler
  5. Android事件总线分发库EventBus3.0的简单讲解与实践
  6. Android之Volley
  7. Android(安卓)NDK开发(六)——使用开源LAME转码mp3
  8. Android(安卓)WebView
  9. SurfaceView应用浅析

随机推荐

  1. PHP二维码的生成与识别案例
  2. PHP解决输出中文乱码问题讲解
  3. javascript基础之数据类型详解
  4. php rename错误原因的查找方法
  5. React项目如何使用Element的方法步骤
  6. JavaScript 对象创建的3种方法
  7. 如何解决php domdocument找不到的问题
  8. php将word转换为html格式代码分析
  9. Android(安卓)对话框【Dialog】去除白色
  10. android一些有用的网址