Bitmap(二) 内存管理#

1.使用内存缓存保证流畅性

这种使用方式在ListView等这种滚动条的展示方式中使用最为广泛,

使用内存缓存

内存缓存位图可以提供最快的展示。但代价就是占用一定的内存空间。这个工作最适合LruCache.java去做。LruCache具有一块内存区域,他可以用来持有value值得强引用。每次一个value进来,就会放到队列的头,一旦队列满了。队尾的value就会被清除,这样垃圾收集器就可以回收掉踢出队列的value.

entryRemoved(boolean, K, V, V)方法可以显示的踢出指定的value.

   int cacheSize = 4 * 1024 * 1024; // 4MiB

LruCache<String, Bitmap> bitmapCache = new LruCache<String, Bitmap>(cacheSize) {

protected int sizeOf(String key, Bitmap value) {

return value.getByteCount();

}

}//这是常见的分配一个4M的内存作为缓存空间的方法。

这个类是线程安全的。多个缓存操作可以在同步代码块中原子级别的完成。

   synchronized (cache) {
if (cache.get(key) == null) {
cache.put(key, value);
}

}

用这个类就能保证缓存下最近用过的位图,最近没用的位图被踢出缓存中。

使用软引用,弱引用缓存

这种方式不建议使用。因为从API9(Android9)开始垃圾收集器更容易收集软引用和弱引用。这就导致这样的方式变得效果不好。除此之外,API11(Android3.0)之前bitmap缓存在native层内存中,这种存储可能导致不可预见性。潜在可能造成应用内存溢出,程序崩溃。

使用LruCache就要选择一个合适的内存块大小。下列因素是需要考虑的方面:

1.应用剩余内存多少

2.一屏加载多少图,其中有多少图是要缓存准备加载的。

3.设备屏幕大小和像素密度。高像素密度的设备比低像素密度的设备需要更大的内存缓存空间来持有相同大小的图

4.位图的配置和尺寸,每个位图占多大内存

5.图片调用的频繁度,其中有些图片是否比其他图片调用频繁度高。如果是这种情况就需要把某些图片一直放在内存中,或者使用多个LruCache区分存储。

6.数量和质量要有一个平衡。有时候缓存大量的小图比较有用。这时候在后台去加载更大质量更好的图。

LruCache的内存占用大小是一个经验值。这个值取决于用途和方案。如果太小可能会连一张图也存不下。如果太大可能就会造成OOM或者导致剩余给应用使用的内存会变小。

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;//用1/8内存空间做存储
//在使用normal/hdpi的手机上,1/8大概是4M内存。一个在800*480的设备上,充满图片的全屏GridView大概使用1.5M内存。(800*400*4)(这里默认使用8888格式图片)也就是4M的LruCache能缓存两屏半的图。
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);
} //这里用资源ID做key,那如果加载过了,那bitmap就不是null的。可以直接set进ImageView。如果null,那就去开线程拿图。再做缓存
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);
}
}

拿到图以后需要缓存进LruCache

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.使用硬盘缓存保证流畅性

内存缓存可以有效加快Bitmap的加载,然而不能依赖内存缓存去处理正在被加载的图片。

像需要加载大量数据的GridView等视图可以很快用光内存缓存空间。比如一个电话打进来可能就会打断APP的流程。APP进了后台以后就可能被杀死或者被回收掉内存。一旦用户返回应用,那APP可能还是需要加载一边之前的图。

当内存中没有图的时候,这种情况下就需要硬盘缓存去持久化位图,减少加载的时间。

ContenetProvider可能更适合持久化使用频繁的图片。比如系统图片库应用。

	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);//这里去执行拿图的策略
...
}

这个InitDiskCacheTask类用于开一个硬盘缓存

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

BitmapWorkerTask类用于获取位图,先从硬盘缓存取,如果没有再按正常的方式取。取到以后加到内存缓存和硬盘缓存

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

3.处理运行时配置变换

比如屏幕旋转,造成界面重启。这时候肯定是不希望去重新加载一边的。所以最好的办法就是复用原来保存在界面中的图。

可以使用设置了 setRetainInstance(true)的Fragment做这个工作。界面重启以后,这个Fragment可以带着之前的缓存重新attach到界面中。

这样就可以迅速加载图片了。

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

这是相应的Fragment

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

这样就能保证无论怎样反转,都不会影响到图片的加载了。

