Почему AssetManger.list() работает так медленно?

Я пытаюсь заполнить ListView смесью файлов, хранящихся на SD-карте, и сохраненных в виде активов в APK. Используя TraceView, я вижу, что производительность AssetManager.list() слабее по сравнению с File.listFiles(), даже несмотря на то, что я использую фильтр имени файла для SD-карты.

Вот простой метод, который возвращает все файлы png из папки на SD-карте:

// 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? Я знаю, что активы сжаты, но я получаю только имена активов, которые, как я думал, будут храниться где-то в таблице.


person coverdriven    schedule 28.09.2012    source источник
comment
вы в значительной степени должны знать, что находится в папке с вашими активами   -  person njzk2    schedule 28.09.2012
comment
AssetManager отстой, а производительность низкая. У меня есть около 7,5 тыс. файлов в активах в подпапках. Они находятся в активах, потому что они из внешнего источника, а накладные расходы на обслуживание (переименование файлов) и т. д., а снижение производительности при размещении их в ресурсах неприемлемо. Чтобы найти файлы в этой структуре, мне приходится рекурсивно искать ее, и производительность, как вы говорите, ужасна, в основном вокруг .list(). Кажется, дизайнеры никогда не думали о приложениях, которые используют большие объемы статических данных. Я посмотрю на это с интересом.   -  person Simon    schedule 28.09.2012


Ответы (4)


Комментарий Coverdriven «хранится где-то в таблице» вдохновил меня на решение моей собственной проблемы, которую я какое-то время откладывал.

Это не отвечает OP, но предлагает другой подход и обрабатывает подпапки, которых нет в решении CommonsWare, если вы не используете рекурсию (что, конечно, является еще одним возможным решением). Он специально предназначен для приложений с большим количеством ресурсов в подпапках.

Я добавил цель предварительной сборки ANT для запуска этой команды (у меня Windows)

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

Это создает рекурсивный (/s), базовый (/b) список всех файлов, за исключением записей каталога (/A-d) в папке моих активов.

Затем я создал этот класс для статической загрузки содержимого файлов ресурсов в хэш-карту, ключом которой является имя файла, а значением — полный путь.

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(). Гораздо быстрее!

NB. Я еще не закончил этот класс, и он еще не усилен, поэтому вам нужно будет добавить соответствующие перехваты исключений и действия. Это также довольно специфично для моего приложения, поскольку все мои ресурсы находятся в подпапках, которые, в свою очередь, расположены в подпапке папки ресурсов (см. FILES_ROOT), но их легко адаптировать к вашей ситуации.

Обратите также внимание на необходимость замены обратной косой черты, поскольку Windows генерирует список файлов ресурсов с прямой косой чертой. Вы можете устранить это на платформах OSX и * nix.

person Simon    schedule 28.09.2012

Кто-нибудь может объяснить, почему производительность будет такой плохой?

По-видимому, чтение содержимого ZIP-архива (APK, в котором находятся ресурсы) медленнее, чем чтение содержимого каталога в файловой системе. Абстрактно это не особенно удивительно, так как я подозреваю, что это справедливо для всех основных операционных систем.

Прочитайте эти list() данные один раз, а затем сохраните их в другом месте для более быстрого доступа (например, в базе данных), особенно в форме, оптимизированной для будущих поисков (например, когда простой запрос к базе данных может дать вам то, что вы хотите, вместо того, чтобы загрузить и снова "рекурсивно искать").

person CommonsWare    schedule 28.09.2012
comment
Спасибо, это вдумчивый ответ, но если кэширование списка так полезно, почему система еще этого не делает? Ресурсы уже перечислены при упаковке в apk, почему нельзя одновременно сохранить список ресурсов? Конечно, я мог бы сделать то, что предлагает CommonsWare, и прочитать его один раз при запуске, но не имеет ли больше смысла кэшировать это в AssetManager? - person coverdriven; 01.10.2012
comment
@coverdriven: почему система этого еще не делает? - возможно, потому что количество разработчиков с вашим вариантом использования довольно мало, насколько я знаю. У Google нет бесконечного количества инженерного времени, а устройства Android не имеют бесконечного объема памяти и оперативной памяти. Следовательно, не каждая проблема, которую разработчики могут решить сами, будет решена на уровне ОС. - person CommonsWare; 01.10.2012

Если у вас есть глубокое дерево каталогов в ресурсах, вы можете сначала определить, является ли элемент файлом или каталогом, а затем вызвать для него .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
}
person Vladyslav Savchenko    schedule 03.03.2015

Вы можете подойти к пакету APK как к ZIP-файлу и прочитать все записи, используя встроенный в Java ZipFile. Это даст вам все имена файлов с их полными путями. Возможно, не составит труда найти, какие каталоги у вас есть.

Пока это самый быстрый подход, который я тестировал.

заслуга принадлежит фиксации @obastemur на пример проекта jxcore-android-basics

person Nuray Altin    schedule 16.03.2015
comment
Отказ от ответственности: самореклама. Я создал плагин Gradle специально для добавления безопасности во время компиляции, который имеет дополнительное преимущество автоматического создания списка для каждой папки. Проверьте это на github.com/oriley-me/crate. - person Kane O'Riley; 13.03.2016