GridView加载大量图片

项目中的需求,简单记录下来


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;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值