Android编程权威指南(第二版)学习笔记(二十四)—— 第2

2017-05-18  本文已影响30人  kniost

本章顾名思义,讲了 Looper,Handler,以及线程之间的交互。

GitHub 地址:
完成24章但未完成挑战
完成24章挑战1
完成24章挑战2

在本章,我们要使用一个新的线程,并且使用其中 Looper 来完成下载图片的工作。为什么不直接在 AsyncTask 的 doInBackground 中添加网络下载代码呢?那样就要每次下载一张,直到完成全部100张的下载。最后才会执行 onPostExecute(...)方法,让所有下载的图片全部显示在 RecyclerView 视图中,这样既耗时又耗内存。AsyncTask 是执行后台线程的最简单方式,但它不适用于那些重复且长时间运行的任务。

1. 各种定义

1.1 Looper

Android 系统中,线程有一个消息队列(message queue)。使用消息队列的线程叫作消息循环(message loop)。消息循环会循环检查队列上是否有新消息。消息循环由线程和 looper 组成。Looper 对象管理着线程的消息队列。
主线程就是个消息循环,因此也拥有 looper。主线程的所有工作都是由其 looper 完成的。looper 不断从消息队列中抓取消息,然后完成消息指定的任务。

1.2 Message

Message 类是 Handler 的子类用于 Handler 传递消息,它有好几个实例变量,其中有三个需在实现时定义。

1.3 Handler

Handler 可看作 message handler 的简称。Handler 不仅仅是处理 Message 的目标(target),也是创建和发布 Message 的接口。创建 Handler 时,它会自动和该线程的 Looper 相关联。

1.4 三者关系

1.5 一个典型 Looper 的处理过程

首先需要准备好 Looper,然后建立一个其他线程能引用的 Handler,在需要的时候让 Handler 发消息给 Looper 让其执行。

2. HandlerThread

HandlerThread 类帮我们完成了建立 Looper 的过程,因此,只要继承它就能省去一些工作,并且避免一些风险。

// ThumbnailDowloadler.java 省略了许多内容
public class ThumbnailDowloader<T> extends HandlerThread {
    private static final int MESSAGE_DOWNLOAD = 0;

    private Boolean mHasQuit = false;
    private Handler mRequestHandler;

    @Override
    protected void onLooperPrepared() {
        // 新建一个 Handler
        mRequestHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == MESSAGE_DOWNLOAD) {
                    // get a photo and refresh view
                }
            }
        };
    }

    public void queueThumbnail(T target, String url) {
        // 外部调用,发消息给 Handler
        mRequestHandler.obtainMessage(MESSAGE_DOWNLOAD, target)
                    .sendToTarget();
    }

    public void clearQueue() {
        mRequestHandler.removeMessages(MESSAGE_DOWNLOAD);
    }
}

主线程中是这样的:

mThumbnailDownloader = new ThumbnailDownloader<>();
mThumbnailDownloader.start();
mThumbnailDownloader.getLooper();
// 在需要的时候调用
mThumbnailDownloader.queueThumbnail(holder, url);

3. 线程交互

主线程现在能够适时调用这个线程的方法,用于下载图片了。但是还存在一个问题,那就是下载线程下载完一个任务以后如何更新视图呢?我们知道 UI 只能在主线程里更新,所以我们采用在主线程里声明一个 Handler,传递给下载线程,让下载线程在下载完成后在主线程执行更新操作。因为不能直接引用主线程的方法,故而在这里用到了回调。

3.1 下载线程中

// ThumbnailDownloader,也就是下载线程中

// 成员声明
private Handler mResponseHandler;
private ThumbnailDowloadListener<T> mThumbnailDownloadListener;

// 回调接口
public interface ThumbnailDowloadListener<T> {
    void onThumbnailDownloaded(T target, Bitmap thumbnail);
}

public void setThumbnailDownloaderListener(ThumbnailDowloadListener<T> listener) {
    mThumbnailDownloadListener = listener;
}

// 通过构造函数传递主线程的 Handler
public ThumbnailDowloader(Handler responseHandler) {
    super(TAG);
    mResponseHandler = responseHandler;
}

这样,主线程通过调用这些方法,就能够让下载线程获取到主线程的 Handler 和回调接口实例。

3.2 主线程中

// 成员声明
private ThumbnailDowloader<PhotoHolder> mThumbnailDownloader;

