Android笔记--Bitmap(二)内存管理
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(二)内存管理的更多相关文章
- iOS学习笔记之ARC内存管理
iOS学习笔记之ARC内存管理 写在前面 ARC(Automatic Reference Counting),自动引用计数,是iOS中采用的一种内存管理方式. 指针变量与对象所有权 指针变量暗含了对其 ...
- 【cocos2d-x 3.x 学习笔记】对象内存管理
内存管理 内存管理一直是一个不易处理的问题.开发人员必须考虑分配回收的方式和时机,针对堆和栈做不同的优化处理,等等.内存管理的核心是动态分配的对象必须保证在使用完成后有效地释放内存,即管理对象的生命周 ...
- iOS阶段学习第20天笔记(MRC内存管理)
iOS学习(OC语言)知识点整理 一.OC中的内存管理 1)概念:内存管理的对象为所有继承了NSObject的对象,对基本数据(如:int .float.double...)无效 OC中采用 ...
- cocos2d-x游戏引擎核心之二——内存管理
(一) cocos2d-x 内存管理 cocos2d里面管理内存采用了引用计数的方式,具体来说就是CCObject里面有个成员变量m_uReference(计数); 1, m_uReference的变 ...
- 《深入分析Java Web技术内幕》读书笔记之JVM内存管理
今天看JVM的过程中收获颇丰,但一想到这些学习心得将来可能被遗忘,便一阵恐慌,自觉得以后要开始坚持做读书笔记了. 操作系统层面的内存管理 物理内存是一切内存管理的基础,Java中使用的内存和应用程序的 ...
- Android笔记--Bitmap
Android | Bitmap解析 Android中Bitmap是对图像的一种抽象.通过他可以对相应的图像进行剪裁,旋转,压缩,缩放等操作.这里循序渐进的一步步了解Bitmap的相关内容. 先了解B ...
- Android笔记(二十八) Android中图片之简单图片使用
用户界面很大程度上决定了APP是否被用户接收,为了提供友好的界面,就需要在应用中使用图片了,Android提供了丰富的图片处理功能. 简单使用图片 使用Drawable对象 为Android应用增加了 ...
- effective OC2.0 52阅读笔记(五 内存管理)
第五章:内存管理 29 理解引用计数 30 以ARC简化引用计数 总结:ARC通过命名约定将内存管理规则标准化.其他编程语言很少像OC这样强调命名.ARC通过设置全局数据结构(此数据结构的具体内容因处 ...
- Spark(二): 内存管理
Spark 作为一个以擅长内存计算为优势的计算引擎,内存管理方案是其非常重要的模块: Spark的内存可以大体归为两类:execution和storage,前者包括shuffles.joins.sor ...
随机推荐
- docker学习 (二)基本概念
基本概念: Docker包括三个基本概念: 镜像(Image): 特殊的文件系统,提供容器运行时所需的程序.库.资源.配置文件.镜像不包含动态数据,内容在构建后不会被改变. 容器(Container) ...
- grep的用法(CentOS7)及有关正则表达式的使用
环境准备:alias grep="grep --color" 1.grep以整行为单位进行处理,行中有的匹配显示出来 Last中取出符合root的行:grep '查找字符串' l ...
- SELinux处理命令
1 查看SELinux状态 Enforcing为开启状态:Disabled为关闭状态. [root@localhost /]# getenforce Enforcing 2 临时关闭SELinux [ ...
- Java对象序列化详解
深入理解Java对象序列化 1. 什么是Java对象序列化 Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比 ...
- Oracle的rowid
ROWID是数据的详细地址,通过rowid,oracle可以快速的定位某行具体的数据的位置. ROWID可以分为物理rowid和逻辑rowid两种.普通的堆表中的rowid是物理rowid,索引组织表 ...
- [poj3417]Network(LCA+树形dp)
题意:给出一棵无根树,然后下面再给出m条边,把这m条边连上,每次你去两条边,规定一条是树边,一条是新边,问有多少种方案能使树断裂. 解题关键:边权转化为点权,记录每条边被环覆盖的次数,通过val[a] ...
- CI框架3.0版本以后,前后台分离的方法。
笔者认为,CI框架官方其实并没有考虑这个前后台分离的问题,所以没有官方的分离方法.而且,2.0版本的分离,也被官方认为这是一个bug.所以在前后台分离这个问题上,其实并不如thinkphp框架. 在C ...
- QDUOJ ycb的ACM进阶之路 二进制多重背包
ycb的ACM进阶之路 发布时间: 2017年5月22日 14:30 最后更新: 2017年5月22日 14:31 时间限制: 1000ms 内存限制: 128M 描述 ycb是个天资聪颖 ...
- POJ - 3037 Skiing SPFA
Skiing Bessie and the rest of Farmer John's cows are taking a trip this winter to go skiing. One day ...
- 深入剖析ASP.NET Core2.1部署模型,你会大吃一惊
---------------------------- 以下内容针对 ASP.NET Core2.1版本,2.2推出windows IIS进程内寄宿 暂不展开讨论---------------- ...