进一步优化Android ListView GridView中异步加载图片

转自:http://my.oschina.net/DragonWang/blog/64489


最近在对编写完后的FileManager做优化,发觉其中异步加载图片的那块代码还需要在重构一下。

首先我先说明一下,该FileManager中显示文件图标的控件为GridView,并且最大可视区域为20个图标,就是因为要同时显示20个才给我惹了大麻烦。

简单地说是由于测试部在对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. 

并且那是每次触发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,执行提取缩略图的操作。好了,不废话,直接代码。

001public class SyncThumbnailExtractor implements Callback{
002     
003    private static final String LOADER_THREAD_NAME = "FileIconLoader";
004    /**
005     * Type of message sent by the UI thread to itself to indicate that some
006     * thumbnails need to be extracted.
007     */
008    private static final int MESSAGE_REQUEST_EXTRACTING = 1;
009 
010    /**
011     * Type of message sent by the loader thread to indicate that some thumbnails
012     * have been extracted.
013     */
014    private static final int MESSAGE_THUMBNAIL_EXTRACTED = 2;
015 
016    private boolean mPaused;
017    private boolean mDecodingRequested = false;
018     
019    final Handler mMainHandler = new Handler(this);
020    ExtractorThread mExtractorThread;
021    private Context mContext ;
022     
023    private final ConcurrentHashMap<ImageView, FileInfo> mPendingRequests = new ConcurrentHashMap<ImageView, FileInfo>();
024    private final static ConcurrentHashMap<String, ImageHolder> mImageCache = new ConcurrentHashMap<String, ImageHolder>();
025     
026    private static abstract class ImageHolder {
027        public static final int NEEDED = 0;
028 
029        public static final int EXTRACTING = 1;
030 
031        public static final int EXTRACTED = 2;
032 
033        int state;
034 
035        public static ImageHolder create(String mime) {
036            if(mime == null)
037                return null;
038            if(mime.contains(ThumbnailUtils.APK)){
039                return new DrawableHolder();
040            }
041            else if(MediaFile.isImageByMimeType(mime) ||
042                    MediaFile.isVideoByMimeType(mime)){
043                return new BitmapHolder();
044            }
045 
046            return null;
047        };
048 
049        public abstract boolean setImageView(ImageView v);
050 
051        public abstract boolean isNull();
052 
053        public abstract void setImage(Object image);
054    }
055     
056    private static class BitmapHolder extends ImageHolder {
057        SoftReference<Bitmap> bitmapRef;
058 
059        @Override
060        public boolean setImageView(ImageView v) {
061            if (bitmapRef.get() == null)
062                return false;
063            v.setImageBitmap(bitmapRef.get());
064            return true;
065        }
066 
067        @Override
068        public boolean isNull() {
069            return bitmapRef == null;
070        }
071 
072        @Override
073        public void setImage(Object image) {
074            bitmapRef = image == null ? null : new SoftReference<Bitmap>((Bitmap) image);
075        }
076    }
077 
078    private static class DrawableHolder extends ImageHolder {
079        SoftReference<Drawable> drawableRef;
080 
081        @Override
082        public boolean setImageView(ImageView v) {
083            if (drawableRef.get() == null)
084                return false;
085 
086            v.setImageDrawable(drawableRef.get());
087            return true;
088        }
089 
090        @Override
091        public boolean isNull() {
092            return drawableRef == null;
093        }
094 
095        @Override
096        public void setImage(Object image) {
097            drawableRef = image == null ? null : new SoftReference<Drawable>((Drawable) image);
098        }
099    }
100 
101    private static class FileInfo{
102        public FileInfo(String path,String mime){
103            this.path = path;
104            this.mime = mime;
105        }
106         
107        public String path;
108        public String mime;
109    }
110     
111    public SyncThumbnailExtractor(Context context) {
112        mContext = context;
113    }
114     
115    public void clear(){
116        mPaused = false;
117        mImageCache.clear();
118        mPendingRequests.clear();
119    }
120     
121        //当前Activity调用OnDestory时,将<span style="background-color:#ffffff;">ExtractorThread退出,并清空缓存</span>   
122        public void stop(){
123        pause();
124         
125        if (mExtractorThread != null) {
126            mExtractorThread.quit();
127            mExtractorThread = null;
128        }
129 
130        clear();
131    }
132     
133    public void resume(){
134        mPaused = false;
135            if (!mPendingRequests.isEmpty()) {
136                <span style="background-color:#ffffff;">requestExtracting</span>();
137            }
138    }
139     
140    public void pause(){
141        mPaused = true;
142    }
143     
144    /**
145     * Load thumbnail into the supplied image view. If the thumbnail is already cached,
146     * it is displayed immediately. Otherwise a request is sent to load the
147     * thumbnail from the database.
148     *
149     * @param id, database id
150     */
151    public boolean decodeThumbnail(ImageView view, String path,String mime) {
152        boolean extracted = loadCache(view, path, mime);
153        if (extracted) {
154            mPendingRequests.remove(view);
155        } else {
156            mPendingRequests.put(view, new FileInfo(path,mime));
157            if (!mPaused) {
158                // Send a request to start loading thumbnails
159                <span style="background-color:#ffffff;">requestExtracting</span>();
160            }
161        }
162        return extracted;
163    }
164     
165    //set default icon by MimeType for unextracted mefile
166    private void setImageByMimeType(ImageView image,String mime){
167        if( mime.contains(ThumbnailUtils.APK)){
168            image.setImageResource(R.drawable.apk);
169        }
170        else if (mime.contains(ThumbnailUtils.VIDEO)) {
171            image.setImageResource(R.drawable.video);
172        }
173        else if (mime.contains(ThumbnailUtils.IMAGE)) {
174            image.setImageResource(R.drawable.image);
175        }
176    }
177     
178    /**
179     * Checks if the thumbnail is present in cache. If so, sets the thumbnail on the
180     * view, otherwise sets the state of the thumbnail to
181     * {<a href="http://my.oschina.net/link1212" target="_blank" rel="nofollow">@link</a>  BitmapHolder#NEEDED}
182     */
183    private boolean loadCache(ImageView view, String path, String mime) {
184        ImageHolder holder = mImageCache.get(path);
185         
186        if (holder == null) {
187            holder = ImageHolder.create(mime);
188            if (holder == null)
189                return false;
190            mImageCache.put(path, holder);
191        } else if (holder.state == ImageHolder.EXTRACTED) {
192            if (holder.isNull()) {
193                setImageByMimeType(view, mime);
194                return true;
195            }
196            // failing to set imageview means that the soft reference was
197            // released by the GC, we need to reload the thumbnail.
198            if (holder.setImageView(view)) {
199                return true;
200            }
201             
202            holder.setImage(null);
203        }
204         
205        setImageByMimeType(view, mime);
206        holder.state = ImageHolder.NEEDED;
207        return false;
208    }
209     
210    /**
211     * Sends a message to this thread itself to start loading images. If the
212     * current view contains multiple image views, all of those image views will
213     * get a chance to request their respective thumbnails before any of those
214     * requests are executed. This allows us to load images in bulk.
215     */
216    private void requestExtracting() {
217        if (!mDecodingRequested) {
218            mDecodingRequested = true;
219            mMainHandler.sendEmptyMessage(MESSAGE_REQUEST_EXTRACTING);
220        }
221    }
222 
223    /**
224    * @Description: handle <span style="background-color:#ffffff;">MESSAGE_REQUEST_EXTRACTING message to create </span><span style="background-color:#ffffff;">ExtractorThread and start</span>    *                to extract thumbnail in mPendingRequests's file
225    * @param msg
226    * <a href="http://my.oschina.net/u/556800" target="_blank" rel="nofollow">@return</a>
227    */
228    @Override
229    public boolean handleMessage(Message msg) {
230        switch(msg.what){
231            case MESSAGE_REQUEST_EXTRACTING:
232                mDecodingRequested = false;
233                if (mExtractorThread == null) {
234                    mExtractorThread = new ExtractorThread();
235                    mExtractorThread.start();
236                }
237                mExtractorThread.requestLoading();
238                return true;
239            case MESSAGE_THUMBNAIL_EXTRACTED:
240                if (!mPaused) {
241                    processExtractThumbnails();
242                }
243                return true;
244        }
245        return false;
246         
247    }
248     
249    /**
250     * Goes over pending loading requests and displays extracted thumbnails. If some of
251     * the thumbnails still haven't been extracted, sends another request for image
252     * loading.
253     */
254    private void processExtractThumbnails() {
255        Iterator<ImageView> iterator = mPendingRequests.keySet().iterator();
256        while (iterator.hasNext()) {
257            ImageView view = iterator.next();
258            FileInfo info = mPendingRequests.get(view);
259            boolean extracted = loadCache(view, info.path, info.mime);
260            if (extracted) {
261                iterator.remove();
262            }
263        }
264 
265        if (!mPendingRequests.isEmpty()) {
266            requestExtracting();
267        }
268    }
269 
270     
271     
272    private class ExtractorThread extends HandlerThread implements Callback{
273 
274        private Handler mExtractorHandler;
275        /**
276         * @Description:
277         * @param name
278         */
279        public ExtractorThread() {
280            super(LOADER_THREAD_NAME);
281        }
282         
283        /**
284         * Sends a message to this thread to extract requested thumbnails.
285         */
286        public void requestLoading() {
287            if (mExtractorHandler == null) {
288                mExtractorHandler = new Handler(getLooper(), this);
289            }  
290            mExtractorHandler.sendEmptyMessage(0);
291        }  
292 
293        /**
294        * @Description: extract thumbnail
295        * @param msg
296        * <a href="http://my.oschina.net/u/556800" target="_blank" rel="nofollow">@return</a>
297        */
298        @Override
299        public boolean handleMessage(Message msg) {
300            Iterator<FileInfo> iterator = mPendingRequests.values().iterator();
301            while (iterator.hasNext()) {
302                FileInfo info = iterator.next();
303                 
304                ImageHolder holder = mImageCache.get(info.path);
305                if (holder != null && holder.state == ImageHolder.NEEDED) {
306                    // Assuming atomic behavior
307                    holder.state = ImageHolder.EXTRACTING;
308 
309                    if(info.mime == null){
310                        holder.setImage(FileUtil.sInvalidBmp);
311                    } else {
312                        if(info.mime.contains(ThumbnailUtils.APK)){
313                            Drawable icon = ThumbnailUtils.getApkIcon(mContext, info.path);
314                            holder.setImage(icon);
315                        }
316                        else if(MediaFile.isVideoByMimeType(info.mime)){
317                            holder.setImage(ThumbnailUtils.getVideoThumb(info.path));
318                        }
319                        else if(MediaFile.isImageByMimeType(info.mime)){
320                            holder.setImage(ThumbnailUtils.getScaleImageThumb(mContext, info.path));
321                        }
322                    }
323                            
324                    holder.state = BitmapHolder.EXTRACTED;
325                    mImageCache.put(info.path, holder);
326                }
327            }
328 
329            mMainHandler.sendEmptyMessage(MESSAGE_THUMBNAIL_EXTRACTED);
330            return true;
331        }
332         
333    }
334     
335}

 

