Bitmap

一)高效加载大图

1)读取Bitmap尺寸和类型


BitmapFactory类提供了几个生成bitmap的解码方法:

decodeByteArray()

decodeFile()

decodeResource()

decodeStream()

这些方法会为Bitmap分配内存,因此很容易产生OutOfMemory异常。

可以通过BitmapFactory.Options类来设置解码选项,设置inJusteDecodeBounds属性为true, 则上述方法先不分配内存,而是设置Options的outWidth, outHeight 和 outMimeType, 此时返回值为null。可以通过这个方法在给bitmap分配内存前获取图片的尺寸和类型。例子如下:

BitmapFactory.Options options =newBitmapFactory.Options();

options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

2)加载按比例缩减后的版本到内存


设置BitmapFactory.Options对象的inSampleSize为true,通知解码函数部分取样图片,加载一个较小的版本。一个分辨率为2048*1536的图片使用inSampleSize为4解码后生成分辨率大约为512*384的图片,加载该图片到内存只需要0.75MB而不是原来的12MB(假设图片使用ARGB_8888样式)。例子如下:

publicstaticint calculateInSampleSize(

            BitmapFactory.Options options,int reqWidth,int reqHeight){
    // Raw heightand 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 keepsboth
        // height and width larger than the requested height and width.
        while ((halfHeight/ inSampleSize)> reqHeight
                && (halfWidth / inSampleSize)> reqWidth){
            inSampleSize *= 2;
        }
    }

    return inSampleSize;
}

注意:解码函数使用与2的幂次方的最近的值,所以使用2的幂次方来计算inSampleSize.

使用过程中,先设置inJusteDecodeBounds属性为true调用解码方法,然后对得到的option设置新的inSampleSize值和设置inJusteDecodeBounds属性为false再次调用解码方法,如下所示:

public staticBitmap decodeSampledBitmapFromResource(Resources res,int resId,
        int reqWidth,int reqHeight){

    // First decodewith inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options=newBitmapFactory.Options();
    options.inJustDecodeBounds=true;
    BitmapFactory.decodeResource(res, resId, options);

    // CalculateinSampleSize
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // Decode bitmapwith inSampleSize set
    options.inJustDecodeBounds=false;
    return BitmapFactory.decodeResource(res, resId, options);
}

使用上述方法可以很容易的将任意大小的大图加载到显示100*100的缩略图的ImageView中,如下所示:

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage,100,100));

二)在非UI线程中处理Bitmap

所有的BitmapFactory.decode* 方法在从外存或网络(或其他内存以外的其他地方)加载图片,都不应该在主UI线程中执行。

1) 使用AsyncTask

AsyncTask类可以在后台线程中执行处理工作并把结果显示到UI线程中。如下是使用AsyncTask加载图片的示例:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    private final WeakReference<ImageView> imageViewReference;
    private int data = 0;

    public BitmapWorkerTask(ImageView imageView) {
        // Use a WeakReference to ensure the ImageView can be garbage collected
        imageViewReference = new WeakReference<ImageView>(imageView);
    }

    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        data = params[0];
        return decodeSampledBitmapFromResource(getResources(), data, 100, 100));
    }

    // Once complete, see if ImageView is still around and set bitmap.
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            if (imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

通过创建上述任务并执行它,可以异步加载图片:

public void loadBitmap(int resId, ImageView imageView) {
    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}
2)处理并发性

ListView和GridView在跟上述AsyncTask一起使用时会存在如下问题:

为了有效的使用内存,上述组件会在用户滑动时回收子View。如果每个子View都触发一个AsyncTask的话,无法保证任务完成后关联的view没有被回收。

无法保证异步任务完成的顺序与开始的顺序一致。

一种解决方法是在ImageView中保存一个指向最近AsyncTask的指针,当任务完成后可以用来做比对。

创建一个自定义的Drawable子类保存指向AsyncTask的指针。这里使用BitmapDrawable在任务完成前,在ImageView中显示占位图。

static class AsyncDrawable extends BitmapDrawable {
    private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;

    public AsyncDrawable(Resources res, Bitmap bitmap,
            BitmapWorkerTask bitmapWorkerTask) {
        super(res, bitmap);
        bitmapWorkerTaskReference =
            new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
    }

    public BitmapWorkerTask getBitmapWorkerTask() {
        return bitmapWorkerTaskReference.get();
    }
}

在执行BitmapWorkerTask之前,创建AsyncDrawable并与目标ImageView绑定。 

public void loadBitmap(int resId, ImageView imageView) {
    if (cancelPotentialWork(resId, imageView)) {
        final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
        final AsyncDrawable asyncDrawable =
                new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
        imageView.setImageDrawable(asyncDrawable);
        task.execute(resId);
    }
}

