展会信息港展会大全

进一步优化Android ListView GridView中异步加载图片
来源:互联网   发布日期:2015-11-26 09:31:56   浏览:4096次  

导读:最近在对编写完后的FileManager做优化,发觉其中异步加载图片的那块代码还需要在重构一下。首先我先说明一下,该FileManager中显示文件图标的控件为GridView,并且最大可视区域为20个图标,就是因为要同时显示20......

最近在对编写完后的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 Executor, ThreadPoolExecutor 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,执行提取缩略图的操作。好了,不废话,直接代码。

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) {

}

赞助本站

人工智能实验室

相关热词: 异步加载

AiLab云推荐
展开

热门栏目HotCates

Copyright © 2010-2024 AiLab Team. 人工智能实验室 版权所有    关于我们 | 联系我们 | 广告服务 | 公司动态 | 免责声明 | 隐私条款 | 工作机会 | 展会港