Bitmap处理之Bitmap缓存

在ListView和GridView中,如果子View在屏幕中看不到,对应的子View的资源会被自动的释放,如果子View重新在屏幕中显示,就需要去重新加载资源,如果是从网络或者从硬盘中去读取资源,这样导致的后果是滑动的时候,显示的效果不是很流畅,如果能将之前已经加载过的资源 放在缓存中,每次都是从缓存中读取资源,这样显示的效果就会变得很流畅,下面分别介绍两种缓存的形式,内存缓存和硬盘缓存

内存缓存

内存缓存主要是使用应用本身的一部分内存作为bitmap的缓存,主要是使用class  LruCache ,这个类还有对应的兼容包,主要的原理就是把bitmap先加载到强引用 LinkedHashMap中,释放掉不再使用的bitmap,官方网站还介绍了一些需要考虑的因素......这里不一一说明,有兴趣的童鞋可以去官方文档,文章的最后有相应的链接,下面直接上代码:
private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    //获得虚拟机的内存,如果超出这个容量就会报出OutOfMemory exception
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    //使用内存的1/8作为bitmap的缓存
    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);
}

当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> {
    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.
    // 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;
    }
    
    // Once complete, see if ImageView is still around and set 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);
            }
        }
    }
}

在后台加载图片时是将图片放在缓存中

硬盘内存

对于Gridiew这样的组件,有时候会很容易就会把内存缓存占满,应用本身如果被其他的应用打断(来电),如果重新回到这个应用,据需要重新处理图片资源。这种情况下开源使用DiskCache,DiskCache缓存处理过的bitmap,当然从硬盘中读取可能会比从内存中读取的速度要低一些,这些操作需要放在后台线程中去处理

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) {
    ...
    // 初始化缓存
    ...
    // 在后台线程中初始化缓存
    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; // 初始化是否完成
            mDiskCacheLock.notifyAll(); // 通知其他的等待线程
        }
        return null;
    }
}

//加载Bitmap的后台线程
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
    ...
    // 在后台解析bitmap.
    @Override
    protected Bitmap doInBackground(Integer... params) {
        final String imageKey = String.valueOf(params[0]);

        // 检查缓存中的资源
        Bitmap bitmap = getBitmapFromDiskCache(imageKey);

        if (bitmap == null) { // 没有资源
            // 处理bitamp
            final Bitmap bitmap = decodeSampledBitmapFromResource(
                    getResources(), params[0], 100, 100));
        }

        // 加载bitmap到缓存中
        addBitmapToCache(imageKey, bitmap);

        return bitmap;
    }
    ...
}

public void addBitmapToCache(String key, Bitmap bitmap) {
    // 在内存缓存中添加资源
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }

    // 同样在disk缓存中也添加对应的资源
    synchronized (mDiskCacheLock) {
        if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
            mDiskLruCache.put(key, bitmap);
        }
    }
}

//总缓存中获得资源
public Bitmap getBitmapFromDiskCache(String key) {
    synchronized (mDiskCacheLock) {
        // 如果diskCache在后台运行,则等待
        while (mDiskCacheStarting) {
            try {
                mDiskCacheLock.wait();
            } catch (InterruptedException e) {}
        }
        if (mDiskLruCache != null) {
            return mDiskLruCache.get(key);
        }
    }
    return null;
}


//创建一个唯一的文件夹作为缓存区,如果有外置的sdcard,就使用,否则就使用内部的sdcard
public static File getDiskCacheDir(Context context, String uniqueName) {

    //检查外置sdcard的状态
    final String cachePath =
            Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
                    !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
                            context.getCacheDir().getPath();

    return new File(cachePath + File.separator + uniqueName);
}
内存缓存的检查在UI线程中而硬盘缓存的检查中后台线程中,不要在UI线程中作读写硬盘的操作,最终处理完成的bitmap会分别加载到内存缓存和硬盘缓存中

处理配置的变化

运行时配置的变化(屏幕方向的变化),在Android中会destory、restart,庆幸的是开源使用内存缓存,内存缓存在Fragment可以传递到新的activity的,例子使用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);
    }
}


原文链接  http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值