项目中的需求,简单记录下来
package com.ebensz.enote.notepad.logic.data;
import java.io.File;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.HandlerThread;
import android.os.Message;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import com.ebensz.enote.notepad.R;
import com.ebensz.enote.notepad.ui.adapter.BrowserGridAdapter;
/**
* Asynchronously loads contact THUMBNAIL and maintains cache of THUMBNAIL. The
* class is mostly single-threaded. The only two methods accessed by the loader
* thread are {@link #cacheBitmap}. Those methods access concurrent hash maps
* shared with the main thread.
*/
public class ThumbsLazyLoader implements Callback {
private static final String LOADER_THREAD_NAME = "GridThumbnailLoader";
/**
* Type of message sent by the UI thread to itself to indicate that some
* THUMBNAIL need to be loaded.
*/
private static final int MESSAGE_REQUEST_LOADING = 1;
/**
* Type of message sent by the loader thread to indicate that some THUMBNAIL
* have been loaded.
*/
private static final int MESSAGE_THUMBNAIL_LOADED = 2;
/**
* The resource ID of the image to be used when the photo is unavailable or
* being loaded.
*/
// private final int mDefaultResourceId;
/**
* Maintains the state of a particular photo.
*/
private static class ThumbnailHolder {
private static final int NEEDED = 0;
private static final int LOADING = 1;
private static final int LOADED = 2;
int state;
SoftReference<Bitmap> bitmapRef;
}
public ThumbsLazyLoader(Context context, int sampleSize, boolean fadeIn) {
this.context = context;
this.sampleSize = sampleSize;
this.fadeIn = fadeIn;
}
public void deleteImageForId(String path) {
if (mBitmapCache.containsKey(path)) {
mBitmapCache.remove(path);
}
}
/**
* Load photo into the supplied image view. If the photo is already cached,
* it is displayed immediately. Otherwise a request is sent to loadPageData the
* photo from the database.
*/
public void loadThumbnail(ImageView view, File item) {
if (item == null) {
mPendingRequests.remove(view);
}
else {
boolean loaded = loadCachedThumbnail(view, item);
if (loaded) {
mPendingRequests.remove(view);
}
else {
mPendingRequests.put(view, item);
if (!mPaused) {
// Send a request to start loading THUMBNAIL
requestLoading();
}
}
}
}
/**
* Checks if the photo is present in cache. If so, sets the photo on the
* view, otherwise sets the state of the photo to
* {@link ThumbnailHolder#NEEDED} and temporarily set the image to the
* default resource ID.
*/
private boolean loadCachedThumbnail(ImageView view, File item) {
ThumbnailHolder holder = mBitmapCache.get(item.getPath());
String path = (String) view.getTag();
if (holder == null) {
holder = new ThumbnailHolder();
mBitmapCache.put(item.getPath(), holder);
}
else if (holder.state == ThumbnailHolder.LOADED) {
if (holder.bitmapRef == null) {
//没有缩略图时显示"正在加载..."
setDefaultBackground(view,holder);
return false;
}
Bitmap bitmap = holder.bitmapRef.get();
if (bitmap != null && path != null && path.equals(item.getPath()) && !bitmap.isRecycled()) {
view.setImageBitmap(bitmap);
gridItemInvalidate(view);
return true;
}
}
//没有缩略图时显示"正在加载..."
setDefaultBackground(view, holder);
return false;
}
/**
* 专题的背景(二张、三张、多和纸效果),因为lazy加载,会出现背景有时错乱的问题,
* 在lazy线程完成数据加载时,只要刷新parent就会避免这个现象。具体原因不详。
*/
private void gridItemInvalidate(View view){
boolean loadForGrid = (sampleSize == BrowserGridAdapter.SAMPLE_SIZE_FOR_GRID);
if (loadForGrid) {
ViewGroup parent = (ViewGroup) view.getParent();
parent.invalidate();
}
}
private void setDefaultBackground(ImageView imageView, ThumbnailHolder holder) {
imageView.setImageResource(R.drawable.loading_data);
holder.state = ThumbnailHolder.NEEDED;
}
/**
* Stops loading images, kills the image loader thread and clears all
* caches.
*/
public void stop() {
pause();
if (mLoaderThread != null) {
mLoaderThread.quit();
mLoaderThread = null;
}
mPendingRequests.clear();
mBitmapCache.clear();
}
public void clear() {
mPendingRequests.clear();
mBitmapCache.clear();
}
/**
* Temporarily stops loading THUMBNAIL
*/
public void pause() {
mPaused = true;
}
/**
* Resumes loading THUMBNAIL
*/
public void resume() {
mPaused = false;
if (!mPendingRequests.isEmpty()) {
requestLoading();
}
}
/**
* Sends a message to this thread itself to start loading images. If the
* current view contains multiple image views, all of those image views will
* get a chance to request their respective THUMBNAIL before any of those
* requests are executed. This allows us to loadPageData images in bulk.
*/
private void requestLoading() {
if (!mLoadingRequested) {
mLoadingRequested = true;
mMainThreadHandler.sendEmptyMessage(MESSAGE_REQUEST_LOADING);
}
}
/**
* Processes requests on the main thread.
*/
public boolean handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_REQUEST_LOADING: {
mLoadingRequested = false;
if (!mPaused) {
if (mLoaderThread == null) {
mLoaderThread = new LoaderThread();
mLoaderThread.start();
}
mLoaderThread.requestLoading();
}
return true;
}
case MESSAGE_THUMBNAIL_LOADED: {
if (!mPaused) {
processLoadedImages();
}
return true;
}
}
return false;
}
/**
* Goes over pending loading requests and displays loaded THUMBNAIL. If some
* of the THUMBNAIL still haven't been loaded, sends another request for
* image loading.
*/
private void processLoadedImages() {
Iterator<ImageView> iterator = mPendingRequests.keySet().iterator();
while (iterator.hasNext()) {
ImageView view = iterator.next();
File item = mPendingRequests.get(view);
boolean loaded = loadCachedThumbnail(view, item);
if (loaded)
iterator.remove();
}
if (!mPendingRequests.isEmpty()) {
requestLoading();
}
}
/**
* Stores the supplied bitmap in cache.
*/
private void cacheBitmap(File item, Bitmap bitmap) {
if (mPaused) return;
ThumbnailHolder holder = mBitmapCache.get(item.getPath());
if (holder == null) {
holder = new ThumbnailHolder();
}
holder.state = ThumbnailHolder.LOADED;
if (bitmap == null) return;
mBitmapCache.put(item.getPath(), holder);
try {
holder.bitmapRef = new SoftReference<Bitmap>(bitmap);
}
catch (OutOfMemoryError e) {
// Do nothing - the photo will appear to be missing
}
catch (Exception ignored) {
}
}
/**
* Populates an array of photo IDs that need to be loaded.
*/
private void obtainThumbnailsToLoad(ArrayList<File> items) {
items.clear();
/*
* Since the call is made from the loader thread, the map could be
* changing during the iteration. That's not really a problem:
* ConcurrentHashMap will allow those changes to happen without throwing
* exceptions. Since we may miss some requests in the situation of
* concurrent change, we will need to check the map again once loading
* is complete.
*/
for (File item : mPendingRequests.values()) {
ThumbnailHolder holder = mBitmapCache.get(item.getPath());
if (holder != null && holder.state == ThumbnailHolder.NEEDED) {
// Assuming atomic behavior
holder.state = ThumbnailHolder.LOADING;
items.add(item);
}
}
}
/**
* The thread that performs loading of THUMBNAIL from the database.
*/
private class LoaderThread extends HandlerThread implements Callback {
private final ArrayList<File> mThumbnailItems = new ArrayList<File>();
private Handler mLoaderThreadHandler;
public LoaderThread() {
super(LOADER_THREAD_NAME);
}
/**
* Sends a message to this thread to loadPageData requested THUMBNAIL.
*/
public void requestLoading() {
if (mLoaderThreadHandler == null) {
mLoaderThreadHandler = new Handler(getLooper(), this);
}
mLoaderThreadHandler.sendEmptyMessage(0);
}
/**
* Receives the above message, loads THUMBNAIL and then sends a message
* to the main thread to process them.
*/
public boolean handleMessage(Message msg) {
loadThumbnail();
mMainThreadHandler.sendEmptyMessage(MESSAGE_THUMBNAIL_LOADED);
return true;
}
private void loadThumbnail() {
obtainThumbnailsToLoad(mThumbnailItems);
int count = mThumbnailItems.size();
if (count == 0) {
return;
}
for (int i = 0; i < count; i++) {
Bitmap bitmap = null;
File item = mThumbnailItems.get(i);
if (item != null) {
try {
// todo: 全尺寸的图,可以先查看Cache
bitmap = BackgroundDataAccess.loadThumbWithCreate(
item,
sampleSize
);
}
catch (Throwable e) {
e.printStackTrace();
}
}
cacheBitmap(item, bitmap);
}
}
}
/**
* A soft cache for THUMBNAIL.
*/
private final ConcurrentHashMap<String, ThumbnailHolder> mBitmapCache =
new ConcurrentHashMap<String, ThumbnailHolder>();
/**
* A map from ImageView to the corresponding photo ID. Please note that this
* photo ID may change before the photo loading request is started.
*/
private final ConcurrentHashMap<ImageView, File> mPendingRequests =
new ConcurrentHashMap<ImageView, File>();
private final Context context;
private int sampleSize;
private final boolean fadeIn;
/**
* Handler for messages sent to the UI thread.
*/
private final Handler mMainThreadHandler = new Handler(this);
/**
* Thread responsible for loading THUMBNAIL from the database. Created upon
* the first request.
*/
private LoaderThread mLoaderThread;
/**
* A gate to make sure we only send one instance of MESSAGE_THUMBNAIL_NEEDED
* at a time.
*/
private boolean mLoadingRequested;
/**
* Flag indicating if the image loading is paused.
*/
private boolean mPaused;
}