編輯:關於Android編程
一、概述
講解優化查詢相冊圖片之前,我們先來看下PM提出的需求,PM的需求很簡單,就是要做一個類似微信的本地相冊圖片查詢控件,主要包含兩個兩部分:
這兩個需求看似簡單,實則隱藏著一系列的性能優化問題。在做優化之前,我們調研了一些其他比較出名的app在加載大數量圖片的性能表現(gif錄制的不夠清晰,但展示問題已經夠了):
下面測試了幾個常用軟件
微信:
微信的圖片查詢速度還是非常快的,基本上進入圖片選擇頁面,相冊數據就已經查出來了,包括各個圖片目錄下圖片的個數和封面圖片的url,這個體驗還是比較好的。
新浪微博:
相比較微信來說,新浪微博做的體驗就比較差了,進入圖片選擇頁面後,先是黑屏然後是白屏,連個進度條都沒有,讓用戶以為app死掉了,等過一段時間才顯示出來,這個體驗較差
QQ:
QQ一上來是加載的最近100張照片,這個速度非常快,但是進入Camera相冊(有5000多張)後,有一個進度條等待,我體驗了下,等待的時間還是比較長的,這個體驗比新浪微博稍微好點,比微信差
閒魚:
閒魚是做的最爛的一個,一上來是卡死四五秒,然後是黑屏兩三秒,最後才顯示出來
二、綜合對比
經過綜合對比後,就微信做的還比較好,基本上進入相冊頁面就能展示出所有照片,相冊目錄也非常快的展示出來!!!
經過我們的調研,發現微信是采用循環分頁加載策略,我們優化的思路也是采用這種策略,先看優化後的效果圖:
進入圖片選擇頁面,圖片能夠非常快的顯示出來,進入更換相冊頁面,圖片目錄也能非常快的顯示出來,這裡沒有像微信一樣做圖片目錄的緩存:一是因為查詢速度非常快,基本上不到2秒就加載出來了,二是能夠實時刷新出相冊的最新數據
頻繁的切換各個相冊目錄,圖片都能非常快速的查詢出來,體驗還是不錯的!!!
三、優化實現
優化查詢相冊目錄
因為要列舉出所有的相冊目錄列表,這裡沒有其他好的辦法,直接請求ContentResolver的query方法來查詢,這裡為了加速查詢,去掉了while循環中一些耗時的判斷,將一些檢測圖片是否判斷的邏輯移到外面去,具體用的時候再去判斷
查詢圖片的URI
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
因為我們只查詢圖片url和圖片所在的目錄
String[] projection = {MediaStore.Images.ImageColumns.DATA, MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME};
PM要求相冊按照圖片的時間倒敘排列,圖片的創建、修改會影響其所在目錄的排序,排序按時間倒敘排列
String sortOrder = MediaStore.Images.Media.DATE_TAKEN + " DESC ";
根據這些查詢條件,經過query之後得到一個Cursor,這個cursor裡面就包含我們所需要的所有圖片的信息,然後我們while循環遍歷這個cursor,在while循環中一定不能有耗時操作
//一個輔助集合,防止同一目錄被掃描多次 HashSet<String> dirPaths = new HashSet<String>(); while (cursor.moveToNext()) { // 獲取圖片的路徑 String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA)); String bucketName = cursor.getString(cursor.getColumnIndex(MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME)); if (TextUtils.isEmpty(allFolderItem.coverImagePath)) { allFolderItem.coverImagePath = path; } File parentFile = new File(path).getParentFile(); if (parentFile == null) continue; String dirPath = parentFile.getAbsolutePath(); PicFolderItem folderItem = null; // 利用一個HashSet防止多次掃描同一個文件夾(不加這個判斷,圖片多起來還是相當恐怖的~~) if (dirPaths.contains(dirPath)) { continue; } else { dirPaths.add(dirPath); boolean isNew = true; //判斷一下是否dirPath不同,但是bucketName相同 for (PicFolderItem item : picList) { if (item.name.equals(bucketName)) { folderItem = item; item.addParentPath(dirPath); isNew = false; break; } } if (isNew) { folderItem = new PicFolderItem(); folderItem.coverImagePath = path; folderItem.name = bucketName; folderItem.addParentPath(dirPath); } } String[] array = parentFile.list(new FilenameFilter() { @Override public boolean accept(File dir, String filename) { if (filename.endsWith(".jpg") || filename.endsWith(".png") || filename.endsWith(".jpeg")) return true; return false; } }); int arrayCount = array == null ? 0 : array.length; folderItem.count += arrayCount; if (!picList.contains(folderItem) && arrayCount > 0) { picList.add(folderItem); } }
這樣就能非常快速的查詢出手機中所有的圖片目錄、目錄的圖片張數以及封面圖url。這裡主要優化了三點:
while循環中去除耗時判斷
之前的代碼中存在判斷文件圖片是否存在的代碼:
public static boolean isFileExist(String path) { File file = new File(path); if (file == null || !file.exists()) { return false; } return true; }
這段代碼放到while循環中是很恐怖的,我測試了下,5000多張圖片都要檢測的話總時間會增加三四秒。這個判斷可以放到外面去,具體操作哪一個圖片的時候再做具體的業務判斷!
防止一個圖片文件夾被掃描多次
這裡添加了一個變量來存儲已經掃描過的圖片目錄,已經掃描的就不在處理了:
//一個輔助集合,防止統一目錄查詢多次 HashSet<String> dirPaths = new HashSet<String>();
這塊優化了之後效果還是很明顯的,相同的目錄不會掃描多次!
獲取圖片目錄下圖片個數
String[] array = parentFile.list(new FilenameFilter() { @Override public boolean accept(File dir, String filename) { if (filename.endsWith(".jpg") || filename.endsWith(".png") || filename.endsWith(".jpeg")) return true; return false; } });
這個file.list()方法內部是一個native方法,查詢效率非常快!!!
當然獲取某個目錄下的圖片有多少張也可以通過cursor查詢的方式來獲取!!!
查詢某個相冊目錄下的所有照片
在介紹查詢目錄下的照片之前,我們先介紹下我們查詢圖片的兩種策略,一種是針對目錄下圖片比較多的,動不動就上千上萬張的那種;另一種是那種目錄下圖片比較少的,就幾百張圖片
一次加載策略
當目錄下圖片數量小於1000張時采用file.list這個native方法來一次加載所有圖片,這個native查詢效率非常快,上千張圖片都是秒級查詢出來
循環分頁加載策略
當圖片數量大於等於1000張時采用循環分頁加載策略,這種策略專門針對圖片數量特別多的情況,通過分頁的方式先把第一頁的圖片加載出來,讓用戶能第一眼看到最新的圖片,然後後台異步循環的查詢下一頁圖片,直到所有圖片都查詢完成,這也是微信的查詢相冊策略。
一次加載策略實現
我們這裡看下一次加載完策略實現代碼,首先通過File的list方法將後綴為圖片格式的文件過濾出來,返回一個圖片路徑數組
File dirFile = new File(dir); String[] list = dirFile.list(new FilenameFilter() { @Override public boolean accept(File dir, String filename) { if (filename.endsWith(".jpg") || filename.endsWith(".png") || filename.endsWith(".jpeg")) return true; return false; } });
因為我們要的是按時間倒敘進行排列的數組,所以要對上面查詢出來的數組進行排序,這裡用到了File文件lastModified方法
Collections.sort(strings, new Comparator<String>() { @Override public int compare(String lhs, String rhs) { Long time1 = new File(lhs).lastModified(); Long time2 = new File(rhs).lastModified(); return time2.compareTo(time1); } });
循環分頁加載策略實現
這個策略借鑒了微信,通過分頁的方式來一頁一頁的加載圖片,直到所有的圖片都加載完成。
這裡的核心就是查詢條件,將你要查詢的某個目錄添加到查詢參數中
String selection = MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME + " = '" + 目錄名稱 + "' ";
這個selection一定不能寫錯,不然查詢不出來
因為要分頁,sortOrder不是簡單的按照時間倒敘來排了
String sortOrder = MediaStore.Images.Media.DATE_TAKEN + " DESC limit " + PAGE_SIZE + " offset " + pageIndex * PAGE_SIZE;
最後對Cursor進行循環遍歷拿到我們要的圖片路徑
PAGE_SIZE是個常量,表示我們要一次查詢多少條,我們這裡定的是200,一次查詢200條數據,pageIndex是查詢第幾頁,從0開始
一開始的時候查詢第一頁的數據,當查詢的數據列表大小大於等於我們要查詢的PageSize大小時,我們就認為有下一頁,pageIndex加1循環查詢下一頁,直到查詢的列表大小小於PageSize。
經過上面幾步優化後,加載本地相冊圖片基本上就沒有什麼問題了。我們經過真機測試,圖片5549張,都能夠非常快速的查詢出來,堪比微信和圖庫。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持本站。
本文我們將介紹一些關於Android矢量圖的相關知識點。最新的項目中要求以矢量圖替代傳統的.png資源文件,所以特意學習了一下Android中的矢量圖相關概念,不得不說矢
原生應用不僅可以跳轉到RN頁面,也可以吧RN的組件放到原生應用中,作為原生應用的一部分。首先介紹如何把react native嵌入到android中,然後再介紹如何把RN
微信右上角的操作菜單看起來很好用,就照著仿了一下,不過是舊版微信的,手裡剛好有一些舊版微信的資源圖標,給大家分享一下。不知道微信是用什麼實現的,我使用popupwindo
了解二維碼這個東西還是從微信中,當時微信推出二維碼掃描功能,自己感覺挺新穎的,從一張圖片中掃一下竟然能直接加好友,不可思議啊,那時候還不了解二維碼,呵呵,然後