Android優(yōu)化查詢加載大數(shù)量的本地相冊(cè)圖片
一、概述
講解優(yōu)化查詢相冊(cè)圖片之前,我們先來看下PM提出的需求,PM的需求很簡(jiǎn)單,就是要做一個(gè)類似微信的本地相冊(cè)圖片查詢控件,主要包含兩個(gè)兩部分:
- 進(jìn)入圖片選擇頁面就要顯示出手機(jī)中所有的照片,包括系統(tǒng)相冊(cè)圖片和其他目錄下的所有圖片,并按照時(shí)間倒敘排列
- 切換相冊(cè)功能,切換相冊(cè)頁面列出手機(jī)中所有的圖片目錄列表,并且顯示出每個(gè)目錄下所有的圖片個(gè)數(shù)以及封面圖片
這兩個(gè)需求看似簡(jiǎn)單,實(shí)則隱藏著一系列的性能優(yōu)化問題。在做優(yōu)化之前,我們調(diào)研了一些其他比較出名的app在加載大數(shù)量圖片的性能表現(xiàn)(gif錄制的不夠清晰,但展示問題已經(jīng)夠了):
下面測(cè)試了幾個(gè)常用軟件
微信:
微信的圖片查詢速度還是非??斓?,基本上進(jìn)入圖片選擇頁面,相冊(cè)數(shù)據(jù)就已經(jīng)查出來了,包括各個(gè)圖片目錄下圖片的個(gè)數(shù)和封面圖片的url,這個(gè)體驗(yàn)還是比較好的。
新浪微博:
相比較微信來說,新浪微博做的體驗(yàn)就比較差了,進(jìn)入圖片選擇頁面后,先是黑屏然后是白屏,連個(gè)進(jìn)度條都沒有,讓用戶以為app死掉了,等過一段時(shí)間才顯示出來,這個(gè)體驗(yàn)較差
QQ:
QQ一上來是加載的最近100張照片,這個(gè)速度非???,但是進(jìn)入Camera相冊(cè)(有5000多張)后,有一個(gè)進(jìn)度條等待,我體驗(yàn)了下,等待的時(shí)間還是比較長(zhǎng)的,這個(gè)體驗(yàn)比新浪微博稍微好點(diǎn),比微信差
閑魚:
閑魚是做的最爛的一個(gè),一上來是卡死四五秒,然后是黑屏兩三秒,最后才顯示出來
二、綜合對(duì)比
經(jīng)過綜合對(duì)比后,就微信做的還比較好,基本上進(jìn)入相冊(cè)頁面就能展示出所有照片,相冊(cè)目錄也非??斓恼故境鰜恚。?!
經(jīng)過我們的調(diào)研,發(fā)現(xiàn)微信是采用循環(huán)分頁加載策略,我們優(yōu)化的思路也是采用這種策略,先看優(yōu)化后的效果圖:
進(jìn)入圖片選擇頁面,圖片能夠非常快的顯示出來,進(jìn)入更換相冊(cè)頁面,圖片目錄也能非??斓娘@示出來,這里沒有像微信一樣做圖片目錄的緩存:一是因?yàn)椴樵兯俣确浅??,基本上不?秒就加載出來了,二是能夠?qū)崟r(shí)刷新出相冊(cè)的最新數(shù)據(jù)
頻繁的切換各個(gè)相冊(cè)目錄,圖片都能非??焖俚牟樵兂鰜?,體驗(yàn)還是不錯(cuò)的?。?!
三、優(yōu)化實(shí)現(xiàn)
優(yōu)化查詢相冊(cè)目錄
因?yàn)橐信e出所有的相冊(cè)目錄列表,這里沒有其他好的辦法,直接請(qǐng)求ContentResolver的query方法來查詢,這里為了加速查詢,去掉了while循環(huán)中一些耗時(shí)的判斷,將一些檢測(cè)圖片是否判斷的邏輯移到外面去,具體用的時(shí)候再去判斷
查詢圖片的URI
MediaStore.Images.Media.EXTERNAL_CONTENT_URI
因?yàn)槲覀冎徊樵儓D片url和圖片所在的目錄
String[] projection = {MediaStore.Images.ImageColumns.DATA, MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME};
PM要求相冊(cè)按照?qǐng)D片的時(shí)間倒敘排列,圖片的創(chuàng)建、修改會(huì)影響其所在目錄的排序,排序按時(shí)間倒敘排列
String sortOrder = MediaStore.Images.Media.DATE_TAKEN + " DESC ";
根據(jù)這些查詢條件,經(jīng)過query之后得到一個(gè)Cursor,這個(gè)cursor里面就包含我們所需要的所有圖片的信息,然后我們while循環(huán)遍歷這個(gè)cursor,在while循環(huán)中一定不能有耗時(shí)操作
//一個(gè)輔助集合,防止同一目錄被掃描多次 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; // 利用一個(gè)HashSet防止多次掃描同一個(gè)文件夾(不加這個(gè)判斷,圖片多起來還是相當(dāng)恐怖的~~) 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); } }
這樣就能非常快速的查詢出手機(jī)中所有的圖片目錄、目錄的圖片張數(shù)以及封面圖url。這里主要優(yōu)化了三點(diǎn):
while循環(huán)中去除耗時(shí)判斷
之前的代碼中存在判斷文件圖片是否存在的代碼:
public static boolean isFileExist(String path) { File file = new File(path); if (file == null || !file.exists()) { return false; } return true; }
這段代碼放到while循環(huán)中是很恐怖的,我測(cè)試了下,5000多張圖片都要檢測(cè)的話總時(shí)間會(huì)增加三四秒。這個(gè)判斷可以放到外面去,具體操作哪一個(gè)圖片的時(shí)候再做具體的業(yè)務(wù)判斷!
防止一個(gè)圖片文件夾被掃描多次
這里添加了一個(gè)變量來存儲(chǔ)已經(jīng)掃描過的圖片目錄,已經(jīng)掃描的就不在處理了:
//一個(gè)輔助集合,防止統(tǒng)一目錄查詢多次 HashSet<String> dirPaths = new HashSet<String>();
這塊優(yōu)化了之后效果還是很明顯的,相同的目錄不會(huì)掃描多次!
獲取圖片目錄下圖片個(gè)數(shù)
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; } });
這個(gè)file.list()方法內(nèi)部是一個(gè)native方法,查詢效率非??欤。?!
當(dāng)然獲取某個(gè)目錄下的圖片有多少?gòu)堃部梢酝ㄟ^cursor查詢的方式來獲取??!!
查詢某個(gè)相冊(cè)目錄下的所有照片
在介紹查詢目錄下的照片之前,我們先介紹下我們查詢圖片的兩種策略,一種是針對(duì)目錄下圖片比較多的,動(dòng)不動(dòng)就上千上萬張的那種;另一種是那種目錄下圖片比較少的,就幾百?gòu)垐D片
一次加載策略
當(dāng)目錄下圖片數(shù)量小于1000張時(shí)采用file.list這個(gè)native方法來一次加載所有圖片,這個(gè)native查詢效率非???,上千張圖片都是秒級(jí)查詢出來
循環(huán)分頁加載策略
當(dāng)圖片數(shù)量大于等于1000張時(shí)采用循環(huán)分頁加載策略,這種策略專門針對(duì)圖片數(shù)量特別多的情況,通過分頁的方式先把第一頁的圖片加載出來,讓用戶能第一眼看到最新的圖片,然后后臺(tái)異步循環(huán)的查詢下一頁圖片,直到所有圖片都查詢完成,這也是微信的查詢相冊(cè)策略。
一次加載策略實(shí)現(xiàn)
我們這里看下一次加載完策略實(shí)現(xiàn)代碼,首先通過File的list方法將后綴為圖片格式的文件過濾出來,返回一個(gè)圖片路徑數(shù)組
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; } });
因?yàn)槲覀円氖前磿r(shí)間倒敘進(jìn)行排列的數(shù)組,所以要對(duì)上面查詢出來的數(shù)組進(jìn)行排序,這里用到了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); } });
循環(huán)分頁加載策略實(shí)現(xiàn)
這個(gè)策略借鑒了微信,通過分頁的方式來一頁一頁的加載圖片,直到所有的圖片都加載完成。
這里的核心就是查詢條件,將你要查詢的某個(gè)目錄添加到查詢參數(shù)中
String selection = MediaStore.Images.ImageColumns.BUCKET_DISPLAY_NAME + " = '" + 目錄名稱 + "' ";
這個(gè)selection一定不能寫錯(cuò),不然查詢不出來
因?yàn)橐猪?,sortOrder不是簡(jiǎn)單的按照時(shí)間倒敘來排了
String sortOrder = MediaStore.Images.Media.DATE_TAKEN + " DESC limit " + PAGE_SIZE + " offset " + pageIndex * PAGE_SIZE;
最后對(duì)Cursor進(jìn)行循環(huán)遍歷拿到我們要的圖片路徑
PAGE_SIZE是個(gè)常量,表示我們要一次查詢多少條,我們這里定的是200,一次查詢200條數(shù)據(jù),pageIndex是查詢第幾頁,從0開始
一開始的時(shí)候查詢第一頁的數(shù)據(jù),當(dāng)查詢的數(shù)據(jù)列表大小大于等于我們要查詢的PageSize大小時(shí),我們就認(rèn)為有下一頁,pageIndex加1循環(huán)查詢下一頁,直到查詢的列表大小小于PageSize。
經(jīng)過上面幾步優(yōu)化后,加載本地相冊(cè)圖片基本上就沒有什么問題了。我們經(jīng)過真機(jī)測(cè)試,圖片5549張,都能夠非??焖俚牟樵兂鰜恚氨任⑿藕蛨D庫。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android優(yōu)化之電量?jī)?yōu)化的實(shí)現(xiàn)
這篇文章主要介紹了Android優(yōu)化之電量?jī)?yōu)化的實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-07-07解決Eclipse啟動(dòng)出錯(cuò):Failed to create the Java Virtual Machine
這篇文章主要介紹了解決Eclipse啟動(dòng)出錯(cuò):Failed to create the Java Virtual Machine的相關(guān)資料,這里說明出錯(cuò)原因及查找錯(cuò)誤和解決辦法,需要的朋友可以參考下2017-07-07Android使用Shape實(shí)現(xiàn)ProgressBar樣式實(shí)例
本篇文章主要介紹了Android使用Shape實(shí)現(xiàn)ProgressBar樣式實(shí)例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-04-04神經(jīng)網(wǎng)絡(luò)API、Kotlin支持,那些你必須知道的Android 8.1預(yù)覽版和Android Studio 3.0新特
這篇文章主要介紹了神經(jīng)網(wǎng)絡(luò)API、Kotlin支持,那些你必須了解的Android 8.1預(yù)覽版和Android Studio 3.0新特性,需要的朋友可以參考下2017-10-10