Looper、Handler与HandlerThread

子线程与主线程通信

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,它会一直运行下去
    }

代码地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值