仿微信相冊選擇圖片懒叛,查看大圖勇蝙,寫的不太好,希望評論指出不足综液,諒解款慨,先介紹一下我的基本思路。
轉(zhuǎn)載請注明出處:http://blog.csdn.net/self_study/article/details/69397859
對技術(shù)感興趣的同鞋加群 544645972 一起交流谬莹。
思路
第一步檩奠,獲取所有圖片路徑
第一步獲取手機(jī)上的所有圖片路徑:
Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
ContentResolver contentResolver = getContentResolver();
//獲取jpeg和png格式的文件,并且按照時(shí)間進(jìn)行倒序
Cursor cursor = contentResolver.query(uri, null, MediaStore.Images.Media.MIME_TYPE + "=\"image/jpeg\" or " +
MediaStore.Images.Media.MIME_TYPE + "=\"image/png\"", null, MediaStore.Images.Media.DATE_MODIFIED+" desc");
if (cursor != null){
while (cursor.moveToNext()){
//do something
}
handler.sendEmptyMessage(0);
}
然后定義一個(gè)存儲圖片的數(shù)據(jù)格式:
/** 按時(shí)間排序的所有圖片list */
private ArrayList<SingleImageModel> allImages;
/** 按目錄排序的所有圖片list */
private ArrayList<SingleImageDirectories> imageDirectories;
/**
* 一個(gè)文件夾中的圖片數(shù)據(jù)實(shí)體
*/
private class SingleImageDirectories{
/** 父目錄的路徑 */
public String directoryPath;
/** 目錄下的所有圖片實(shí)體 */
public ImageDirectoryModel images;
}
一個(gè)是全部圖片的存儲順序附帽,第二個(gè)是按照目錄的圖片存儲順序埠戳。
第二步,壓縮加載圖片
獲取到圖片之后蕉扮,放入到 Gridview 中進(jìn)行顯示整胃,但是 BitmapFactory.decodeFile() 函數(shù)會非常耗時(shí),所以為了使得非常流暢的顯示圖片喳钟,創(chuàng)建一個(gè)類 AlbumBitmapCacheHelper
屁使,用來異步加載圖片,該類使用 LruCache<String,Bitmap>
來緩存 Bitmap奔则,使得存儲圖片不會造成 OOM蛮寂,我這里設(shè)置 LruCache 的初始大小為 1/4 的運(yùn)行時(shí)內(nèi)存然后使用 ThreadPoolExecutor
線程池來處理圖片的顯示,線程池大小應(yīng)該設(shè)置適中(可以根據(jù)系統(tǒng)的處理器線程數(shù)來設(shè)置易茬,系統(tǒng)也提供一個(gè)相關(guān)的線程池酬蹋,感興趣的也可以去了解),做完這兩件事情之后就可以用來加載圖片了疾呻,方法 getBitmap 用來返回圖片:
Bitmap bitmap = getBitmapFromCache(path, width, height);
//如果能夠從緩存中獲取符合要求的圖片除嘹,則直接回調(diào)
if (bitmap != null) {
} else {
//新建線程放入線程池去處理該圖片的顯示
}
return bitmap;
如果 cache 中找不到該圖片,則調(diào)用 BitmapFactory.decodeFile() 去加載圖片岸蜗,加載圖片不能夠直接加載原圖尉咕,會造成 OOM,所以要去計(jì)算壓縮比:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
options.inSampleSize = computeScale(options, width, height);
options.inJustDecodeBounds = false;
bitmap = BitmapFactory.decodeFile(path, options);
//獲取之后璃岳,放入緩存年缎,以便下次繼續(xù)使用
if (bitmap != null && cache!=null) {
cache.put(path, bitmap);
}
方法 computeScale() 主要是計(jì)算圖片最小的壓縮比悔捶,這樣在 Gridview 中的 getview 方法中去調(diào)用 AlbumBitmapCacheHelper
的 getBitmap() 方法即可。
第三步单芜,處理顯示問題
經(jīng)過上面的處理之后蜕该,在實(shí)際顯示的時(shí)候會出現(xiàn)一些問題,這里也匯總和分析一下:
顯示圖片閃爍
第一個(gè)問題就是圖片顯示會閃爍洲鸠,這主要是由于 getview 方法的 convertView 的復(fù)用導(dǎo)致一個(gè) Imageview 被設(shè)置多次 background堂淡,解決方法就是使用 setTag 方法:
holder.iv_content.setTag(path);
將要顯示的 Imageview 的 tag 設(shè)置為需要顯示的圖片路徑,這樣在回調(diào)的時(shí)候使用方法 gridView.findViewWithTag(path)扒腕,找到這個(gè) Imageview 進(jìn)行顯示绢淀,閃爍的問題就解決了。
大圖片加載速度緩慢
第二個(gè)問題就是加載速度很慢瘾腰,拉的速度很快的情況下皆的,圖片要很久才會加載出來,特別是很大的圖片蹋盆,比如拍照和截圖的照片费薄,這個(gè)問題可以有兩個(gè)步驟去優(yōu)化:第一個(gè)優(yōu)化方案就是在 AlbumBitmapCacheHelper
類中維護(hù)一個(gè) ArrayList<String> currentShowString,在 getView 方法中栖雾,如果該圖片要顯示楞抡,則直接將 path 加入到該 list 中,同時(shí)如果這個(gè) view 的 tag 不為空析藕,說明該 view 的原來的 path 是不需要顯示的拌倍,所以需要將這個(gè) path 從 ArrayList 中刪除:
//優(yōu)化顯示效果
if(holder.iv_content.getTag() != null) {
String remove = (String) holder.iv_content.getTag();
AlbumBitmapCacheHelper.getInstance().removePathFromShowlist(remove);
}
AlbumBitmapCacheHelper.getInstance().addPathToShowlist(path);
這樣在線程池中的處理方式就是先查看需要顯示的 path 是否在 ArrayList 中,如果沒有在 ArrayList 中噪径,則該線程直接關(guān)閉,如果在 ArrayList 中数初,則顯示該圖片:
if (!currentShowString.contains(path)||cache==null) {
return;
}
第二個(gè)優(yōu)化方案是如果顯示的圖片很大找爱,特別是拍照和截圖的圖片,decode 有時(shí)會耗時(shí)幾秒鐘泡孩,微信顯示效果非常好车摄,我自己參考微信的表現(xiàn)想出來的處理的方式是:<ol><li>第一步,從應(yīng)用的緩存 temp 目錄下取仑鸥,如果取不到吮播,則進(jìn)行下一步;</li><li>第二步眼俊,計(jì)算圖片的壓縮比例 samplesize意狠,如果 samplesize < 4(根據(jù)表現(xiàn)一般的相機(jī)拍攝照片為 4000*3000,需要縮放 4 倍才能加速 decode 步驟)疮胖,圖片的 BitmapFactory.decodeFile() 時(shí)間短环戈,直接返回圖片闷板,但是如果 samplesize > 4,執(zhí)行第三步院塞;</li><li>第三步則將壓縮后的圖片存入 temp 目錄下遮晚,以便下次快速取出,這樣微信圖片展示的效果就出來了拦止,顯示的速度和微信一樣县遣,第一次大圖加載慢之外,之后的顯示就能很快:
if (!new File(CommonUtil.getDataPath()).exists())
new File(CommonUtil.getDataPath()).mkdirs();
//臨時(shí)文件的文件名
String tempPath = CommonUtil.getDataPath() + hash + ".temp";
//如果該文件存在
if (new File(tempPath).exists())
bitmap = BitmapFactory.decodeFile(tempPath);
......
//第三步,如果縮放比例大于4汹族,該圖的加載會非常慢萧求,所以將該圖保存到臨時(shí)目錄下以便下次的快速加載
if (options.inSampleSize >= 4) {
try {
File file = new File(tempPath);
if (!file.exists())
file.createNewFile();
FileOutputStream fos = new FileOutputStream(file);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
</li></ol>
問題到這里就差不多解決了;
第四步鞠抑,詳情頁面大圖的展示
第四步大圖的查看饭聚,大圖主要是使用網(wǎng)上開源的 ZoomImageView+Viewpagger 的組合,但是使用這個(gè)出現(xiàn)的問題就是很容易 OOM搁拙,沒辦法秒梳,我的處理方式就是在點(diǎn)進(jìn)去大圖的時(shí)候:
public void releaseHalfSizeCache() {
cache.resize((int) (Runtime.getRuntime().maxMemory() / 1024 / 8));
}
直接將 LruCache 的大小變成原來的一半,因?yàn)椴榭创髨D頁加載一張大圖占用的內(nèi)存本身就很大箕速,所以這樣去特殊處理酪碘,顯示效果頁還湊合,有別的方法盐茎,一定要留言告訴我兴垦。
<font color='red'>還有一點(diǎn)需要注意的是大圖的查看由于需要通過 intent 傳遞數(shù)據(jù),但是 intent 傳遞的數(shù)據(jù)大小不能太大字柠,如果手機(jī)上有幾千張圖片探越,則數(shù)據(jù)量大小可能會超過 intent 所能傳遞的最大量,所以可以寫入一個(gè)公共的地方窑业,共享內(nèi)存钦幔、數(shù)據(jù)庫或者文件都可以,具體的原因可以參考我的另一篇博客:Android TransactionTooLargeException 解析常柄,思考與監(jiān)控方案鲤氢。</font>
第五步,退出相冊頁面清空 LruCache
圖片選擇完成之后西潘,完成善后工作卷玉,將 AlbumBitmapCacheHelper
類中 LruCache 清空,差不多就這樣了喷市,還有很多的功能小點(diǎn)相种,比如圖片時(shí)間的顯示,這里就不詳細(xì)一一去介紹了东抹,具體大家看源碼蚂子。
問題討論
最新發(fā)現(xiàn)的問題:3.0 以前 GC 操作需要很長時(shí)間沃测,經(jīng)常大于 100ms,在執(zhí)行 GC 時(shí)(關(guān)于 JVM 和 ART 的 GC 可以看看我的這篇博客 Android 性能優(yōu)化之內(nèi)存泄漏檢測以及內(nèi)存優(yōu)化(上))食茎,程序就會出現(xiàn)卡的現(xiàn)象蒂破,3.0 以后 GC 修改為同步,執(zhí)行的時(shí)間通常在 5ms 以內(nèi)别渔,在 3.0 以前的版本中附迷,加載圖片時(shí),系統(tǒng)把 Bitmap 加載到 Native 緩存中哎媚,并不受 GC 管理喇伯,需要手機(jī)自己釋放,不然會遇到莫名奇妙的內(nèi)存問題拨与。3.0 以后 Bitmap 直接放到內(nèi)存中在執(zhí)行 GC 時(shí)稻据,會及時(shí)清理無用的 Bitmap 所占的內(nèi)存,在初始化圖片時(shí)把圖片放到內(nèi)存中买喧,當(dāng)加載完后捻悯,系統(tǒng)會把圖片從內(nèi)存轉(zhuǎn)移到顯存中,當(dāng)你用內(nèi)存測試工具時(shí)淤毛,會發(fā)現(xiàn)在加載圖片時(shí)今缚,內(nèi)存占用率很高,當(dāng)加載完成后低淡,內(nèi)存使用量突然下來姓言,加載大量圖片時(shí)會發(fā)現(xiàn)這種情況。
總而言之就是 2.x 版本的時(shí)候蔗蹋,就算你使用的是 LruCache何荚,Bitmap 還是不會被 GC 主動回收,必須要手動釋放猪杭,所以如果需要適配 2.x 版本兽泣,該 demo 需要加入手動釋放 Bitmap 的操作。