cancelPotentialWork 方法检查是否已经有正在运行的任务与当前ImageView关联,如果有的话,先取消以前的任务。它的实现如下:

public static boolean cancelPotentialWork(int data, ImageView imageView) {
    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);

    if (bitmapWorkerTask != null) {
        final int bitmapData = bitmapWorkerTask.data;
        // If bitmapData is not yet set or it differs from the new data
        if (bitmapData == 0 || bitmapData != data) {
            // Cancel previous task
            bitmapWorkerTask.cancel(true);
        } else {
            // The same work is already in progress
            return false;
        }
    }
    // No task associated with the ImageView, or an existing task was cancelled
    return true;
}

 getBitmapWorkerTask()方法用来获取与ImageView 关联的任务:

private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
   if (imageView != null) {
       final Drawable drawable = imageView.getDrawable();
       if (drawable instanceof AsyncDrawable) {
           final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
           return asyncDrawable.getBitmapWorkerTask();
       }
    }
    return null;
}

最后一步是更新BitmapWorkerTask 的onPostExecute() 方法,它负责检查任务是否已经被取消已经当前任务是否与关联的ImageView匹配:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...

    @Override
    protected void onPostExecute(Bitmap bitmap) {
        if (isCancelled()) {
            bitmap = null;
        }

        if (imageViewReference != null && bitmap != null) {
            final ImageView imageView = imageViewReference.get();
            final BitmapWorkerTask bitmapWorkerTask =
                    getBitmapWorkerTask(imageView);
            if (this == bitmapWorkerTask && imageView != null) {
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

上述实现适合于ListView和GridView这样的会回收子View的组件。只需在ImageView中调用loadBitmap即可。例如,在GridView的实现中,这将在它的adapter的getView()方法中。

三)缓存Bitmap

1)使用内存做缓存
LruCache很适合对Bitmap做缓存,它使用一个对LinkedHashMap的强引用来保存最近使用过的对象,当缓存体积大于目标时踢出最近最少使用的对象。 

需要为LruCache选择一个合适的大小,有如下因素需要考虑:

      1)需要同时显示多少图片,以及需要多少图片备用着以便马上显示?

       2)设备的屏幕大小和密度

      3)Bitmap的尺寸和配置以便计算出每张图片的大小

      4)图片的存储频率等

如下是使用LruCache缓存Bitmap的例子:

privateLruCache<String,Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Get max available VM memory, exceeding this amount will throw an
    // OutOfMemory exception. Stored in kilobytes as LruCache takes an
    // int in its constructor.
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = maxMemory / 8;

    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in kilobytes rather than
            // number of items.
            return bitmap.getByteCount() / 1024;
        }
    };
    ...
}

public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

public Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}

注意:上例中,1/8的应用程序内存用来做缓存,使用正常设备的话,大约是4MB(32/8). 在800*480分辨率的设备中,满屏的GridView大约需要1.5MB(800*480*4bytes),这样内存中可以缓存大约2.5页图片。

当加载bitmap到ImageView时, 先检查LruCache,如果找到的话,立即使用其来更新ImageView, 否则启动后台线程来处理图片:

public void loadBitmap(int resId, ImageView imageView) {
    final String imageKey = String.valueOf(resId);

    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    if (bitmap != null) {
        mImageView.setImageBitmap(bitmap);
    } else {
        mImageView.setImageResource(R.drawable.image_placeholder);
        BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
        task.execute(resId);
    }
}

The BitmapWorkerTask also needs to be updated to addentries to the memory cache:

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final Bitmap bitmap = decodeSampledBitmapFromResource(
                getResources(), params[0], 100, 100));
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);
        return bitmap;
    }
    ...
}

2)使用外存做缓存
在程序使用过程中有可能会被电话等其他任务中断,这个时候在后台有可能会被杀死,这时内存缓存就被销毁了,电话完成后恢复到该应用程序后,需要重新做一遍图片处理。
为应对这种情况,需要使用外存作为持久的缓存,此时要注意使用后台线程来处理,因为外存的读取时间是不可预知的。

需要注意的是:对应频繁存取的图片, ContentProvider可能是更好的选择,图库应用程序就是使用的ContentProvider。

下例在上述的内存缓存的基础上添加外存缓存,该外存缓存是从Android source 中截取DiskLruCache类来实现的:

private DiskLruCache mDiskLruCache;
private final Object mDiskCacheLock = new Object();
private boolean mDiskCacheStarting = true;
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
private static final String DISK_CACHE_SUBDIR = "thumbnails";

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    // Initialize memory cache
    ...
    // Initialize disk cache on background thread
    File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
    new InitDiskCacheTask().execute(cacheDir);
    ...
}

