詳解Android_性能優(yōu)化之ViewPager加載成百上千高清大圖oom解決方案
一、背景
最近做項目需要用到選擇圖片上傳,類似于微信、微博那樣的圖片選擇器,ContentResolver讀取本地圖片資源并用RecyclerView+Glide加載圖片顯示就搞定列表的顯示,這個沒什么大問題,重點是,點擊圖片進入大圖瀏覽,比如你相冊有幾百張圖片,也就意味著在ViewPager中需要加載幾百個view,況且手機拍出來的圖片都是1-2千萬左右像素的高清大圖(筆者手機2千萬像素 也就是拍照出來的照片3888*5152),大小也有5-7個兆,ViewPager滑動不了十幾張就oom了,即是對圖片做了壓縮處理,把圖片分辨率降低至1366*960,大小壓縮至150k以下,并且在ViewPager的destroyItem方法做了bitmap資源的回收,雖然效果好了點,這也抵擋不了oom的降臨(網(wǎng)上查找的方案都是壓縮、使用第三方控件、回收,其實這都沒用,可能他們沒有真正體驗過ViewPager加載幾百上千張大圖的感受),瀏覽到了第50-70張的時候就oom了 內(nèi)存一直暴漲,根本回收不了的,不信你們試試,壓縮和回收根本不能根治問題,那么怎么解決呢?研究了微信和微博,他們怎么也不會oom,最后我想到了一種解決方案。
二、方案實施
1、以往的普通做法
部分代碼:
List<SubsamplingScaleImageView> mViews = new ArrayList<>();
int size = mDatas.size();
for (int i = 0; i < size; i++) {
SubsamplingScaleImageView view = new SubsamplingScaleImageView(this);
mViews.add(view);
}
mBinding.viewpager.setAdapter(new MyAdapter());
class MyAdapter extends PagerAdapter {
@Override
public int getCount() {
return mDatas.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, final int position) {
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
ViewPager.LayoutParams.MATCH_PARENT,ViewPager.LayoutParams.MATCH_PARENT);
final SubsamplingScaleImageView imageView = mViews.get(position);
imageView.setLayoutParams(params);
final String url = mDatas.get(position);
String cacheExists = cacheExists(url);
if(TextUtils.isEmpty(cacheExists)) {//沒緩存 需要壓縮(壓縮耗時 異步)
new AsyncTask<Void, Void, String>() {
@Override
protected String doInBackground(Void... voids) {
String cacheNoExistsPath = getCacheNoExistsPath(url);
BitmapCompressUtils.compressBitmap(url, cacheNoExistsPath);
File file = new File(cacheNoExistsPath);
if (file.exists()) {//存在表示成功
return cacheNoExistsPath;
} else {
return url;
}
}
@Override
protected void onPostExecute(String s) {
imageView.setImage(ImageSource.uri(s));
}
}.execute();
} else {//有緩存 直接顯示
imageView.setImage(ImageSource.uri(cacheExists));
}
container.addView(imageView);
return imageView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
SubsamplingScaleImageView imageView = mViews.get(position);
if(imageView != null) {
imageView.recycle();
}
container.removeView(imageView);
}
}
/**
* 判斷當前圖片url對應的壓縮過的緩存是否存在 ""表示不存在
*
* @param url 圖片路徑
* @return
*/
private String cacheExists(String url) {
try {
File fileDir = new File(mCacheRootPath);
if(!fileDir.exists()) {
fileDir.mkdirs();
}
File file = new File(mCacheRootPath,new StringBuffer().append(MD5EncryptorUtils.md5Encryption(url)).toString());
if(file.exists()) {
return file.getAbsolutePath();
}
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
public String getCacheNoExistsPath(String url) {
File fileDir = new File(mCacheRootPath);
if(!fileDir.exists()) {
fileDir.mkdirs();
}
return new StringBuffer().append(mCacheRootPath)
.append(MD5EncryptorUtils.md5Encryption(url)).toString();
}
可以看到,這里筆者通過自己的壓縮算法(上一篇文章Android_NDK圖片壓縮之Libjpeg庫使用 )做了圖片壓縮,并緩存,細心的朋友應該有發(fā)現(xiàn)mViews集合添加的view個數(shù)是mDatas的size大小個數(shù),這樣就會導致一個問題ViewPager一直向下滑動的時候,內(nèi)存一直是增加的,即是做了資源回收,也是不能解決問題(況且筆者這里展示圖片的控件是SubsamplingScaleImageView 很不錯的大圖局部加載控件 能有效防止oom),大家可以試試,大量圖片的時候還是會oom,這得歸根于viewpager加載的圖片數(shù)量問題。

2、解決方案:
圖片壓縮也做了,資源回收也做了,但是ViewPager加載越來越多圖片的時候就會oom 你避免不了,不信你試試;
這里就要用到ViewPager的view的重用機制(自己理解的),也就是mViews我們固定給定個數(shù)量,如4,這樣ViewPager的i實際所需要的item也就只有4個。
修改后的部分代碼:
for (int i = 0; i < 4; i++) {
SubsamplingScaleImageView view = new SubsamplingScaleImageView(this);
mViews.add(view);
}
mBinding.viewpager.setAdapter(new MyAdapter());
class MyAdapter extends PagerAdapter {
@Override
public int getCount() {
return mDatas.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, final int position) {
ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(
ViewPager.LayoutParams.MATCH_PARENT,ViewPager.LayoutParams.MATCH_PARENT);
int i = position % 4;
final SubsamplingScaleImageView imageView = mViews.get(i);
imageView.setLayoutParams(params);
final String url = mDatas.get(position);
String cacheExists = cacheExists(url);
if(TextUtils.isEmpty(cacheExists)) {//沒緩存 需要壓縮(壓縮耗時 異步)
new AsyncTask<Void, Void, String>() {
@Override
protected String doInBackground(Void... voids) {
String cacheNoExistsPath = getCacheNoExistsPath(url);
BitmapCompressUtils.compressBitmap(url, cacheNoExistsPath);
File file = new File(cacheNoExistsPath);
if (file.exists()) {//存在表示成功
return cacheNoExistsPath;
} else {
return url;
}
}
@Override
protected void onPostExecute(String s) {
imageView.setImage(ImageSource.uri(s));
}
}.execute();
} else {//有緩存 直接顯示
imageView.setImage(ImageSource.uri(cacheExists));
}
container.addView(imageView);
return imageView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
int i = position % 4;
SubsamplingScaleImageView imageView = mViews.get(i);
if(imageView != null) {
imageView.recycle();
}
container.removeView(imageView);
}
很簡單的修改 就能有效防止oom 利用position%4拿到第幾個控件從mViews取值,保證了viewpager加載的mViews存儲的圖片為4個
看一直向下滑動的內(nèi)存走勢圖

內(nèi)存基本維持穩(wěn)定
三、demo演示
因為要讀取相冊就沒再模擬器運行錄制gif ,直接截圖

demo下載:demo
四、總結(jié)
這個只是簡單的演示,實際項目中的相冊比這個復雜多了,簡單說就是要壓縮,要回收,View重用。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
基于flutter?sound插件實現(xiàn)錄音與播放功能
這篇文章主要介紹了基于flutter?sound插件實現(xiàn)錄音與播放功能,介紹了如何錄音,如何播放本地和遠程音頻文件,以及如何實現(xiàn)動畫,在錄制完音頻文件后如何上傳,這些都是我們平常使用這個功能會遇到的問題。在使用的過程中遇到的問題也有列出,需要的朋友可以參考下2022-05-05
基于Android SQLiteOpenHelper && CRUD 的使用
本篇文章小編為大家介紹,基于Android SQLiteOpenHelper && CRUD的使用。需要的朋友可以參考一下2013-04-04
詳解Android中實現(xiàn)ListView左右滑動刪除條目的方法
這篇文章主要介紹了Android中實現(xiàn)ListView左右滑動刪除條目的方法,文中分別展示了通過Scroller和NineOldAndroids來實現(xiàn)的例子,需要的朋友可以參考下2016-04-04
android:layout_gravity和android:gravity的區(qū)別
本篇文章主要介紹了android中g(shù)iavity和layout_gravity的區(qū)別。具有很好的參考價值。下面跟著小編一起來看下吧2017-04-04

