Bitmap资料整合记录

一 概述

我们都知到在我们使用Bitmap的时候,它会快速地消费我们的内存,我相信大家会经常遇到下面这个异常:

java.lang.OutofMemoryError: bitmap size exceeds VM budget.

   所以在Android中加载Bitmap是很严格的理由如下:

   1  Android设备只有16m的内存分配给每一个应用。(当然也有例外)

   2  Bitmap本身是内存杀手:

For example, the camera on the Galaxy Nexus takes photos up to 2592x1936 pixels (5 megapixels).If the bitmap configuration used is ARGB_8888 (the default from the Android 2.3 onward) then loading this image into memory takes about 19MB of memory (2592*1936*4 bytes), immediately exhausting the per-app limit on some devices.

   3  android UI 要求一次性频繁的加载很多Bitmap 比如ListView GirdView,ViewPager,等等控件,屏幕中有很多显性或者隐性的Bitmap要通过用户操作显示。

二 如何有效加载Large Bitmaps

BitmapFactory 里面有很多方法可以使我们的程序避免出现OOM 其中BitmapFactory.option起到了关键的作用。(

BitmapFactory.decodeFile(path, op)

BitmapFactory.decodeStream(getContentResolver().openInputStream(imageFileUri), null,  bmpFactoryOptions)

BitmapFactory.decodeByteArray()

比如图片太大的话我们可以去压缩,以下是压缩的两个步骤:

获取图片的宽高,这里要设置Options.inJustDecodeBounds=true,这时候decode的bitmap为null,只是把图片的宽高放在Options里。

2  设置合适的压缩比例inSampleSize,这时候获得合适的Bitmap。

流程图:


但是这个压缩比例设置多少就是个问题,我们可以用如下方法去获得inSampleSize的值:

public static 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;
}
Options 中有个属性 inJustDecodeBounds可以很好的避免OOM,当我们设置为true的时候,他返回实际的Bitmap

不给其分配内存空间这样就避免内存溢出了。但是允许我们查询图片的信息这其中就包括图片大小信息(

options.outHeight (图片原始高度)和option.outWidth(图片原始宽度))。

真正实现缩放是由Options的 属性 inSampleSize决定的:

实现步骤:

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

    // 第一次设置inJustDecodeBounds=true to check dimensions
    final BitmapFactory.Options options = new BitmapFactory.Options();
    options.inJustDecodeBounds = true;
    BitmapFactory.decodeResource(res, resId, options);

    // 计算inSampleSize的缩放值
    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

    // 设置缩放分割图片的时候 inJustDecodeBounds=false 然后再重新读出Bitmap
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res, resId, options);
}
应用:

上面的方法可以很容易的加载一个容易大小的Bitmap图片到ImageView控件中以100*100的像素展示

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

在UI主线程外处理Bitmap

 BitmapFactory.decode*方法如果是从disk或者网络上面读取文件的话我们不应该在UI主线程里面操作,否则有可能阻塞主线程造成ANR

   解决办法:

1 use an AsyncTask(异步)

  异步类AsyncTask提供了简单的方法在子线程(非主线程)去执行一些任务,并且会返回结果给主线程,使用的时候我们应该创建一个子类去继承它并且复写它提供的方法,下面是用异步类和 decodeSampledBitmapFromResource方法(在一里面的方法) 在ImageView控里面加载大图片的例子:
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);
            }
        }
    }
}

首先我们需要知道AsynTask 主要有三个参数四个方法 以及他们的主要作用请自行百度

在代码中我们还发现了这一行:
imageViewReference = new WeakReference<ImageView>(imageView);
   从单词可以知道它是弱引用,它是什么鬼?我先百度下。。。
有 Weak Reference 当然也有 Strong Reference。其实我们常用的引用就是 strong reference
只要有 Strong Reference 指向对象,这个对象就不会被垃圾收集器回收。哦,原来使用Weak Reference就是为了便于给垃圾收集器回收。
什么时候可能用到 Weak Reference?

