从 AsyncTask 说起
在Android网络库之HttpURLConnection与数据解析我们提到了利用 AsyncTask 在后台运行耗时操作。
AsyncTask是执行后台线程的最简单方式,但它不适用于那些重复且长时间运行的任务。虽然耗时操作在后台运行,不会使系统崩溃,但是我们如果加载图片不能等它全都下载之后一起显示(内存占用也是一个问题)。
考虑到上面的问题,很多应用通常会选择仅在需要显示图片时才需要下载。
主线程
Android系统中,线程使用的收件箱叫作消息队列(message queue)。使用消息队列的线程叫作消息循环(message loop)。消息循环会循环检查队列上是否有新消息。消息循环由线程和looper组成。Looper对象管理着线程的消息队列。主线程就是个消息循环,因此也拥有looper。主线程的所有工作都是由其looper完成的。looper不断从消息队列中抓取消息,然后完成消息指定的任务。
1 | private ThumbnailDownloader<PhotoHolder> mThumbnailDownloader; |
- ThumbnailDownloader的
getLooper()
方法是在start()
方法之后调用的。(稍后会学习更多有关Looper的知识)这能保证线程就绪,避免潜在竞争(尽管极少发生)。因为getLooper()
方法能执行成功,说明onLooperPrepared()
方法肯定早已完成。这样,queueThumbnail()方法因Handler为空而调用失败的情况就能避免了。 - 在
onDestroy()
方法内调用quit()
方法结束线程。这非常关键。如不终止 HandlerThread ,它会一直运行下去,成为僵尸。
1 |
|
Handler、Looper、Message 结构分析
剖析Message
Message需要自己定义三个变量,分别是
- What:用户定义的int型消息代码,用来描述消息
- obj:用户指定,随消息发送的对象
- target:处理消息的 Handler
剖析 Handler
作用:创建、发布、处理 Message
我们来说一下 Handler 使用时的流程,结合一下代码分析。
1 | public void queueThumbnail(T target, String url){ |
- 我们在obtainMessage(…) 方法获取消息,紧接着用让这个Message调用 sendToTarget() 方法将其发送给它的Handler,Handler 会将这个Message放置在 Looper 消息队列的尾部。
- Looper 取得消息队列中的Message后,会将他发送给消息的目标Handler去处理。消息一般实在目标 Handler 的 Handler.handleMessage(…) 实现方法中进行处理的。
1 |
|
上述代码中,我们是在 onLooperPrepared()
方法里实现 Handler.handleMessage(...)
方法的。HandlerThread.onLooperPrepared() 是在Looper首次检查消息队列之前调用,所以该方法是创建Handler实现的好地方。
在Handler.handleMessage(…)方法中,首先检查消息类型(what),再获取obj值(T类型下载请求),然后将其传递给handleRequest(…)方法处理。(前面说过,队列中的下载消息取出并可以处理时,就会触发调用Handler.handleMessage(…)方法。handleRequest() 方法是下载执行的地方。在这里,确认URL有效后,就将它传递给FlickrFetchr新实例。确切地说,此处使用的是Android网络库之HttpURLConnection与数据解析中创建的FlickrFetchr.getUrlBytes(…)方法。
传递 Handler
我们使用 mRequestHandler,已经可以从主线程安排后台任务。
反过来,也可以从后台线程使用与主线程关联的Handler,安排主线任务
![从ThumbnailDownloader线程上规划主线程任务]
主线程是一个拥有 handler 和 Looper 的消息循环。主线程上创建的 Handler 会自动与它的 Looper 相关联。主线程上创建的这个 Handler 也可以传递给另一线程。传递出去的 Handler 与创建它的线程 Looper 始终保持着联系。因此,已传出 Handler 负责处理的所有消息都将在主线程的消息队列中处理。
1 | // HandlerThread 文件 |
1 | // 主线程创建的Handler |
那么就有一个疑问了,如果在这里面更新UI,那么在PhotoAdapter中的 onBindViewHolder()
中的
1 | mThumbnailDownloader.queueThumbnail(photoHolder, galleryItem.getUrl()); |
是什么用途呢?
其实显而易见 queueThumbnail()
方法的作用是向消息队列中添加下载请求,而下载之后负责更新UI是利用在主线程创建的 mResponseHandler 处理的。
说回正题,现在,通过mResponseHandler,ThumbnailDownloader能够使用与主线程 Looper 绑定的 Handler。同时,还有ThumbnailDownloadListener 使用返回的 Bitmap 执行UI更新操作。具体来说,就是通过 onThumbnailDownloaded 实现,使用新下载的 Bitmap 来设置 PhotoHolder 的 Drawable。
我们在 handleRequest 方法中添加下面代码
1 | mResponseHandler.post(new Runnable() { |
Message设有回调方法属性后,取出队列的消息是不会发给target Handler的。相反,存储在回调方法中的 Runnable 的 run()
方法会直接执行。
那么上述代码有什么作用呢?
- 首先,它再次检查 requestMap。这很有必要,因为 RecyclerView 会循环使用其视图。在ThumbnailDownloader下载完成 Bitmap 之后,RecyclerView可能循环使用了 PhotoHolder 并相应请求了一个不同的URL。该检查可保证每个PhotoHolder都能获取到正确的图片,即使中间发生了其他请求也无妨。
- 接下来,检查mHasQuit值。如果ThumbnailDownloader已经退出,运行任何回调方法可能都不太安全。
- 最后,从requestMap中删除配对的PhotoHolder-URL,然后将位图设置到目标PhotoHolder上。
总结
最后,我们做一个流程总结,当然,很多细节我没有写在里面,例如mResponse的监听器接口、清理队列、处理完一个Handler之后在Map中删除相应的target,仅供参考。
之后会从源码的角度继续分析Android的Handler机制。