進一步優化Android ListView GridView中異步加載圖片

最近在對編寫完後的FileManager作優化,發覺其中異步加載圖片的那塊代碼還須要在重構一下。html

首先我先說明一下,該FileManager中顯示文件圖標的控件爲GridView,而且最大可視區域爲20個圖標,就是由於要同時顯示20個纔給我惹了大麻煩。java

簡單地說是因爲測試部在對FileManager的穩定性進行很是暴力的測試發生的問題,他們極其迅速地屢次上下來回滑動GridView,建立過多AsyncTask致使了CPU沒法負荷而發生ANR。這個問題也是因爲以前我對android線程的瞭解還不夠深刻所引起的。AsyncTask本質是屬於線程池,屢次new所消耗的資源遠遠超過了Thread,這就是爲何AsyncTask比較適合簡短、少次的異步操做。下面是官方解釋:
AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent pacakge such as ExecutorThreadPoolExecutor and FutureTask. android

而且那是每次觸發getView時若是cache中沒有圖片的bitmap話,就會new AsyncTask來執行獲取縮略圖的操做,這樣的後果就是那段代碼變成了垃圾。所以我就從網上尋找可行的優化方案,找到了一個比較靠譜的http://cindy-lee.iteye.com/blog/1300818,這回監聽了Scroll狀態,new的子線程是少了,但是我仔細想了想,感受他的代碼仍是能夠進一步優化(七樓就是我提的兩個改進方案)。個人終極優化方案就是應用啓動時就專門建立一個子線程來根據ScrollListener的狀態執行解析縮略圖的操做。緩存

終於黃天不服有心人,還真讓我在MIUI的FileExplore源碼和android的Contacts源碼中找到了。異步

SyncThumbnailExtractor是異步提取縮略圖的主類,最主要的就是繼承於HandlerThread的ExtractorThread,經過mExtractorHandler = new Handler(getLooper(), this)建立惟一的線程,產成消息隊列,這樣它會無限循環地處理send進來的Message,執行提取縮略圖的操做。好了,不廢話,直接代碼。ide

public class SyncThumbnailExtractor implements Callback{
    
    private static final String LOADER_THREAD_NAME = "FileIconLoader";
    /**
     * Type of message sent by the UI thread to itself to indicate that some
     * thumbnails need to be extracted.
     */
    private static final int MESSAGE_REQUEST_EXTRACTING = 1;

    /**
     * Type of message sent by the loader thread to indicate that some thumbnails
     * have been extracted.
     */
    private static final int MESSAGE_THUMBNAIL_EXTRACTED = 2;

	private boolean mPaused;
	private boolean mDecodingRequested = false;
	
	final Handler mMainHandler = new Handler(this);
	ExtractorThread mExtractorThread;
	private Context mContext ;
	
	private final ConcurrentHashMap<ImageView, FileInfo> mPendingRequests = new ConcurrentHashMap<ImageView, FileInfo>();
	private final static ConcurrentHashMap<String, ImageHolder> mImageCache = new ConcurrentHashMap<String, ImageHolder>(); 
	
	private static abstract class ImageHolder {
        public static final int NEEDED = 0;

        public static final int EXTRACTING = 1;

        public static final int EXTRACTED = 2;

        int state;

        public static ImageHolder create(String mime) {
            if(mime == null)
                return null;
            if(mime.contains(ThumbnailUtils.APK)){
                return new DrawableHolder();
            }
            else if(MediaFile.isImageByMimeType(mime) || 
                    MediaFile.isVideoByMimeType(mime)){
                return new BitmapHolder();
            }

            return null;
        };

        public abstract boolean setImageView(ImageView v);

        public abstract boolean isNull();

        public abstract void setImage(Object image);
    }
	
    private static class BitmapHolder extends ImageHolder {
        SoftReference<Bitmap> bitmapRef;

        @Override
        public boolean setImageView(ImageView v) {
            if (bitmapRef.get() == null)
                return false;
            v.setImageBitmap(bitmapRef.get());
            return true;
        }

        @Override
        public boolean isNull() {
            return bitmapRef == null;
        }

        @Override
        public void setImage(Object image) {
            bitmapRef = image == null ? null : new SoftReference<Bitmap>((Bitmap) image);
        }
    }

