Bitmap(一) http://www.cnblogs.com/fishbone-lsy/p/4486571.html
Bitmap(二) http://www.cnblogs.com/fishbone-lsy/p/4496166.html
Bitmap(三) http://www.cnblogs.com/fishbone-lsy/p/4526922.html
在Bitmap(一)中介绍了,Bitmap如何通过BitmapFactory获得图片资源。在Bitmap(二)中,介绍了将Bitmap进行预加载,缩放,再载入ImageView。Bitmap(三)中,介绍了通过异步任务加载图片,并使用弱引用封装ImageView。
上面三节在加载单张图片时,已经满足基本要求了。但是,在多个图片的ListView中,那个方法就不适用了。
上一节最后的代码是这样的
LoadImageTask task = new LoadImageTask(imageView1 , 100 , 100); task.execute(R.drawable.images);
如果我将上面一段代码放在自定义Adapter里面的getView中,每一个Item都会开启一个新的线程。由于Item在getView中会以viewHolder的形式重用,当用户疯狂地上下滑动ListView时,就会产生N个LoadImageTask在为同一个ImageView工作。这显然是非常不科学的。
我们希望,每个ImageView只有一个属于自己的线程,如果这个ImageView被重用有了新的任务,那么它前面未加载完成的任务也应该中止掉。因此在这一节中,我们试图将ImageView和LoadImageTask绑定起来,顺便给它一个defaultBitmap用作加载过程中的显示图片。它的最终形式应该是这个样子
public void loadBitmap(int resId, ImageView imageView , int defaultResId , int reqWidth , int reqHeight)
使用它时,应该只需要这样子
loadBitmap(R.drawable.images2 , imageView1 , R.drawable.images , 100 , 100);
好了,开始说怎么做。第一步,就如上面所说,我们要绑定ImageView和LoadImageTask,顺便加上defaultBitmap。所以首先使用了BitmapDrawable:
/** * 将Bitmap的加载task封装进BitmapDrawable中,设置默认图,并在加载完新图后换掉 */ private class AsyncDrawable extends BitmapDrawable{ private final WeakReference<LoadImageTask> bitmapWorkTask ; public AsyncDrawable(Resources resources, Bitmap bitmap, LoadImageTask bitmapWorkTask){ super(resources,bitmap); this.bitmapWorkTask = new WeakReference<LoadImageTask>(bitmapWorkTask); } public LoadImageTask getLoadImageTask(){ return bitmapWorkTask.get(); } }
在上面的代码中,通过一个弱引用,将LoadImageTask和Bitmap绑定了起来。
然后,将封装了Bitmap和LoadImageTask的AsyncDrawable,通过imageView.setImageDrawable(asyncDrawable);直接赋给ImageView,最后再开始加载异步任务。
/** * 加载图片 * @param resId * @param imageView * @param defaultResId * @param reqWidth * @param reqHeight */ public void loadBitmap(int resId, ImageView imageView , int defaultResId , int reqWidth , int reqHeight) { //判断任务是否正在进行 if (cancelPotentialWork(resId, imageView)) { final LoadImageTask task = new LoadImageTask(imageView , reqWidth , reqHeight ); AsyncDrawable asyncDrawable = null; try{ asyncDrawable = new AsyncDrawable(getResources(), BitmapFactory.decodeResource(getResources() , defaultResId), task); }catch (Exception ex){ asyncDrawable = new AsyncDrawable(getResources(), BitmapFactory.decodeResource(getResources() , R.mipmap.ic_launcher) , task); } imageView.setImageDrawable(asyncDrawable); task.execute(resId); } }
这样,每次赋给ImageView的不仅仅是一张图片,而是一张默认图片、一张目标图片和一个加载目标图片的异步任务。当我们要在ImageView上开一个新任务时,可以在新开任务前判断,我要开始的任务的目标是不是和正在进行的任务目标一致,如果一致就不要新开任务了。
那么如何进行这样的判断呢?
首先,我们需要一个方法,能取出ImageView中的LoadImageTask,原理是取出ImageView中的Drawable,然后判断它是不是AsyncDrawable,进而取出它的LoadImageTask。
/** * 获得视图当前任务 * @param imageView * @return */ private LoadImageTask getLoadImageTask(ImageView imageView){ if(imageView != null ){ final Drawable drawable = imageView.getDrawable(); if(drawable instanceof AsyncDrawable){ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; return asyncDrawable.getLoadImageTask(); } } return null; }
然后,我们需要给异步任务类加入能够取出它当前正在进行的任务的目标的方法
/** * 异步加载图像的任务类 */ private class LoadImageTask extends AsyncTask<Integer , Void , Bitmap>{ private final WeakReference imageViewReference; private final int mReqWidth , mReqHeight; private int data = 0; public LoadImageTask(ImageView imageView , int reqWidth , int reqHeight){ imageViewReference = new WeakReference<ImageView>(imageView); mReqWidth = reqWidth; mReqHeight = reqHeight; } @Override protected Bitmap doInBackground(Integer... params) { data = params[0]; return decodeSampledBitmapFromResource(getResources() , params[0],mReqWidth , mReqHeight); } @Override protected void onPostExecute(Bitmap bitmap) { //因为目标切换等原因中止了任务 if (isCancelled()) { bitmap = null; } if(bitmap!=null && imageViewReference !=null){ final ImageView imageView = (ImageView) imageViewReference.get(); if(imageView != null){ final LoadImageTask loadImageTask = getLoadImageTask(imageView); if (this == loadImageTask){ imageView.setImageBitmap(bitmap); } } } } public int getData() { return data; } }
值得一提的是,所谓的中止异步任务,并不是说能够在异步任务外的UI线程中,中止它。我们所能做的,只能是将异步任务中的cancel标志位设为true,真正中止它的,只能是在异步任务内,通过检测isCancelled来决定它是否要使自己无效。
最后,是判断目标任务与正在进行中的任务是否一致的方法:
/** * 通过判断任务中的数据,任务是否已经开始,或者任务目标与当前需求目标不同。 * @param res * @param imageView * bitmapData为正在进行中的任务的目标resId * @return false:目标任务与正在进行的任务相同 */ private boolean cancelPotentialWork(int res , ImageView imageView){ final LoadImageTask loadImageTask = getLoadImageTask(imageView); if (loadImageTask!=null){ final int bitmapData = loadImageTask.getData(); // If bitmapData is not yet set or it differs from the new data if (bitmapData == 0 || bitmapData != res ){ loadImageTask.cancel(true); } else{ return false; } } return true; }
该方法只有在没有进行中的任务,或者目标任务与正在进行中的任务不同时,才会返回true。然后结合loadBitmap方法,只有在返回true时,才会加载图片,开启新任务。
虽然过程比较繁琐,但是用起来,就简单了!
public void createImage(View view){ loadBitmap(R.drawable.images2 , imageView1 , R.drawable.images , 30 , 30); }
Done
最后附上一套稍微完整一点的代码
import android.app.Activity; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.support.v7.app.ActionBarActivity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ImageView; import java.lang.ref.WeakReference; public class MainActivity extends Activity { private ImageView imageView1,imageView2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); imageView1 = (ImageView) findViewById(R.id.image1); } /** * 计算图象的缩放比例 * @param options 图像的参数设置接口 * @param reqWidth 要求的宽 * @param reqHeight 要求的高 * @return 缩放比例,系数越大缩小的越多 */ private int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; } /** * 压缩图片 * @param res 系统资源 * @param resId 压缩前的资源ID * @param reqWidth 要求的宽 * @param reqHeight 要求的高 * @return 压缩后的Bitmap图 */ private Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } /** * 异步加载图像的任务类 */ private class LoadImageTask extends AsyncTask<Integer , Void , Bitmap>{ private final WeakReference imageViewReference; private final int mReqWidth , mReqHeight; private int data = 0; public LoadImageTask(ImageView imageView , int reqWidth , int reqHeight){ imageViewReference = new WeakReference<ImageView>(imageView); mReqWidth = reqWidth; mReqHeight = reqHeight; } @Override protected Bitmap doInBackground(Integer... params) { data = params[0]; return decodeSampledBitmapFromResource(getResources() , params[0],mReqWidth , mReqHeight); } @Override protected void onPostExecute(Bitmap bitmap) { //因为目标切换等原因中止了任务 if (isCancelled()) { bitmap = null; } if(bitmap!=null && imageViewReference !=null){ final ImageView imageView = (ImageView) imageViewReference.get(); if(imageView != null){ final LoadImageTask loadImageTask = getLoadImageTask(imageView); if (this == loadImageTask){ imageView.setImageBitmap(bitmap); } } } } public int getData() { return data; } } /** * 将Bitmap的加载task封装进BitmapDrawable中,设置默认图,并在加载完新图后换掉 */ private class AsyncDrawable extends BitmapDrawable{ private final WeakReference<LoadImageTask> bitmapWorkTask ; public AsyncDrawable(Resources resources, Bitmap bitmap, LoadImageTask bitmapWorkTask){ super(resources,bitmap); this.bitmapWorkTask = new WeakReference<LoadImageTask>(bitmapWorkTask); } public LoadImageTask getLoadImageTask(){ return bitmapWorkTask.get(); } } /** * 加载图片 * @param resId * @param imageView * @param defaultResId * @param reqWidth * @param reqHeight */ public void loadBitmap(int resId, ImageView imageView , int defaultResId , int reqWidth , int reqHeight) { //判断任务是否正在进行 if (cancelPotentialWork(resId, imageView)) { final LoadImageTask task = new LoadImageTask(imageView , reqWidth , reqHeight ); AsyncDrawable asyncDrawable = null; try{ asyncDrawable = new AsyncDrawable(getResources(), BitmapFactory.decodeResource(getResources() , defaultResId), task); }catch (Exception ex){ asyncDrawable = new AsyncDrawable(getResources(), BitmapFactory.decodeResource(getResources() , R.mipmap.ic_launcher) , task); } imageView.setImageDrawable(asyncDrawable); task.execute(resId); } } /** * 通过判断任务中的数据,任务是否已经开始,或者任务目标与当前需求目标不同。 * @param res * @param imageView * bitmapData为正在进行中的任务的目标resId * @return false:目标任务与正在进行的任务相同 */ private boolean cancelPotentialWork(int res , ImageView imageView){ final LoadImageTask loadImageTask = getLoadImageTask(imageView); if (loadImageTask!=null){ final int bitmapData = loadImageTask.getData(); // If bitmapData is not yet set or it differs from the new data if (bitmapData == 0 || bitmapData != res ){ loadImageTask.cancel(true); } else{ return false; } } return true; } /** * 获得视图当前任务 * @param imageView * @return */ private LoadImageTask getLoadImageTask(ImageView imageView){ if(imageView != null ){ final Drawable drawable = imageView.getDrawable(); if(drawable instanceof AsyncDrawable){ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; return asyncDrawable.getLoadImageTask(); } } return null; } public void createImage(View view){ loadBitmap(R.drawable.images2 , imageView1 , R.drawable.images , 30 , 30); } }