然后在Adapter的构造函数中创建该类:
1public FileGridQueneAdapter(Context context, GridView gridView) {
2    mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
3    mContext = context;
4    syncThumbExtractor = new SyncThumbnailExtractor(context);
5    mGridView = gridView;
6    mGridView.setOnScrollListener(this);
7}

getView中为需要缩略图的文件调用decodeThumbnail方法:
1String mime = FileUtil.getMime(path);
2if(ThumbnailUtils.isNeedSyncDecodeByMime(mime)){
3    syncThumbExtractor.decodeThumbnail(icon, path, mime);
4} else {
5    icon.setImageBitmap(ThumbnailUtils.getThumbnail(mContext, path));
6}

在Adapter中添加SyncThumbnailExtractor四个操作,供Activity以及ScrollListener使用:
01public void clear(){
02    syncThumbExtractor.clear();
03}
04 
05public void pause(){
06    syncThumbExtractor.pause();
07}
08 
09public void stop(){
10    syncThumbExtractor.stop();
11}
12 
13public void resume(){
14    syncThumbExtractor.resume();
15}

最后给GridView或者ListView添加ScrollListener:
01@Override
02public void onScrollStateChanged(AbsListView view, int scrollState) {
03    if(scrollState == OnScrollListener.SCROLL_STATE_FLING){
04        pause();
05    } else {
06        resume();
07    }
08}
09 
10@Override
11public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
12        int totalItemCount) {
13     
14}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值