目录
3.2、方案二:根据系统内存状态来及时清空缓存内存,防止OOM
3.2.3、 OnLowMemory和OnTrimMemory的比较
3.4、方案四:记录你加载的图片链接,当程序关闭时,主动清除图片缓存
1、背景
Fresco使用,如果列表图片比多,会特别消耗内存,有可能造成OOM,所以必须进行优化。
2、三级缓存
1、 Bitmap缓存---一级缓存
Bitmap缓存存储Bitmap对象,这些Bitmap对象可以立刻用来显示或者用于后处理。在5.0以下系统,Bitmap缓存位于ashmem,这样Bitmap对象的创建和释放将不会引发GC,更少的GC会使你的APP运行得更加流畅。5.0及其以上系统,内存管理有了很大改进,Bitmap缓存直接位于Java的heap上。当应用在后台运行时,该内存会被清空。
2. 未解码图片的内存缓存---二级缓存
这个缓存存储的是原始压缩格式的图片。从该缓存取到的图片在使用之前,需要先进行解码。如果有调整大小,旋转,或者WebP编码转换工作需要完成,这些工作会在解码之前进行。
3. 磁盘缓存(保存到本地)---三级缓存
和未解码的内存缓存相似,磁盘缓存存储的是未解码的原始压缩格式的图片,在使用之前同样需要经过解码等处理。和一二级缓存不一样,磁盘缓存在APP后台运行时,内容是不会被清空的。即使关机也不会。用户可以随时用系统的设置菜单中进行清空缓存操作。
3、内存优化方案
我们在这里将会采用4种优化方案来对大量图片加载时对内存的过度消耗。
方案一:设置缓存策略。
方案二:根据系统内存状态来及时清空缓存内存,防止OOM
方案三:压缩过大的图片,降低内存消耗
方案四:记录你加载的图片链接,当程序关闭时,主动清除图片缓存
3.1、方案一:设置缓存策略
在我们初始化Fresco时,我们可以通过自主设定Fresco的加载参数来控制图片加载占用内存大小。(下边的参数最好根据自己App图片的加载量和内存大小来设置。)
Fresco.initialize(this,config);
/*
* 一级缓存参数 config, >=5.0 in java memory, <5.0 in native memeory.
*/
Supplier<MemoryCacheParams> memoryCacheConfigL1 = new Supplier<MemoryCacheParams>() {
@Override
public MemoryCacheParams get() {
return new MemoryCacheParams(
/* 已解码图片缓存最大值,以字节为单位 */
30 * ByteConstants.MB,
/* 已解码缓存图片最大数量 */
256,
/* 缓存中准备清除但尚未被删除的容器大小(驱逐器) */
15 * ByteConstants.MB,
/* 驱逐器中的图片数量 */
Integer.MAX_VALUE,
/* 缓存中单个图片最大值 */
Integer.MAX_VALUE);
}
};
/*
* 二级缓存参数 config, in native memory.
*/
Supplier<MemoryCacheParams> memoryCacheConfigL2 = new Supplier<MemoryCacheParams>() {
@Override
public MemoryCacheParams get() {
return new MemoryCacheParams(
/* 未解码图片缓存最大值,以字节为单位 */
10 * ByteConstants.MB,
/* 未解码缓存图片最大数量 */
Integer.MAX_VALUE,
/* 缓存中准备清除但尚未被删除的容器大小(驱逐器) */
10 * ByteConstants.MB,
/* 驱逐器中的图片数量 */
Integer.MAX_VALUE,
/* 缓存中单个图片最大值 */
8 * ByteConstants.MB);
}
};
/**
*三级缓存--参数
*/
DiskCacheConfig diskCacheConfig = DiskCacheConfig.newBuilder(context)
.setBaseDirectoryPath(Environment.getExternalStorageDirectory().getAbsoluteFile())// 缓存图片基路径
/* 缓存文件夹名 */
.setBaseDirectoryName("yo_image")
/* 错误缓存的日志记录器 */
.setCacheErrorLogger(new GlobalDiskCacheErrorLogger())
/* 默认缓存的最大值 */
.setMaxCacheSize(100 * ByteConstants.MB)
/* 低磁盘空间时,缓存的最大值 */
.setMaxCacheSizeOnLowDiskSpace(100 / 2 * ByteConstants.MB)
/* 非常低磁盘空间时,缓存的最大值 */
.setMaxCacheSizeOnVeryLowDiskSpace(100 / 3 * ByteConstants.MB)
/* 磁盘配置的版本 */
.setVersion(1).build();
ImagePipelineConfig.Builder builder = ImagePipelineConfig.newBuilder(context)
.setDownsampleEnabled(true)
/* 一级缓存配置 */
.setBitmapMemoryCacheParamsSupplier(memoryCacheConfigL1)
/* 二级缓存配置 */
.setEncodedMemoryCacheParamsSupplier(memoryCacheConfigL2)
/* 三级缓存配置(磁盘) */
.setMainDiskCacheConfig(diskCacheConfig).setBitmapsConfig(bitmapConfig)
.setMemoryTrimmableRegistry(this);
ImagePipelineConfig config=builder.build();
Fresco.initialize(this,config);
3.2、方案二:根据系统内存状态来及时清空缓存内存,防止OOM
在Android的API中有两个方法,可以监听到系统的内存状态。我们可以根据系统的内存状态来主动地释放自身的内存来防止OOM和卡顿的现象。这两个方法分别是onLowMemory& onTrimMemory。
3.2.1、onLowMemory()
OnLowMemory是Android提供的API,在系统内存不足,所有后台程序(优先级为background的进程,不是指后台运行的进程)都被杀死时,系统会调用OnLowMemory。
系统提供的回调有:
- Application.onLowMemory()
- Activity.OnLowMemory()
- Fragement.OnLowMemory()
- Service.OnLowMemory()
- ContentProvider.OnLowMemory()
除了上述系统提供的API,还可以自己实现ComponentCallbacks接口,通过API注册,这样也能得到OnLowMemory回调。然后,通过Context.registerComponentCallbacks ()在合适的时候注册回调就可以了。
通过这种自定义的方法,可以在很多地方注册回调,而不需要局限于系统提供的组件。onLowMemory 当后台程序已经终止资源还匮乏时会调用这个方法。好的应用程序一般会在这个方法里面释放一些不必要的资源来应付当后台程序已经终止,前台应用程序内存还不够时的情况。
3.2.2、onTrimMemory
OnTrimMemory是Android 4.0之后提供的API,系统会根据不同的内存状态来回调。系统提供的回调有:
- Application.onTrimMemory()
- Activity.onTrimMemory()
- Fragement.OnTrimMemory()
- Service.onTrimMemory()
- ContentProvider.OnTrimMemory()
OnTrimMemory的参数是一个int数值,代表不同的内存状态:
- TRIM_MEMORY_RUNNING_MODERATE(5):内存不足(后台进程超过5个),并且该进程优先级比较高,需要清理内存
- TRIM_MEMORY_RUNNING_LOW(10):内存不足(后台进程不足5个),并且该进程优先级比较高,需要清理内存
- TRIM_MEMORY_RUNNING_CRITICAL(15):内存不足(后台进程不足3个),并且该进程优先级比较高,但如果不是放资源,系统开始杀后台进程,此时app应该清理一些资源
- TRIM_MEMORY_UI_HIDDEN(20): 你的用户界面不再可见,此时应该释放仅仅使用在UI上的大资源
- RIM_MEMORY_BACKGROUND(40):系统正处于低内存状态,app进程位于LRU List开始附近,尽管app进程被杀死的概率低,系统可能已经开始杀在LRU List中的后台进程,此时应该释放一些资源,防止被杀的几率。
- TRIM_MEMORY_MODERATE(60) 系统正处于低内存状态,app进程位于LRU List中部附近,如果系统进一步内存紧张,app进程可能会被杀掉
- TRIM_MEMORY_COMPLETE(80) 系统正处于低内存状态,app进程是首先被杀进程之一,如果系统现在没有恢复内存,应该立即释放对app不重要的所有内容
Note:当系统开始杀LRU List中进程时,虽然它主要是自下而上,但它同时会考虑哪些进程占用了更多的内存。因此,在LRU List中消耗的内存越少,更可能保留在List中并且快速恢复的机会就越大
系统也提供了一个ComponentCallbacks2,我们可以实现这个接口然后通过Context.registerComponentCallbacks()注册后,就会被系统回调到。
3.2.3、 OnLowMemory和OnTrimMemory的比较
- OnLowMemory被回调时,已经没有后台进程;而onTrimMemory被回调时,还有后台进程。
- OnLowMemory是在最后一个后台进程被杀时调用,一般情况是low memory killer 杀进程后触发;而OnTrimMemory的触发更频繁,每次计算进程优先级时,只要满足条件,都会触发。
- 通过一键清理后,OnLowMemory不会被触发,而OnTrimMemory会被触发一次。
3.2.4、优化方案代码
public class BaseApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
}
/**
* OnTrimMemory是Android 4.0之后提供的API,系统会根据不同的内存状态来回调。
*/
@Override
public void onTrimMemory(int level) {
super.onTrimMemory(level);
if (level >= ComponentCallbacks2.TRIM_MEMORY_MODERATE) { // 60
ImagePipelineFactory.getInstance().getImagePipeline().clearMemoryCaches();
}
}
/**
* OnLowMemory是Android提供的API,在系统内存不足,所有后台程序
* (优先级为background的进程,不是指后台运行的进程)都被杀死时,系统会调用OnLowMemory。
*/
@Override
public void onLowMemory() {
super.onLowMemory();
ImagePipelineFactory.getInstance().getImagePipeline().clearMemoryCaches();
}
}
3.3、方案三:压缩过大的图片,降低内存消耗
public static void setImageURI(String url,SimpleDraweeView simpleDraweeView, int width, int height) {
Uri uri = (url != null) ? Uri.parse(url) : null;
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setResizeOptions(new ResizeOptions(width, height)).build();
DraweeController controller = Fresco.newDraweeControllerBuilder().setUri(uri)
.setAutoPlayAnimations(true)
.setOldController(simpleDraweeView.getController()).setImageRequest(request)
.build();
simpleDraweeView.setController(controller);
}
3.3.1、注意!
当你适用new ResizeOptions(width, height) 来压缩图片尺寸的时候,这个方法是有限制的:只支持JPG格式的图片。
那么我们怎么来支持更多的格式呢?我们需要在初始化ImagePipelineConfig的时候,setDownsampleEnabled(true)就可以了。上面我们的方案一里边有代码。
3.4、方案四:记录你加载的图片链接,当程序关闭时,主动清除图片缓存
我们在加载图片的Adapter中创建一个存储图片url的数组。
protected Set<String> picCacheUris = null;//图片加载链接
在adapter加载图片的时候保存这些url:
protected void tryAddToCacheUrls(String url) {
if (!picCacheUris.contains(url + ""))
picCacheUris.add(url + "");
}
然后在页面关闭或者退出等必要时候手动清除内存:
@Override
public void onDestroy() {
super.onDestroy();
tryClearMemoryCache();
}
protected void tryClearMemoryCache() {
if (picCacheUris != null && picCacheUris.size() > 0) {
evictFromMemoryCache(picCacheUris);
picCacheUris.clear();
picCacheUris = null;
}
protected void evictFromDiskCache( Set<String> picCacheUris){
if(picCacheUris!=null) {
for (String url : picCacheUris) {
try {
Uri uri = Uri.parse(url + "");
Fresco.getImagePipeline().evictFromMemoryCache(uri);
} catch (Exception e) {
}
}
}
}