高效的显示位图(四):缓冲位图

- 一次加载一组大图
- 用内存或磁盘缓冲来解决反复加载图片的问题(ListView,GridView,ViewPager等)
- 本篇介绍如何通过内存和磁盘位图缓冲来改善UI在加载多个图片时的灵敏度和流畅性

使用内存缓冲

- 内存缓冲消耗宝贵的应用内存空间来提供位图的快速访问
- LruCache类正好适合此项任务:在一个强引用的LinkedHashMap中保留最近引用的对象,在缓冲达到上限之前淘汰最老的对象
- 确定LruCache的合适大小,需要考虑
  • 有多少内存留给你的活动及应用?
  • 一次要显示多少图片?又有多少是准备马上显示的
  • 屏幕的尺寸和密度时多少?
  • 位图的大小和配置,会分别占用多少内存
  • 图像访问的频率如何?是否一部分图片比另一部分更频繁的被访问?如果是,也许你会希望长期保存某些图片在内存中,甚至为不同组的位图分别谁里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:  In this example, one eighth of the application memory is allocated for our cache. On a normal/hdpi device this is a minimum of around 4MB (32/8). A full screen  GridView  filled with images on a device with 800x480 resolution would use around 1.5MB (800*480*4 bytes), so this would cache a minimum of around 2.5 pages of images in memory.

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

使用磁盘缓冲

- 内存大小有局限,大数据集的组件很容易将其耗尽
- 应用程序被来电等事件打断后,在后台可能被杀死,缓冲也随之销毁,之后一切重来
- 磁盘缓冲可以用于这些情况,将已经处理的位图持久化,帮助降低加载耗时
- ContentProvider可能更加适于用来存储缓冲图像,如果访问频繁的话,比如相册程序。
- Sample代码使用从Android源码中抠出来的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);
}

- 磁盘缓冲的初始化不能在主线程中进行,这意味着缓冲有可能在初始化之前被访问。本例中,一个锁对象被用来保证应用只能在初始化完成之后才能访问缓冲
- 从工作线程操作磁盘缓冲
- 当图像处理完成,将同时被加入磁盘缓冲和内存缓冲待用

处理配置变化

- 运行时配置变化(转屏等)导致活动被销毁并重启,这影响内存缓冲的使用
- 幸运的是,我们的内存缓冲可以通过一个Fragment传递给新的活动实例。这个Fragment通过调用setRetainInstance(true)来保存。
- 当活动重建,这个保存下来的Fragment被重新部署,于是可以接着访问内存缓冲
private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
   
...
   
RetainFragment mRetainFragment =
           
RetainFragment.findOrCreateRetainFragment(getFragmentManager());
    mMemoryCache
= RetainFragment.mRetainedCache;
   
if (mMemoryCache == null) {
        mMemoryCache
= new LruCache<String, Bitmap>(cacheSize) {
           
... // Initialize cache here as usual
       
}
        mRetainFragment
.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();
       
}
       
return fragment;
   
}

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




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值