目錄
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) {
}
}
}
}