    private static class DrawableHolder extends ImageHolder {
        SoftReference<Drawable> drawableRef;

        @Override
        public boolean setImageView(ImageView v) {
            if (drawableRef.get() == null)
                return false;

            v.setImageDrawable(drawableRef.get());
            return true;
        }

        @Override
        public boolean isNull() {
            return drawableRef == null;
        }

        @Override
        public void setImage(Object image) {
            drawableRef = image == null ? null : new SoftReference<Drawable>((Drawable) image);
        }
    }

    private static class FileInfo{
        public FileInfo(String path,String mime){
            this.path = path;
            this.mime = mime;
        }
        
        public String path;
        public String mime;
    }
	
	public SyncThumbnailExtractor(Context context) {
	    mContext = context;
	}
	
	public void clear(){
	    mPaused = false;
	    mImageCache.clear();
	    mPendingRequests.clear();
	}
	
        //當前Activity調用OnDestory時,將ExtractorThread退出,並清空緩存 	
        public void stop(){
	    pause();
	    
	    if (mExtractorThread != null) {
	        mExtractorThread.quit();
	        mExtractorThread = null;
	    }

	    clear();
	}
	
	public void resume(){
	    mPaused = false;
            if (!mPendingRequests.isEmpty()) {
                requestExtracting();
            }
	}
	
	public void pause(){
	    mPaused = true;
	}
	
    /**
     * Load thumbnail into the supplied image view. If the thumbnail is already cached,
     * it is displayed immediately. Otherwise a request is sent to load the
     * thumbnail from the database.
     *
     * @param id, database id
     */
    public boolean decodeThumbnail(ImageView view, String path,String mime) {
        boolean extracted = loadCache(view, path, mime);
        if (extracted) {
            mPendingRequests.remove(view);
        } else {
            mPendingRequests.put(view, new FileInfo(path,mime));
            if (!mPaused) {
                // Send a request to start loading thumbnails
                requestExtracting();
            }
        }
        return extracted;
    }
    
    //set default icon by MimeType for unextracted mefile
    private void setImageByMimeType(ImageView image,String mime){
        if( mime.contains(ThumbnailUtils.APK)){ 
            image.setImageResource(R.drawable.apk);
        }
        else if (mime.contains(ThumbnailUtils.VIDEO)) {
            image.setImageResource(R.drawable.video);
        }
        else if (mime.contains(ThumbnailUtils.IMAGE)) {
            image.setImageResource(R.drawable.image);
        }
    }
	
    /**
     * Checks if the thumbnail is present in cache. If so, sets the thumbnail on the
     * view, otherwise sets the state of the thumbnail to
     * {@link  BitmapHolder#NEEDED}
     */
    private boolean loadCache(ImageView view, String path, String mime) {
        ImageHolder holder = mImageCache.get(path);
        
        if (holder == null) {
            holder = ImageHolder.create(mime);
            if (holder == null)
                return false;
            mImageCache.put(path, holder);
        } else if (holder.state == ImageHolder.EXTRACTED) {
            if (holder.isNull()) {
                setImageByMimeType(view, mime);
                return true;
            }
            // failing to set imageview means that the soft reference was
            // released by the GC, we need to reload the thumbnail.
            if (holder.setImageView(view)) {
                return true;
            }
            
            holder.setImage(null);
        } 
        
        setImageByMimeType(view, mime);
        holder.state = ImageHolder.NEEDED;
        return false;
    }
    
	/**
     * 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 thumbnails before any of those
     * requests are executed. This allows us to load images in bulk.
     */
    private void requestExtracting() {
        if (!mDecodingRequested) {
            mDecodingRequested = true;
            mMainHandler.sendEmptyMessage(MESSAGE_REQUEST_EXTRACTING);
        }
    }

    /**
    * @Description: handle MESSAGE_REQUEST_EXTRACTING message to create ExtractorThread and start    *                to extract thumbnail in mPendingRequests's file
    * @param msg
    * @return 
    */ 
    @Override
    public boolean handleMessage(Message msg) {
        switch(msg.what){
            case MESSAGE_REQUEST_EXTRACTING:
                mDecodingRequested = false;
                if (mExtractorThread == null) {
                    mExtractorThread = new ExtractorThread();
                    mExtractorThread.start();
                }
                mExtractorThread.requestLoading();
                return true;
            case MESSAGE_THUMBNAIL_EXTRACTED:
                if (!mPaused) {
                    processExtractThumbnails();
                }
                return true;
        }
        return false;
        
    }
    