class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
    @Override
    protected Void doInBackground(File... params) {
        synchronized (mDiskCacheLock) {
            File cacheDir = params[0];
            mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
            mDiskCacheStarting = false; // Finished initialization
            mDiskCacheLock.notifyAll(); // Wake any waiting threads
        }
        return null;
    }
}

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...
    // Decode image in background.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final String imageKey = String.valueOf(params[0]);

        // Check disk cache in background thread
        Bitmap bitmap = getBitmapFromDiskCache(imageKey);

        if (bitmap == null) { // Not found in disk cache
            // Process as normal
            final Bitmap bitmap = decodeSampledBitmapFromResource(
                    getResources(), params[0], 100, 100));
        }

        // Add final bitmap to caches
        addBitmapToCache(imageKey, bitmap);

        return bitmap;
    }
    ...
}

public void addBitmapToCache(String key, Bitmap bitmap) {
    // Add to memory cache as before
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }

    // Also add to disk cache
    synchronized (mDiskCacheLock) {
        if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
            mDiskLruCache.put(key, bitmap);
        }
    }
}

public Bitmap getBitmapFromDiskCache(String key) {
    synchronized (mDiskCacheLock) {
        // Wait while disk cache is started from background thread
        while (mDiskCacheStarting) {
            try {
                mDiskCacheLock.wait();
            } catch (InterruptedException e) {}
        }
        if (mDiskLruCache != null) {
            return mDiskLruCache.get(key);
        }
    }
    return null;
}

// Creates a unique subdirectory of the designated app cache directory. Tries to use external
// but if not mounted, falls back on internal storage.
public static File getDiskCacheDir(Context context, String uniqueName) {
    // Check if media is mounted or storage is built-in, if so, try and use external cache dir
    // otherwise use internal cache dir
    final String cachePath =
            Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
                    !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
                            context.getCacheDir().getPath();

    return new File(cachePath + File.separator + uniqueName);
}

注意:初始化外存缓存也需要外存操作,因此不能在主线程中进行。因此,会存在缓存在初始化前被存取的情况。,添加锁对象以保证存取前先初始化。

尽管内存缓存是在UI线程中检查的,外存缓存需要在后台线程中做检查。当图片处理完毕后,需要同时添加到内存和外存缓存中以备后续使用。

3)处理配置发生改变的情况

配置发生改变,如横竖屏切换会引起activity的销毁和重新启动,这时候需要重新处理图片的加载。为避免这种情况的发生,通过设置setRetainInstance(true)使用Fragment保存前面讲到的内存缓存并提交给新的actiivty实例。在Activity被重新创建后,保留的Fragment重新添加到activity中,重新获取到已经保存的缓存对象并显示出来。

例子如下:

private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    RetainFragment retainFragment =
            RetainFragment.findOrCreateRetainFragment(getFragmentManager());
    mMemoryCache = retainFragment.mRetainedCache;
    if (mMemoryCache == null) {
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            ... // Initialize cache here as usual
        }
        retainFragment.mRetainedCache = mMemoryCache;
    }
    ...
}

class RetainFragment extends Fragment {
    private static final String TAG = "RetainFragment";
    public LruCache<String, Bitmap> mRetainedCache;

    public RetainFragment() {}

    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
        RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
        if (fragment == null) {
            fragment = new RetainFragment();
            fm.beginTransaction().add(fragment, TAG).commit();
        }
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }
}

四)管理Bitmap的内存

Android系统对Bitmap内存的管理演变如下:

在Android 2.2(API 8)及以下的系统,当垃圾回收发生时,App的线程就会停止,这会产生一个滞后,降低性能。Android2.3加入实时并行垃圾回收机制,即一旦bitmap不再被引用,就回收该内存。

在Android2.3.3(API10)及以下的系统,bitmap的像素数据存储在native内存中。它与bitmap本身是分离的,后者存储在Dalvik堆中。在native内存中的像素数据的释放是不可预知的,很容易造成应用程序超出内存限制而崩溃。到Android3.0(API11)中,像素数据与bitmap数据一起都保存在Dalvik堆中。

这里只介绍Android3.0以上的情况。

在Android3.0引入BitmapFactory.Options.inBitmap选项,该选项被设置后,解码方法会在加载数据时试图复用已经存在的bitmap. 这意味着bitmap的内存被复用,产生更好的性能,减少内存的分配. 但是复用是有限制条件的,在Android4.4(API19)之前,只支持尺寸相同的bitmap.

1)保存bitmap以便后续复用