你想给对象附加一些信息,于是你用一个 Hashtable 把对象和附加信息关联起来。你不停的把对象和附加信息放入 Hashtable 中,但是当对象用完的时候,你不得不把对象再从 Hashtable 中移除,否则它占用的内存变不会释放。万一你忘记了,那么没有从 Hashtable 中移除的对象也可以算作是内存泄漏。理想的状况应该是当对象用完时,Hashtable 中的对象会自动被垃圾收集器回收,不然你就是在做垃圾回收的工作了。 你想实现一个图片缓存,因为加载图片的开销比较大。你将图片对象的引用放入这个缓存,以便以后能够重新使用这个对象。但是你必须决定缓存中的哪些图片不再需要了,从而将引用从缓存中移除。不管你使用什么管理缓存的算法,你实际上都在处理垃圾收集的工作,更简单的办法(除非你有特殊的需求,这也应该是最好的办法)是让垃圾收集器来处理,由它来决定回收哪个对象。 
Weak Reference 这时候就能派上用场了。把对象的 weak reference 放入 Hashtable 或者缓存中,当没有 strong reference 指向他们的时候,对象就可以被垃圾收集器回收了。实际上,有一个 WeakHashMap 就是专门做这个事的。 

那么怎样创建对象的 weak reference 呢?很简单,Java 标准库中有个类 WeakReference, 
WeakReference weakref = new WeakReference(ref); 
这样 weakref 就是 ref 指向对象的一个 weak reference。要引用这个 weak reference 指向的对象可以用 get 方法。 
Object ref = weakref.get(); 
注意,get 可能返回 null,这时原先指向的对象就不可用了,它可能已经被回收了。oncurrency(并发)


小结:
ImageView控件不会永远存在,所以我们要考虑垃圾回收。

开始一个加载图片的异步任务,我们主需要new task and execute it.
public void loadBitmap(int resId, ImageView imageView) {
    BitmapWorkerTask task = new BitmapWorkerTask(imageView);
    task.execute(resId);
}

2 Handle Concurrency (并发)

   前面有个问题在Listview 和 GridView使用AsynTask的时候,为了能够有效地使用内存,这些组件可以回收子视图作为用户滚动,此外,也不能保证异步任务开始的顺序是它们完成的顺序。
  优化步骤:
   创建一个专用的drawable子类引用存储的任务。
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之前 you create an  AsyncDrawable  and bind it to the target  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);
    }
}
cancelPotentialmap()检查是否另一个运行任务已经与关联ImageView。如果是这样的话,它试图通过调用cancel()取消前一个任务。
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;
}
 updating  onPostExecute()  in  BitmapWorkerTask  所以,它检查是否任务取消,是否当前的任务相匹配的一个与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组件以及任何其他组件,回收其子视图。只需调用LoadBitmap设置图片到您的控件中。

缓存Bitmap

  
   加载一张简单的图片到UI是很直接的,当加载复杂的图片集合,例如在ListView和GridView 中显示图片,用户滑动本质上是无限制的。这样失去焦点的View就有可能会被回收,垃圾收集器也会释放你加载位图,如果你不保持任何持久的引用。这是很好的,但为了保持一个流体和快速加载的用户界面,你想避免不断处理这些图像,每次他们回来屏幕上。这时内存和磁盘缓存起到了关键作用,可以使组件迅速重新加载处理的图像。

内存缓存:

   内存缓存提供快速访问位图在占用宝贵的应用程序内存的成本。LruCache类(也可在支持库使用回API级别4)特别适合缓存位图的任务,保持最近引用的对象在一个强引用LinkedHashMap和移除最近最少使用的成员缓存超过指定大小之前。
注:在过去,一个流行的内存缓存实现了软引用或WeakReference位图缓存,但是这是不推荐的。从安卓2.3开始(API9)的垃圾收集是更积极的收集软/弱引用,这使得他们相当无效。此外,在之前的安卓3(API 11),位图的备份数据存储在本地内存中,这是不以可预测的方式发布,可能导致应用程序简要超过其内存限制和崩溃。
  
   为了选择一个大小合适的一个LruCache,很多因素要考虑,例如:
如何内存密集型是你的活动和/或应用程序的其余部分?
一次 有多少图像显示屏幕上?有多少图片需要准备好要在屏幕上出现?
什么是屏幕的大小和密度的设备?一个额外的高密度屏幕(xhdpi)装置像Galaxy Nexus将需要一个更大的缓存放在内存中,像Nexus S设备(hdpi)。
什么尺寸和配置的位图,因此将各占多少内存?
如何频繁地访问图像?有什么会更频繁地访问吗?如果是这样的话,即使有多个LruCache对象不同群体的位图,你也想保存同一个item在内存中。
你能平衡质量和数量吗?有时它可以存储大量低质量的图片,可能加载一个高质量的bitmap在子线程中。
  缓存太小导致没有效益的额外开销,一个过大的缓存可再次引起java.lang.outofmemory

