7

我正在尝试使用存储在 SD 卡上并作为资产存储在 APK 中的混合文件填充 ListView。使用TraceView,我可以看到与AssetManager.list()相比,性能很差File.listFiles(),即使我正在为 SD 卡使用文件名过滤器。

这是一个简单的方法,它从 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;
}

我在这里调用该方法,检索 16 个文件大约需要 12 毫秒:

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 方法只需要 49 毫秒来检索大约 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 中存储的资产数量成正比?我知道资产是压缩的,但我只获取资产的名称,我认为这些名称会存储在某个地方的表中。

4

4 回答 4

4

Coverdrive 的评论“存储在某处的表格中”启发了我解决我自己的问题,我已经推迟了一段时间。

这不能回答 OP,但确实提供了一种不同的方法,并且它处理 CommonsWare 的解决方案所不具备的子文件夹,除非您进行递归(这当然是另一种可能的解决方案)。它专门针对在子文件夹中有大量资产的应用程序。

我添加了一个 ANT 预构建目标来运行这个命令(我在 Windows 上)

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

这将创建所有文件的递归 (/s)、准系统 (/b) 列表,不包括我的资产文件夹中的目录条目 (/Ad)。

然后我创建了这个类来将assetfiles的内容静态加载到一个hashmap中,其中的键是文件名,值是完整路径

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 会使用正斜杠生成资产文件列表。您可以在 OSX 和 *nix 平台上消除此问题。

于 2012-09-28T11:42:26.483 回答
2

谁能解释为什么性能会这么差?

显然,读取 ZIP 存档(资产所在的 APK)的内容比读取文件系统上目录的内容要慢。概括地说,这并不特别令人惊讶,因为我怀疑这对于所有主要操作系统都是如此。

读入该list()数据一次,然后将其保存在其他地方以便更快地访问(例如,数据库),特别是以针对未来查找优化的形式(例如,简单的数据库查询可以为您提供所需的内容,而不是加载并再次“递归搜索它”)。

于 2012-09-28T11:15:08.153 回答
1

您可以处理 APK 包,因为它是一个 ZIP 文件,并使用 Java 的内置 ZipFile 读取所有条目。它将为您提供所有文件名及其完整路径。也许不难找到您拥有的目录。

到目前为止,这是我测试过的最快的方法。

归功于@obastemur 对jxcore-android-basics 示例项目的提交

于 2015-03-16T23:01:44.740 回答
1

If you have a deep tree of directories in the assets you can detect firstly if an item is file or directory and then call .list() on it (really accelerates the walking through the tree). This is my solution I've discovered for this:

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
}
于 2015-03-03T21:00:08.750 回答