当一个bitmap被从LruCache收回时,在HashSet中保存指向该bitmap的软指针,以便后续使用inBitmap来复用它.代码如下:

Set<SoftReference<Bitmap>> mReusableBitmaps;

private LruCache<String,BitmapDrawable> mMemoryCache;

// If you'rerunning on Honeycomb or newer, create a
// synchronizedHashSet of references to reusable bitmaps.
if (Utils.hasHoneycomb()){
    mReusableBitmaps =
            Collections.synchronizedSet(newHashSet<SoftReference<Bitmap>>());
}

mMemoryCache = newLruCache<String,BitmapDrawable>(mCacheParams.memCacheSize){

    // Notify theremoved entry that is no longer being cached.
    @Override
    protected void entryRemoved(boolean evicted,String key,
            BitmapDrawable oldValue,BitmapDrawable newValue){
        if (RecyclingBitmapDrawable.class.isInstance(oldValue)){
            // The removed entry is a recyclingdrawable, so notify it
            // that it has been removed from the memorycache.
            ((RecyclingBitmapDrawable) oldValue).setIsCached(false);
        } else{
            // The removed entry is a standardBitmapDrawable.
            if (Utils.hasHoneycomb()){
                // We're running on Honeycomb or later,so add the bitmap
                // to a SoftReference set for possibleuse with inBitmap later.
                mReusableBitmaps.add
                       (newSoftReference<Bitmap>(oldValue.getBitmap()));
            }
        }
    }
....
}

2)使用已经存在的bitmap

在运行app时,解码方法查看是否有已经存在的bitmap可被复用。如:

publicstaticBitmap decodeSampledBitmapFromFile(String filename,

        int reqWidth,int reqHeight,ImageCache cache){

    final BitmapFactory.Options options=newBitmapFactory.Options();
    ...
    BitmapFactory.decodeFile(filename, options);
    ...

    // If we'rerunning on Honeycomb or newer, try to use inBitmap.
    if (Utils.hasHoneycomb()){
        addInBitmapOptions(options, cache);
    }
    ...
    return BitmapFactory.decodeFile(filename, options);
}

其中addInBitmapOptions()方法用来寻找已经存在的bitmap以便设置它为inBitmap:

private static void addInBitmapOptions(BitmapFactory.Options options,
        ImageCache cache){
    // inBitmap onlyworks with mutable bitmaps, so force the decoder to
    // returnmutable bitmaps.
    options.inMutable = true;

    if (cache!=null){
        // Try to find a bitmap to use for inBitmap.
        Bitmap inBitmap = cache.getBitmapFromReusableSet(options);

        if (inBitmap!=null){
            // If a suitable bitmap has been found, setit as the value of
            // inBitmap.
            options.inBitmap = inBitmap;
        }
    }
}

// This methoditerates through the reusable bitmaps, looking for one
// to use forinBitmap:
protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
        Bitmap bitmap = null;

    if (mReusableBitmaps!=null&&!mReusableBitmaps.isEmpty()){
        synchronized (mReusableBitmaps) {
            final Iterator<SoftReference<Bitmap>> iterator
                    = mReusableBitmaps.iterator();
            Bitmap item;

            while (iterator.hasNext()){
                item = iterator.next().get();

                if (null != item&& item.isMutable()){
                    // Check to see it the item can be usedfor inBitmap.
                    if (canUseForInBitmap(item, options)){
                       bitmap = item;

                       // Remove fromreusable set so it can't be used again.
                       iterator.remove();
                       break;
                    }
                } else {
                    // Remove from the set if the referencehas been cleared.
                    iterator.remove();
                }
            }
        }
    }
    return bitmap;
}

canUseForInBitmap方法用来判断是否满足复用条件:

static boolean canUseForInBitmap(
        Bitmap candidate,BitmapFactory.Options targetOptions){

    if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.KITKAT){
        // From Android 4.4 (KitKat) onward we can re-use if the byte size of
        // the new bitmap is smaller than the reusable bitmap candidate
        // allocation byte count.
        int width = targetOptions.outWidth / targetOptions.inSampleSize;
        int height = targetOptions.outHeight / targetOptions.inSampleSize;
        int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
        return byteCount <= candidate.getAllocationByteCount();
    }

    // On earlierversions, the dimensions must match exactly and the inSampleSize must be 1
    return candidate.getWidth()== targetOptions.outWidth
            && candidate.getHeight()== targetOptions.outHeight
            && targetOptions.inSampleSize==1;
}

/**
 * A helper function to return the byte usage per pixel of a bitmap basedon its configuration.
 */