这里是设置一个LruCache为位图实例:

private LruCache<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);
}

Note: 在这个示例中,为我们的高速缓存分配了八分之一的应用程序内存。在一个正常的/ hdpi设备这是最小的一个4mb左右(32 / 8)。全屏幕GridView充满了800X480分辨率图像将使用设备大约1.5mb(800×480×4字节),那么这会在内存中缓存图像大约2.5页。

当加载一个位图到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);
    }
}

BitmapWorkerTask也需要更新添加条目到内存缓存:

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;
    }
    ...
}

硬盘缓存:
       大数据集组件如GridView可以填满一个内存缓存。您的应用程序可能会被另一个任务中断,如电话,而在后台,它可能会被杀死,内存缓存被破坏了。一旦用户恢复,您的应用程序必须处理每一个图像。

      磁盘缓存可以用于持续处理位图,图像不再可用的内存缓存。当然,从磁盘中读取图像比从内存中加载慢,并且应该在后台线程中完成,因为磁盘读取时间是不可预测的。
    
  添加了磁盘高速缓存,除了现有的内存缓存:
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);
}

注意:即使初始化磁盘高速缓存需要磁盘操作,因此不应该发生在主线程。然而,这并不意味着在初始化之前会有一个缓存访问的机会。为了解决这个问题,在上面的实现中,一个锁对象确保应用程序不会从磁盘缓存中读取,直到缓存已被初始化。
当在用户界面线程中检查内存缓存时,在后台线程中检查磁盘缓存。磁盘操作不应该发生在用户界面线程上。当图像处理

完成后,最终的位图被添加到内存和磁盘高速缓存中,以供将来使用。
如屏幕方向更改,导致安卓系统破坏并重新启动新配置的运行活动。你想避免不得不处理所有你的图像
使用内存缓存区。该缓存可以被传递到新的活动实例使用的一个片段,通过调用setretaininstance保存(真正的))。活动结束后已重现,这保留片段复位你可以访问现有的缓存对象,允许图片被快速的获取和重新填充到ImageView对象。

这是保持一个LruCache对象在配置改变使用的一个片段为例:
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内存

Android3.0以上内存管理:
保存一个位图以供以后使用:

Android 3(API Level 11)介绍了bitmapfactory.options.inbitmap场。如果设置了此选项,将在加载内容时尝试使用

选项对象的解码方法将尝试在加载内容时重用现有的位图。

Set<SoftReference<Bitmap>> mReusableBitmaps;
private LruCache<String, BitmapDrawable> mMemoryCache;

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

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

    // Notify the removed 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 recycling drawable, so notify it
            // that it has been removed from the memory cache.
            ((RecyclingBitmapDrawable) oldValue).setIsCached(false);
        } else {
            // The removed entry is a standard BitmapDrawable.
            if (Utils.hasHoneycomb()) {
                // We're running on Honeycomb or later, so add the bitmap
                // to a SoftReference set for possible use with inBitmap later.
                mReusableBitmaps.add
                        (new SoftReference<Bitmap>(oldValue.getBitmap()));
            }
        }
    }
....
}
使用一个现有的位图
在运行的应用程序中,解码器的方法检查,看看是否有一个现有的位图,他们可以使用。例如:
public static Bitmap decodeSampledBitmapFromFile(String filename,
        int reqWidth, int reqHeight, ImageCache cache) {

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

    // If we're running on Honeycomb or newer, try to use inBitmap.
    if (Utils.hasHoneycomb()) {
        addInBitmapOptions(options, cache);
    }
    ...
    return BitmapFactory.decodeFile(filename, options);
}
addinbitmapoptions()方法
private static void addInBitmapOptions(BitmapFactory.Options options,
        ImageCache cache) {
    // inBitmap only works with mutable bitmaps, so force the decoder to
    // return mutable 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, set it as the value of
            // inBitmap.
            options.inBitmap = inBitmap;
        }
    }
}

