android相册需求中,用gridView来做发现一个问题,性能太差。使用中发现,它必须等所有的图片加载完成,才会一次性显示出来。而在这期间会一直黑屏。
另外附带再说一个问题,不论你的图片加载的时候有没有进行压缩,如果不做其他处理的话,早晚会因为图片越来越多出现OOM的问题。
1、解决性能问题,我们可以用线程异步加载,一个imageView一个线程。imageView随着界面的变化可能绑定的图片会发生变换,所以需要记得取消之前的线程。加载新的图片。在这里还有一个很关键的问题,由于adapter的getView是由系统调用的,imageView的生命周期也是系统来控制的,如果我们对它进行了干预,那么我们非但没有优化性能,反而造成了内存泄露。比如系统在某个时刻对某个imageView设成了null。而我们的线程强引用了imageView,并在为imageView加载图片。这个时候这个imageView就不会被垃圾回收器回收,除非我们的线程没有被其他句柄引用以后。
为了不干扰android内部对imageView的管控,我们只能在我们的代码中弱引用imageView。
2、OOM的问题,在线程加载的时候维护一个bitmap的缓存,当页面滑动的时候,将在页面之外的图片用android提供的图片垃圾回收方法recycle()马上销毁。从而释放内存。
adapter的关键代码
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Log.d(TAG, "getView");
int windowWidth = ((Activity) context).getWindowManager().getDefaultDisplay().getWidth();
int pad = 4;
ImageView imageView;
if(convertView == null){
imageView = new ImageView(context);
}
else{
imageView = (ImageView)convertView;
}
//判断是否有线程在加载该图片,或者该imageView的图片已经改变
if (cancelPotentialLoad(fullPathImg.get(position), imageView)) {
AsyncLoadImageTask task = new AsyncLoadImageTask(imageView);
LoadedDrawable loadedDrawable = new LoadedDrawable(task);
imageView.setLayoutParams(new GridView.LayoutParams((windowWidth - pad * 12) / 4, (windowWidth - pad * 12) / 4));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setImageDrawable(loadedDrawable);
task.execute(position);
}
return imageView;
}
private boolean cancelPotentialLoad(String url,ImageView imageview){
//获得该图片是否有线程加载
AsyncLoadImageTask loadImageTask = getAsyncLoadImageTask(imageview);
if (loadImageTask != null) {
String bitmapUrl = loadImageTask.url;
//虽然该imageview的加载已经存在,但是url不同,取消原加载线程,并且重新加载
if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
loadImageTask.cancel(true);
} else {
// 相同的url已经在加载中,不需要加载
return false;
}
}
//需要加载图片
return true;
}
private class AsyncLoadImageTask extends AsyncTask<Integer, Void, Bitmap>{
private String url = null;
private final WeakReference<ImageView> imageViewReference; //为了不干扰android本身的对象清理,不造成内存泄露,因此使用弱应用保证回收资源
public AsyncLoadImageTask(ImageView imageview) {
super();
imageViewReference = new WeakReference<ImageView>(imageview);
}
//加载图片
@Override
protected Bitmap doInBackground(Integer... params) {
// TODO Auto-generated method stub
Bitmap bitmap = null;
this.url = fullPathImg.get(params[0]);
bitmap = BitmapUtil.getCompressBitmap((Activity) context, url,10);
cache.put(fullPathImg.get(params[0]), bitmap);
return bitmap;
}
@Override
protected void onPostExecute(Bitmap resultBitmap) {
//加载完成回收图片
if(isCancelled()){
resultBitmap = null;
}
else{
if(imageViewReference != null){
ImageView imageview = imageViewReference.get();
AsyncLoadImageTask loadImageTask = getAsyncLoadImageTask(imageview);
//当前任务
if (this == loadImageTask) {
//显示图片
imageview.setImageBitmap(resultBitmap);
}
}
}
super.onPostExecute(resultBitmap);
}
}
private AsyncLoadImageTask getAsyncLoadImageTask(ImageView imageview){
if (imageview != null) {
Drawable drawable = imageview.getDrawable();
if (drawable instanceof LoadedDrawable) {
LoadedDrawable loadedDrawable = (LoadedDrawable)drawable;
return loadedDrawable.getLoadImageTask();
}
}
return null;
}
public static class LoadedDrawable extends ColorDrawable{
private final WeakReference<AsyncLoadImageTask> loadImageTaskReference; //记录加载该图片的线程
public LoadedDrawable(AsyncLoadImageTask loadImageTask) {
//默认加载图片,空白
super(Color.TRANSPARENT);
loadImageTaskReference = new WeakReference<AsyncLoadImageTask>(loadImageTask);
}
public AsyncLoadImageTask getLoadImageTask() {
return loadImageTaskReference.get();
}
}
销毁屏幕之外的图片
public static OnScrollListener getOnScrollListener(final Map<String,Bitmap> cache,final List<String> path){
return new OnScrollListener(){
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
BitmapUtil.recycleBitmapCaches(0,firstVisibleItem,cache,path);
BitmapUtil.recycleBitmapCaches(firstVisibleItem+visibleItemCount,totalItemCount,cache,path);
}
@Override
public void onScrollStateChanged(AbsListView arg0, int arg1) {
// TODO Auto-generated method stub
}
};
}
public static void recycleBitmapCaches(int fromPosition,int toPosition,Map<String,Bitmap> cache,List<String> path){
Bitmap delBitmap = null;
for(int del=fromPosition;del<toPosition;del++){
delBitmap = cache.get(path.get(del));
if(delBitmap != null){
cache.remove(path.get(del));
delBitmap.recycle();
delBitmap = null;
}
}
}