// 传递实例给下载线程
// 这个 Handler 在主线程中建立,所以是和主线程 Looper 相关联的
Handler responseHandler = new Handler(); 
mThumbnailDownloader = new ThumbnailDowloader<>(responseHandler);
mThumbnailDownloader.setThumbnailDownloaderListener(
    new ThumbnailDowloader.ThumbnailDowloadListener<PhotoHolder>() {
        @Override
       public void onThumbnailDownloaded(PhotoHolder target, Bitmap thumbnail) {
            Drawable drawable = new BitmapDrawable(getResources(), thumbnail);
            target.bindDrawable(drawable);
        }
    }
);

3.3 在下载线程中给主线程 Looper 发送消息

现在,通过 mResponseHandler,下载线程能够访问与主线程 Looper 绑定的 Handler。同时,还有 ThumbnailDownloadListener 使用返回的 Bitmap 执行 UI 更新操作。具体来说, 就是通过 onThumbnailDownloaded 实现,使用新下载的 Bitmap 来设置 PhotoHolder 的 Drawable。
和在下载线程上把下载图片的请求放入消息队列类似,我们也可以返回定制 Message 给主线程,要求显示已下载图片。不过,这需要另一个 Handler 子类,以及一个 handleMessage(...) 覆盖方法。方便起见,我们转而使用另一个方便的 Handler 方法——post(Runnable)。

mResponseHandler.post(new Runnable() {
    @Override
    public void run() {
        if (mRequestMap.get(target) != url ||
                mHasQuit) {
            return;
        }

        mRequestMap.remove(target);
        mThumbnailDownloadListener.onThumbnailDownloaded(target, bitmap);
    }
});

在这里,新建的 Runnable 对象会被当成 Message 的回调方法,直接执行 run() 方法,所以相当于发送一个消息,里面写明了怎么做,而不是把对象和消息类型发给 Handler,让 Handler 决定怎么做。

4. 挑战练习

4.1 缓存层的添加

首先我们能够意识到,这个缓存应该放在下载线程中,因为所有下载的处理都在其中。LruCache 是一种类似键值对的存在,在图片缓存中,我们理所应当地可以将图片的 url 作为键,对应的 Bitmap 作为值,在下载一张图片之前先检查 Cache 中是否存在这个图片,不存在再下载。

// 其中省略了之前写过的大量过程

private LruCache<String, Bitmap> mCache;

@Override
protected void onLooperPrepared() {
    ……
    // 建立 Cache
    int maxCacheSize = 4 * 1024 * 1024; // 4MiB
    mCache = new LruCache<>(maxCacheSize);
}

private void handleRequest(final T target) {
    try {
        final String url = mRequestMap.get(target);

        if (url == null) {
            return;
        }

        final Bitmap bitmap;
        if (mCache.get(url) == null) {
            byte[] bitmapBytes = new FlickrFetchr().getUrlBytes(url);
            bitmap = BitmapFactory
                    .decodeByteArray(bitmapBytes, 0, bitmapBytes.length);
            Log.i(TAG, "Bitmap created");

            mCache.put(url, bitmap);
        } else {
            bitmap = mCache.get(url);
            Log.i(TAG, "Bitmap from cache");
        }
        ……
    } catch (IOException ioe) {
        Log.e(TAG, "Error downloading image", ioe);
    }
}

4.2 预加载

我用了较为笨的方法实现简单的预加载,那就是在 onBindViewHolder 的时候挨个加载对应 item 的前十个和后十个并放到缓存中。

// 下载线程中的方法
private void handlePreload(String url) {
    try {
        if (url == null) {
            return;
        }

        if (mCache.get(url) == null) {
            byte[] bitmapBytes = new FlickrFetchr().getUrlBytes(url);
            Bitmap bitmap = BitmapFactory
                    .decodeByteArray(bitmapBytes, 0, bitmapBytes.length);
            mCache.put(url, bitmap);
        }

    } catch (IOException ioe) {
        Log.e(TAG, "Error preloading image", ioe);
    }
}
// 主线程中的调用
@Override
public void onBindViewHolder(PhotoHolder holder, int position) {
    ……
    for (int i = Math.max(0, position - 10);
            i < Math.min(mGalleryItems.size() - 1, position + 10);
            i ++ ) {
        Log.i(TAG, "Preload position" + i);
        mThumbnailDownloader.queuePreloadThumbnail(mGalleryItems.get(i).getUrl());
    }
}

GitHub Page: kniost.github.io
简书:http://www.jianshu.com/u/723da691aa42

上一篇 下一篇

猜你喜欢

热点阅读