// This method iterates through the reusable bitmaps, looking for one
// to use for inBitmap:
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 used for inBitmap.
                    if (canUseForInBitmap(item, options)) {
                        bitmap = item;

                        // Remove from reusable set so it can't be used again.
                        iterator.remove();
                        break;
                    }
                } else {
                    // Remove from the set if the reference has been cleared.
                    iterator.remove();
                }
            }
        }
    }
    return bitmap;
}
最后,该方法确定候选位图满足尺寸标准用于inbitmap:
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 earlier versions, 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 based on 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

在viewpager中加载Bitmap:

你可以使用一个由PagerAdapter ViewPager组件实现这个模式支持。然而,一个更适合支持适配器自动破坏类fragmentstatepageradapter保存ViewPager的碎片状态,当他们消失在屏幕,保持内存使用情况下。
注意:如果你有一个较小的数字图像和确定他们可以在应用程序的内存限制中,然后使用一个常规的PagerAdapter或者fragmentpageradapter可能更合适。
这是子view ImageView ViewPager实现。主要活动持有ViewPager和适配器:
public class ImageDetailActivity extends FragmentActivity {
    public static final String EXTRA_IMAGE = "extra_image";

    private ImagePagerAdapter mAdapter;
    private ViewPager mPager;

    // A static dataset to back the ViewPager adapter
    public final static Integer[] imageResIds = new Integer[] {
            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};

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.image_detail_pager); // Contains just a ViewPager

        mAdapter = new ImagePagerAdapter(getSupportFragmentManager(), imageResIds.length);
        mPager = (ViewPager) findViewById(R.id.pager);
        mPager.setAdapter(mAdapter);
    }

    public static class ImagePagerAdapter extends FragmentStatePagerAdapter {
        private final int mSize;

        public ImagePagerAdapter(FragmentManager fm, int size) {
            super(fm);
            mSize = size;
        }

        @Override
        public int getCount() {
            return mSize;
        }

        @Override
        public Fragment getItem(int position) {
            return ImageDetailFragment.newInstance(position);
        }
    }
}
改进:

public class ImageDetailFragment extends Fragment {
    private static final String IMAGE_DATA_EXTRA = "resId";
    private int mImageNum;
    private ImageView mImageView;

    static ImageDetailFragment newInstance(int imageNum) {
        final ImageDetailFragment f = new ImageDetailFragment();
        final Bundle args = new Bundle();
        args.putInt(IMAGE_DATA_EXTRA, imageNum);
        f.setArguments(args);
        return f;
    }

    // Empty constructor, required as per Fragment docs
    public ImageDetailFragment() {}

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mImageNum = getArguments() != null ? getArguments().getInt(IMAGE_DATA_EXTRA) : -1;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // image_detail_fragment.xml contains just an ImageView
        final View v = inflater.inflate(R.layout.image_detail_fragment, container, false);
        mImageView = (ImageView) v.findViewById(R.id.imageView);
        return v;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        final int resId = ImageDetailActivity.imageResIds[mImageNum];
        mImageView.setImageResource(resId); // Load image into ImageView
    }
}
希望你注意到这个问题:图像正在读取的用户界面线程上的资源,这可能会导致一个应用程序挂起和被关闭。使用AsyncTask在处理位图的UI线程课中描述的,可以直接将图像加载和处理后台线程:
public class ImageDetailActivity extends FragmentActivity {
    ...

    public void loadBitmap(int resId, ImageView imageView) {
        mImageView.setImageResource(R.drawable.image_placeholder);
        BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
        task.execute(resId);
    }

    ... // include BitmapWorkerTask class
}

public class ImageDetailFragment extends Fragment {
    ...

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (ImageDetailActivity.class.isInstance(getActivity())) {
            final int resId = ImageDetailActivity.imageResIds[mImageNum];
            // Call out to ImageDetailActivity to load the bitmap in a background thread
            ((ImageDetailActivity) getActivity()).loadBitmap(resId, mImageView);
        }
    }
}
任何额外的处理(如大小或从网络上获取图片)可以发生在bitmapworkertask不影响主UI的响应。如果后台线程正在做的不仅仅是直接从磁盘加载图片,也有利于增加内存或磁盘高速缓存中的缓存位图。这里是内存缓存的额外修改:
public class ImageDetailActivity extends FragmentActivity {
    ...
    private LruCache<String, Bitmap> mMemoryCache;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        // initialize LruCache as per Use a Memory Cache section
    }

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

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

    ... // include updated BitmapWorkerTask from Use a Memory Cache section
}
把所有这些碎片拼凑起来 实现 ViewPager加载Bitmap.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值