上篇文章说明了图片的加载时,要注意大小问题,和并发的处理问题
下面我们再说明一下bitmap的缓存机制
缓存有两种,一种是内存缓存,较快,但是大小有限
一种是硬盘缓存,较慢,但是空间较大
内存缓存主要是使用一个LruCache类,它可以存储一个键值对,也就是存储图片id和图片的bitmap对象
LruCache<String, Bitmap>
基本原理就是每次加载图片前,先判断是否在LruCache里面缓存了图片,有直接拿出来,没有再去加载,把加载后的图片缓存到LruCache里面用于下次的使用
private LruCache<String, Bitmap> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {
...
// Get memory class of this device, exceeding this amount will throw an
// OutOfMemory exception.
final int memClass = ((ActivityManager) context.getSystemService(
Context.ACTIVITY_SERVICE)).getMemoryClass();
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = 1024 * 1024 * memClass / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap bitmap) {
// The cache size will be measured in bytes rather than number of items.
return bitmap.getByteCount();
}
};
...
}
public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
public Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}
硬盘缓存使用的是DIskLruCache类,这是google推荐的一个类,其实本质也是跟内存缓存一样
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);
}
OK,缓存机制就是上面,下面再讨论一个问题,就是配置更改的处理,例如activity的重建,或者横屏,这时会重新加载图片,造成浪费,我们可以把这些图片给缓存起来
保留原来缓存的方法是:可以通过使用Fragment传递到信的Activity(活动)实例,这个Fragment可以调用setRetainInstance(true)方法保留下来。在Activity(活动)被重新创建后,你可以在上面的Fragment中访问到已经存在的缓存对象,使得图片能快加载并重新填充到ImageView对象中。
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);
}
}
最后我们再来谈论一下viepager,gridview
滑动浏览模式(Swipe View Pattern)是一种很好的浏览详细图片的方式。你可以使用ViewPager组件配合PagerAdapter(适配器)来实现这种模式。然而,更加合适的适配器是FragmentStatePagerAdapter,它可以在ViewPager退出屏幕的时候自动销毁并存储Fragments的状态,使得内存依然保留下来。
注意:如果你只有少量的图片,并且确信它们不会超出程序的内存限制,使用常规的PagerAdapter或者FragmentPagerAdapter或许更加合适。
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
}
}