2012-09-28 174 views
7

我試圖用SDcard中存儲的文件混合來填充ListView,並將其作爲資產存儲在APK中。使用TraceView,我可以看到AssetManager.list()的性能與File.listFiles()相比較差,儘管我使用SDcard的文件名過濾器。爲什麼AssetManger.list()這麼慢?

下面是一個從文件夾返回的SD卡所有的PNG文件的簡單方法:

// The folder on SDcard may contain files other than png, so filter them out 
private File[] getMatchingFiles(File path) { 
File[] flFiles = path.listFiles(new FilenameFilter() { 
    public boolean accept(File dir, String name) { 
    name = name.toLowerCase(); 
    return name.endsWith(".png"); 
    } 
}); 
return flFiles; 
} 

我這裏調用該方法,它需要大約12ms的檢索16個文件:

final String state = Environment.getExternalStorageState();   
if (Environment.MEDIA_MOUNTED.equals(state)||Environment.MEDIA_SHARED.equals(state)) { 
    File path = Environment.getExternalStoragePublicDirectory(getResources().getString(R.string.path_dir)); 
if (path.exists()){ 
    File[] files = getMatchingFiles(path); 
     ... 

雖然am.list方法需要49ms才能檢索到約6個文件的名稱!

// Get all filenames from specific Asset Folder and store them in String array 
AssetManager am = getAssets(); 
String path = getResources().getString(R.string.path_dir); 
String[] fileNames = am.list(path); 
... 

任何人都可以解釋爲什麼表現會很差嗎?性能是否與APK中存儲的資產數量成比例?我知道資產是壓縮的,但我只是提取資產的名稱,我認爲這些資產將存儲在某個表中。

+0

你幾乎應該知道什麼在你的資源文件夾 – njzk2

+1

AssetManager很爛,性能很差。我在子文件夾中有大約7.5K文件資源。他們是在資產,因爲他們來自外部來源和維護開銷(重命名文件)等,並將其放入資源的性能打擊是不可接受的。爲了找到這個結構中的文件,我必須遞歸地搜索它,並且性能很糟糕,正如你所說的,主要圍繞.list()。似乎設計師從來沒有想過使用大量靜態數據的應用程序。我會有興趣地看這個。 – Simon

回答

1

任何人都可以解釋爲什麼表現會很差嗎?

讀取ZIP存檔(資產所在的APK)的內容比讀取文件系統上目錄的內容要慢,顯然。總之,這並不奇怪,因爲我懷疑所有主要操作系統都是如此。

閱讀list()數據一次,然後將其保存到其他地方以便更快地訪問(例如數據庫),尤其是以針對未來查找而優化的形式(例如,簡單的數據庫查詢可以爲您提供所需的數據,與不得不加載並「再次遞歸搜索」)。

+0

謝謝,這是一個深思熟慮的答案,但是如果緩存列表非常有用,爲什麼系統不能執行此操作?資源已經被枚舉到apk中,爲什麼不能同時存儲資源列表?當然,我可以做CommonsWare建議的東西,並在啓動時讀一遍,但是在AssetManager中緩存這些內容會更有意義嗎? – coverdriven

+2

@coverdriven:「爲什麼系統沒有這樣做?」 - 也許是因爲你的使用案例的開發人員數量相當小AFAIK。 Google沒有無限的工程時間,Android設備沒有無限的存儲空間和內存。因此,並不是開發人員能夠解決的每個問題都將在操作系統級解決。 – CommonsWare

4

Coverdriven的評論「儲存在某個桌子的某處」激勵我解決了我自己一直在推遲的問題。

這並不回答OP,但確實提供了一種不同的方法,它處理CommonsWare解決方案所不具備的子文件夾,除非您遞歸(當然這是另一種可能的解決方案)。它專門針對在子文件夾中擁有大量資源的應用程序。

我增加了一個ANT構建前的目標來運行這個命令(我在Windows上)

dir assets /b /s /A-d > res\raw\assetfiles 

這將創建一個遞歸(/秒),準系統(/ B)的所有文件的清單,但不包括目錄條目(/廣告)在我的資產文件夾中。

然後創建這個類assetfiles的內容靜態加載到散列映射,關鍵其是文件名和值的完整路徑

public class AssetFiles { 

// create a hashmap of all files referenced in res/raw/assetfiles 

/*map of all the contents of assets located in the subfolder with the name specified in FILES_ROOT 
the key is the filename without path, the value is the full path relative to FILES_ROOT 
includes the root, e.g. harmonics_data/subfolder/file.extension - this can be passed 
directly to AssetManager.open()*/ 
public static HashMap<String, String> assetFiles = new HashMap<String, String>(); 
public static final String FILES_ROOT = "harmonics_data"; 

static { 

    String line; 
    String filename; 
    String path; 

    try { 

     BufferedReader reader = new BufferedReader(new InputStreamReader(TidesPlannerApplication.getContext().getResources().openRawResource(R.raw.assetfiles))); 

     while ((line = reader.readLine()) != null) { 
      // NB backlash (note the escape) is specific to Windows 
      filename = line.substring(line.lastIndexOf("\\")+1); 
      path = line.substring(line.lastIndexOf(FILES_ROOT)).replaceAll("\\\\","/");; 
      assetFiles.put(filename, path); 
     } 

    } catch (IOException e) { 
     e.printStackTrace(); 
    } 

} 

public static boolean exists(String filename){ 
    return assetFiles.containsKey(filename); 
} 

public static String getFilename(String filename){ 
    if (exists(filename)){ 
     return assetFiles.get(filename); 
    } else { 
     return ""; 
    } 

} 

}

要使用它,我只需調用AssetFiles.getFilename(filename)即可返回我可以傳遞給AssetManager.open()的完整路徑。快得多!

注意:我還沒有完成這個課,它還沒有硬化,所以你需要添加適當的異常捕獲和操作。我的應用也非常具體,因爲我的所有資產都位於子文件夾中,而子文件夾又位於資產文件夾的子文件夾中(請參閱FILES_ROOT),但很容易適應您的情況。

還要注意需要替換反斜槓,因爲Windows使用正斜槓生成assetfiles列表。你可以在OSX和* nix平臺上解決這個問題。

0

如果您的資產中有深厚的目錄樹,您可以首先檢測到某個項目是文件或目錄,然後調用它上面的.list()(確實加速了遍歷樹的步行)。這是我的解決方案我發現這個:

try { 
    AssetFileDescriptor desc = getAssets().openFd(path); // Always throws exception: for directories and for files 
    desc.close(); // Never executes 
} catch (Exception e) { 
    exception_message = e.toString(); 
} 

if (exception_message.endsWith(path)) { // Exception for directory and for file has different message 
    // Directory 
} else { 
    // File 
} 
1

可以接近APK包,因爲它是一個ZIP文件,讀取使用Java的內置ZipFile中的所有條目。它會給你所有的文件名和完整的路徑。也許不應該很難找到你擁有的目錄。

到目前爲止,這是我測試過的最快的方法。

歸功於@ obastemur的提交上jxcore-android-basics sample project

+0

免責聲明:自我推銷。我創建了一個專門用於添加編譯時安全性的gradle插件,它具有爲每個文件夾自動生成列表的額外好處。請在github.com/oriley-me/crate查看 –