static int getBytesPerPixel(Config config){
    if (config==Config.ARGB_8888){
        return 4;
    } else if (config == Config.RGB_565){
        return 2;
    } else if (config == Config.ARGB_4444){
        return 2;
    } else if (config == Config.ALPHA_8){
        return 1;
    }
    return 1;
}


四)在UI中展示Bitmap

1)在GridView中加载Bitmap的实现

首先给出GridView的一个标准实现,在Fragment添加GridView,它的子View使用ImageView。

publicclassImageGridFragmentextendsFragmentimplementsAdapterView.OnItemClickListener{

    private ImageAdapter mAdapter;

    // A staticdataset to back the GridView adapter
    public final staticInteger[] imageResIds=newInteger[]{
            R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3,
            R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6,
            R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9};

    // Emptyconstructor as per Fragment docs
    public ImageGridFragment(){}

    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        mAdapter = new ImageAdapter(getActivity());
    }

    @Override
    public View onCreateView(
            LayoutInflater inflater,ViewGroup container,Bundle savedInstanceState){
        final View v= inflater.inflate(R.layout.image_grid_fragment, container, false);
        final GridView mGridView=(GridView) v.findViewById(R.id.gridView);
        mGridView.setAdapter(mAdapter);
        mGridView.setOnItemClickListener(this);
        return v;
    }

    @Override
    public void onItemClick(AdapterView<?> parent,View v, int position,long id){
        final Intent i=newIntent(getActivity(),ImageDetailActivity.class);
        i.putExtra(ImageDetailActivity.EXTRA_IMAGE, position);
        startActivity(i);
    }

    private classImageAdapterextendsBaseAdapter{
        private finalContext mContext;

        public ImageAdapter(Context context){
            super();
            mContext = context;
        }

        @Override
        public int getCount(){
            return imageResIds.length;
        }

        @Override
        public Object getItem(int position){
            return imageResIds[position];
        }

        @Override
        public long getItemId(int position){
            return position;
        }

        @Override
        public View getView(int position,View convertView,ViewGroup container){
            ImageView imageView;
            if (convertView==null){// if it's notrecycled, initialize some attributes
                imageView = new ImageView(mContext);
                imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
                imageView.setLayoutParams(newGridView.LayoutParams(
                       LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT));
            } else{
                imageView = (ImageView) convertView;
            }
            imageView.setImageResource(imageResIds[position]);// Load image into ImageView
            return imageView;
        }
    }
}

该实现的问题是图片在UI线程中进行设置。对于小图由于系统加载和缓冲尚可承受,当图片较大,需要额外的处理时,UI就会出现停顿。

可以采用在非UI线程里处理Bitmap一节中的技巧,如下是更新后的解决方案:

public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener{
    ...

    private classImageAdapterextendsBaseAdapter{
        ...

        @Override
        public View getView(int position,View convertView,ViewGroup container){
            ...

    loadBitmap(imageResIds[position],imageView)
            return imageView;
    }

    public void loadBitmap(int resId,ImageView imageView){
        if (cancelPotentialWork(resId, imageView)){
            final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
            final AsyncDrawable asyncDrawable =
                    new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
            imageView.setImageDrawable(asyncDrawable);
            task.execute(resId);
        }
    }

    static classAsyncDrawableextendsBitmapDrawable{
        private finalWeakReference<BitmapWorkerTask>bitmapWorkerTaskReference;

        public AsyncDrawable(Resources res,Bitmap bitmap,
                BitmapWorkerTask bitmapWorkerTask){
            super(res, bitmap);
            bitmapWorkerTaskReference =
                new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
        }

        public BitmapWorkerTask getBitmapWorkerTask(){
            return bitmapWorkerTaskReference.get();
        }
    }

    public staticboolean cancelPotentialWork(int data,ImageView imageView){
        final BitmapWorkerTask bitmapWorkerTask= getBitmapWorkerTask(imageView);

        if (bitmapWorkerTask!=null){
            final int bitmapData = bitmapWorkerTask.data;
            if (bitmapData!= data){
                // Cancel previous task
                bitmapWorkerTask.cancel(true);
            } else{
                // The same work is already in progress
                return false;
            }
        }
        // No task associated with the ImageView, or an existing task wascancelled
        return true;
    }

    private staticBitmapWorkerTask getBitmapWorkerTask(ImageView imageView){
       if (imageView!=null){
           final Drawable drawable= imageView.getDrawable();
           if (drawableinstanceofAsyncDrawable){
               final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
               return asyncDrawable.getBitmapWorkerTask();
           }
        }
        return null;
    }

    ... // include updatedBitmapWorkerTask class




你可能感兴趣的:(Bitmap)