Android笔记--Bitmap(二)内存管理的更多相关文章

  1. iOS学习笔记之ARC内存管理

    iOS学习笔记之ARC内存管理 写在前面 ARC(Automatic Reference Counting),自动引用计数,是iOS中采用的一种内存管理方式. 指针变量与对象所有权 指针变量暗含了对其 ...

  2. 【cocos2d-x 3.x 学习笔记】对象内存管理

    内存管理 内存管理一直是一个不易处理的问题.开发人员必须考虑分配回收的方式和时机,针对堆和栈做不同的优化处理,等等.内存管理的核心是动态分配的对象必须保证在使用完成后有效地释放内存,即管理对象的生命周 ...

  3. iOS阶段学习第20天笔记(MRC内存管理)

    iOS学习(OC语言)知识点整理 一.OC中的内存管理 1)概念:内存管理的对象为所有继承了NSObject的对象,对基本数据(如:int .float.double...)无效      OC中采用 ...

  4. cocos2d-x游戏引擎核心之二——内存管理

    (一) cocos2d-x 内存管理 cocos2d里面管理内存采用了引用计数的方式,具体来说就是CCObject里面有个成员变量m_uReference(计数); 1, m_uReference的变 ...

  5. 《深入分析Java Web技术内幕》读书笔记之JVM内存管理

    今天看JVM的过程中收获颇丰,但一想到这些学习心得将来可能被遗忘,便一阵恐慌,自觉得以后要开始坚持做读书笔记了. 操作系统层面的内存管理 物理内存是一切内存管理的基础,Java中使用的内存和应用程序的 ...

  6. Android笔记--Bitmap

    Android | Bitmap解析 Android中Bitmap是对图像的一种抽象.通过他可以对相应的图像进行剪裁,旋转,压缩,缩放等操作.这里循序渐进的一步步了解Bitmap的相关内容. 先了解B ...

  7. Android笔记(二十八) Android中图片之简单图片使用

    用户界面很大程度上决定了APP是否被用户接收,为了提供友好的界面,就需要在应用中使用图片了,Android提供了丰富的图片处理功能. 简单使用图片 使用Drawable对象 为Android应用增加了 ...

  8. effective OC2.0 52阅读笔记(五 内存管理)

    第五章:内存管理 29 理解引用计数 30 以ARC简化引用计数 总结:ARC通过命名约定将内存管理规则标准化.其他编程语言很少像OC这样强调命名.ARC通过设置全局数据结构(此数据结构的具体内容因处 ...

  9. Spark(二): 内存管理

    Spark 作为一个以擅长内存计算为优势的计算引擎,内存管理方案是其非常重要的模块: Spark的内存可以大体归为两类:execution和storage,前者包括shuffles.joins.sor ...

随机推荐

  1. fabric优先级,进程管理

    fabric在执行一些命令或者脚本的时候,会执行了操作,但是,进程启动失败,google,发现fabric在执行脚本或者进程的时候,加入set -m参数,就可以正常运行了,解释是:"set ...

  2. MYSQL学习二 关于左连接

    工作中有如下的SQL, 针对A.ID ='abcdefg', left  join  B和C两个表,来查找其他信息.就算是B和C中没有任何满足条件的记录,最后结果也肯定不是空.因为A.ID ='abc ...

  3. 如何分析一个QT类

    作者:gnuhpc  出处:http://www.cnblogs.com/gnuhpc/ 我们以QLineEdit这个类为例来看看如何学习分析一个QT类. 1.Public Types: 这是一个在这 ...

  4. webapi 跨域 (MVC-Web API: 405 method not allowed问题 )

    使用webapi cors 1.安装包:Install-Package Microsoft.AspNet.WebApi.Cors –IncludePrerelease 2.在webapiconfig. ...

  5. jmp $

    in intel x86 instruction set, "jmp $" means jump to this instruction location, thus fallin ...

  6. android fragment ontouch 事件

    由于fragment是存在于activity之中的,所以触摸事件会被activity首先得到. 为了在fragment中执行ontouch方法,需要做如下处理: mFragment = new Tab ...

  7. linux 删除软链接

    Linux下取消软连接,做个案例来说明: 1.先建立一个软连接 ln -s /soft/hexo/nodejs/bin/node /usr/local/bin/node ln -s /soft/hex ...

  8. 洛谷P1044 栈(Catalan数)

    P1044 栈 题目背景 栈是计算机中经典的数据结构,简单的说,栈就是限制在一端进行插入删除操作的线性表. 栈有两种最重要的操作,即pop(从栈顶弹出一个元素)和push(将一个元素进栈). 栈的重要 ...

  9. python 之 软件开发目录规范 、logging模块

    6.4 软件开发目录规范 软件(例如:ATM)目录应该包含: 文件名 存放 备注 bin start.py,用于起动程序   core src.py,程序核心功能代码   conf settings. ...

  10. argparse 在深度学习中的应用

    argparse 介绍 argparse模块主要用来为脚本传递命令参数功能,使他们更加灵活. 代码: parser = argparse.ArgumentParser() #建立解析器,必须写 par ...