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 ...
随机推荐
- vmware 虚拟网卡配置
VMware虚拟机进阶:[1]网卡配置 1. 可以在下面这个图中看到几种网络设置模式,一直以来我们都喜欢用桥接模式和NAT模式,因为这两种模式是最符合一般人上网设置的模式,下面我们就一种种介绍个大家 ...
- Silverlight 5 系列学习之二
昨天学习了一下Silverlight基础感觉也没有什么特别之处,不过圈里朋友劝我不要深入学习了,因为ms已不再爱他的这个孩子了,好吧那就把上些简单的东西稍微过一下吧,要不然公司有什么需求要改的小弟不会 ...
- thymeleaf控制view的返回格式
package com.ailk.dd1.jike.web.config; import nz.net.ultraq.thymeleaf.LayoutDialect; import org.sprin ...
- JQuery中查找父元素,子元素,追加元素,插入元素和删除元素 及其他常用方法
Jquery之所以强大,和其在获取对象时使用与css选择器兼容的语法有很大关系.而且它还兼容了CSS3的选择器,而且多出了不少. 所以jQuery的选择器也就变得很多很强大.就最基本的有以下四个: $ ...
- clone分支,修改文件本地commit后, push回原分支失败,处理方法
从远程clone 一个仓库到本地仓库A后,由于有多个分支,经常需要切换,不同分支区别比较大,切换一下,需要重编译,于是又在本地clone了改动较大的一个分支F到仓库B: 在B仓库改动后,提交到A仓库的 ...
- JS控制GridView行选择
ASP.NET里的GridView控件使用非常广泛,虽然其功能强大,但总有一些不尽如人意的地方.比如在选择行的时候,它就没有UltraWebGrid做的友好:UltraWebGrid允许用户设置是否显 ...
- Git 时光穿梭鸡 管理修改
Git跟踪并管理的是修改,而非文件. 什么是修改? 比如你新增了一行,这就是一个修改, 删除了一行,也是一个修改, 更改了某些字符,也是一个修改, 删了一些又加了一些,也是一个修改, 甚至创建一个新文 ...
- cogs 1901. [国家集训队2011]数颜色
Cogs 1901. [国家集训队2011]数颜色 ★★★ 输入文件:nt2011_color.in 输出文件:nt2011_color.out 简单对比时间限制:0.6 s 内存限制 ...
- axios发送两次请求原因及解决方法
axios发送两次请求原因及解决方法 最近Vue项目中使用axios组件,在页面交互中发现axios会发送两次请求,一种请求方式为OPTIONS,另外一种为自己设置的. 如图: 什么是CORS通信? ...
- C 语言实例 - 判断字母
C 语言实例 - 判断字母 C 语言实例 C 语言实例 用户输入一个字符,判断该字符是否为一个字母. 实例 #include <stdio.h> int main() { char c; ...