仿微信相簿選擇圖片,檢視大圖,寫的不太好,希望評論指出不足,諒解,先介紹一下我的基本思路。
轉載請註明出處:blog.csdn.net/self_study/…
對技術感興趣的同鞋加群 544645972 一起交流。
思路
第一步,獲取所有圖片路徑
第一步獲取手機上的所有圖片路徑:
Uri uri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
ContentResolver contentResolver = getContentResolver();
//獲取jpeg和png格式的檔案,並且按照時間進行倒序
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);
}複製程式碼
然後定義一個儲存圖片的資料格式:
/** 按時間排序的所有圖片list */
private ArrayList<SingleImageModel> allImages;
/** 按目錄排序的所有圖片list */
private ArrayList<SingleImageDirectories> imageDirectories;
/**
* 一個資料夾中的圖片資料實體
*/
private class SingleImageDirectories{
/** 父目錄的路徑 */
public String directoryPath;
/** 目錄下的所有圖片實體 */
public ImageDirectoryModel images;
}複製程式碼
一個是全部圖片的儲存順序,第二個是按照目錄的圖片儲存順序。
第二步,壓縮載入圖片
獲取到圖片之後,放入到 Gridview 中進行顯示,但是 BitmapFactory.decodeFile() 函式會非常耗時,所以為了使得非常流暢的顯示圖片,建立一個類 AlbumBitmapCacheHelper
,用來非同步載入圖片,該類使用 LruCache<String,Bitmap>
來快取 Bitmap,使得儲存圖片不會造成 OOM,我這裡設定 LruCache 的初始大小為 1/4 的執行時記憶體然後使用 ThreadPoolExecutor
執行緒池來處理圖片的顯示,執行緒池大小應該設定適中(可以根據系統的處理器執行緒數來設定,系統也提供一個相關的執行緒池,感興趣的也可以去了解),做完這兩件事情之後就可以用來載入圖片了,方法 getBitmap 用來返回圖片:
Bitmap bitmap = getBitmapFromCache(path, width, height);
//如果能夠從快取中獲取符合要求的圖片,則直接回撥
if (bitmap != null) {
} else {
//新建執行緒放入執行緒池去處理該圖片的顯示
}
return bitmap;複製程式碼
如果 cache 中找不到該圖片,則呼叫 BitmapFactory.decodeFile() 去載入圖片,載入圖片不能夠直接載入原圖,會造成 OOM,所以要去計算壓縮比:
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);
//獲取之後,放入快取,以便下次繼續使用
if (bitmap != null && cache!=null) {
cache.put(path, bitmap);
}複製程式碼
方法 computeScale() 主要是計算圖片最小的壓縮比,這樣在 Gridview 中的 getview 方法中去呼叫 AlbumBitmapCacheHelper
的 getBitmap() 方法即可。
第三步,處理顯示問題
經過上面的處理之後,在實際顯示的時候會出現一些問題,這裡也彙總和分析一下:
顯示圖片閃爍
第一個問題就是圖片顯示會閃爍,這主要是由於 getview 方法的 convertView 的複用導致一個 Imageview 被設定多次 background,解決方法就是使用 setTag 方法:
holder.iv_content.setTag(path);複製程式碼
將要顯示的 Imageview 的 tag 設定為需要顯示的圖片路徑,這樣在回撥的時候使用方法 gridView.findViewWithTag(path),找到這個 Imageview 進行顯示,閃爍的問題就解決了。
大圖片載入速度緩慢
第二個問題就是載入速度很慢,拉的速度很快的情況下,圖片要很久才會載入出來,特別是很大的圖片,比如拍照和截圖的照片,這個問題可以有兩個步驟去優化:第一個優化方案就是在 AlbumBitmapCacheHelper
類中維護一個 ArrayList
//優化顯示效果
if(holder.iv_content.getTag() != null) {
String remove = (String) holder.iv_content.getTag();
AlbumBitmapCacheHelper.getInstance().removePathFromShowlist(remove);
}
AlbumBitmapCacheHelper.getInstance().addPathToShowlist(path);複製程式碼
這樣線上程池中的處理方式就是先檢視需要顯示的 path 是否在 ArrayList 中,如果沒有在 ArrayList 中,則該執行緒直接關閉,如果在 ArrayList 中,則顯示該圖片:
if (!currentShowString.contains(path)||cache==null) {
return;
}複製程式碼
第二個優化方案是如果顯示的圖片很大,特別是拍照和截圖的圖片,decode 有時會耗時幾秒鐘,微信顯示效果非常好,我自己參考微信的表現想出來的處理的方式是:
- 第一步,從應用的快取 temp 目錄下取,如果取不到,則進行下一步;
- 第二步,計算圖片的壓縮比例 samplesize,如果 samplesize < 4(根據表現一般的相機拍攝照片為 4000*3000,需要縮放 4 倍才能加速 decode 步驟),圖片的 BitmapFactory.decodeFile() 時間短,直接返回圖片,但是如果 samplesize > 4,執行第三步;
- 第三步則將壓縮後的圖片存入 temp 目錄下,以便下次快速取出,這樣微信圖片展示的效果就出來了,顯示的速度和微信一樣,第一次大圖載入慢之外,之後的顯示就能很快:
if (!new File(CommonUtil.getDataPath()).exists())
new File(CommonUtil.getDataPath()).mkdirs();
//臨時檔案的檔名
String tempPath = CommonUtil.getDataPath() + hash + ".temp";
//如果該檔案存在
if (new File(tempPath).exists())
bitmap = BitmapFactory.decodeFile(tempPath);
......
//第三步,如果縮放比例大於4,該圖的載入會非常慢,所以將該圖儲存到臨時目錄下以便下次的快速載入
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();
}
}複製程式碼
問題到這裡就差不多解決了;
第四步,詳情頁面大圖的展示
第四步大圖的檢視,大圖主要是使用網上開源的 ZoomImageView+Viewpagger 的組合,但是使用這個出現的問題就是很容易 OOM,沒辦法,我的處理方式就是在點進去大圖的時候:
public void releaseHalfSizeCache() {
cache.resize((int) (Runtime.getRuntime().maxMemory() / 1024 / 8));
}複製程式碼
直接將 LruCache 的大小變成原來的一半,因為檢視大圖頁載入一張大圖佔用的記憶體本身就很大,所以這樣去特殊處理,顯示效果頁還湊合,有別的方法,一定要留言告訴我。
還有一點需要注意的是大圖的檢視由於需要通過 intent 傳遞資料,但是 intent 傳遞的資料大小不能太大,如果手機上有幾千張圖片,則資料量大小可能會超過 intent 所能傳遞的最大量,所以可以寫入一個公共的地方,共享記憶體、資料庫或者檔案都可以,具體的原因可以參考我的另一篇部落格:Android TransactionTooLargeException 解析,思考與監控方案。
第五步,退出相簿頁面清空 LruCache
圖片選擇完成之後,完成善後工作,將 AlbumBitmapCacheHelper
類中 LruCache 清空,差不多就這樣了,還有很多的功能小點,比如圖片時間的顯示,這裡就不詳細一一去介紹了,具體大家看原始碼。
問題討論
最新發現的問題:3.0 以前 GC 操作需要很長時間,經常大於 100ms,在執行 GC 時(關於 JVM 和 ART 的 GC 可以看看我的這篇部落格 Android 效能優化之記憶體洩漏檢測以及記憶體優化(上)),程式就會出現卡的現象,3.0 以後 GC 修改為同步,執行的時間通常在 5ms 以內,在 3.0 以前的版本中,載入圖片時,系統把 Bitmap 載入到 Native 快取中,並不受 GC 管理,需要手機自己釋放,不然會遇到莫名奇妙的記憶體問題。3.0 以後 Bitmap 直接放到記憶體中在執行 GC 時,會及時清理無用的 Bitmap 所佔的記憶體,在初始化圖片時把圖片放到記憶體中,當載入完後,系統會把圖片從記憶體轉移到視訊記憶體中,當你用記憶體測試工具時,會發現在載入圖片時,記憶體佔用率很高,當載入完成後,記憶體使用量突然下來,載入大量圖片時會發現這種情況。
總而言之就是 2.x 版本的時候,就算你使用的是 LruCache,Bitmap 還是不會被 GC 主動回收,必須要手動釋放,所以如果需要適配 2.x 版本,該 demo 需要加入手動釋放 Bitmap 的操作。
顯示效果
原始碼
原始碼下載地址github.com/zhaozepeng/…。