    /**
     * Goes over pending loading requests and displays extracted thumbnails. If some of
     * the thumbnails still haven't been extracted, sends another request for image
     * loading.
     */
    private void processExtractThumbnails() {
        Iterator<ImageView> iterator = mPendingRequests.keySet().iterator();
        while (iterator.hasNext()) {
            ImageView view = iterator.next();
            FileInfo info = mPendingRequests.get(view);
            boolean extracted = loadCache(view, info.path, info.mime);
            if (extracted) {
                iterator.remove();
            }
        }

        if (!mPendingRequests.isEmpty()) {
            requestExtracting();
        }
    }

    
    
    private class ExtractorThread extends HandlerThread implements Callback{

        private Handler mExtractorHandler;
        /**
         * @Description: 
         * @param name
         */
        public ExtractorThread() {
            super(LOADER_THREAD_NAME);
        }
        
        /** 
         * Sends a message to this thread to extract requested thumbnails.
         */
        public void requestLoading() {
            if (mExtractorHandler == null) {
                mExtractorHandler = new Handler(getLooper(), this);
            }   
            mExtractorHandler.sendEmptyMessage(0);
        }   

        /**
        * @Description: extract thumbnail 
        * @param msg
        * @return 
        */ 
        @Override
        public boolean handleMessage(Message msg) {
            Iterator<FileInfo> iterator = mPendingRequests.values().iterator();
            while (iterator.hasNext()) {
                FileInfo info = iterator.next();
                
                ImageHolder holder = mImageCache.get(info.path);
                if (holder != null && holder.state == ImageHolder.NEEDED) {
                    // Assuming atomic behavior
                    holder.state = ImageHolder.EXTRACTING;

                    if(info.mime == null){
                        holder.setImage(FileUtil.sInvalidBmp);
                    } else {
                        if(info.mime.contains(ThumbnailUtils.APK)){
                            Drawable icon = ThumbnailUtils.getApkIcon(mContext, info.path);
                            holder.setImage(icon);
                        }
                        else if(MediaFile.isVideoByMimeType(info.mime)){
                            holder.setImage(ThumbnailUtils.getVideoThumb(info.path));
                        }
                        else if(MediaFile.isImageByMimeType(info.mime)){
                            holder.setImage(ThumbnailUtils.getScaleImageThumb(mContext, info.path));
                        }
                    }
                           
                    holder.state = BitmapHolder.EXTRACTED;
                    mImageCache.put(info.path, holder);
                }
            }

            mMainHandler.sendEmptyMessage(MESSAGE_THUMBNAIL_EXTRACTED);
            return true;
        }
        
    }
    
}

 

而後在Adapter的構造函數中建立該類:

public FileGridQueneAdapter(Context context, GridView gridView) {
    mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    mContext = context;
    syncThumbExtractor = new SyncThumbnailExtractor(context);
    mGridView = gridView;
    mGridView.setOnScrollListener(this);
}

getView中爲須要縮略圖的文件調用decodeThumbnail方法:

String mime = FileUtil.getMime(path);
if(ThumbnailUtils.isNeedSyncDecodeByMime(mime)){
    syncThumbExtractor.decodeThumbnail(icon, path, mime);
} else {
    icon.setImageBitmap(ThumbnailUtils.getThumbnail(mContext, path));
}

在Adapter中添加SyncThumbnailExtractor四個操做,供Activity以及ScrollListener使用:

public void clear(){
	    syncThumbExtractor.clear();
	}
	
	public void pause(){
	    syncThumbExtractor.pause();
	}
	
	public void stop(){
	    syncThumbExtractor.stop();
	}
	
	public void resume(){
	    syncThumbExtractor.resume();
	}

最後給GridView或者ListView添加ScrollListener:

@Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if(scrollState == OnScrollListener.SCROLL_STATE_FLING){
            pause();
        } else {
            resume();
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
            int totalItemCount) {
        
    }
相關文章
相關標籤/搜索