Защо AssetManger.list() е толкова бавен?

Опитвам се да попълня ListView със смес от файлове, съхранени на SDcard И съхранени като активи в APK. Използвайки TraceView, виждам, че производителността на AssetManager.list() е лоша в сравнение с File.listFiles(), въпреки че използвам филтър за имена на файлове за SD картата.

Ето един прост метод, който връща всички png файлове от папка на SDcard:

// 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;
}

Извиквам този метод тук и отнема около 12 ms за извличане на 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 отнема 49 ms, за да извлече само имената на около 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,5K файла в активи в подпапки. Те са в активи, защото са от външен източник и режийните разходи за поддръжка (преименуване на файлове) и т.н. и ударът на производителността от поставянето им в ресурси не е приемлив. За да намеря файлове в тази структура, трябва да я търся рекурсивно и производителността е ужасна, както казвате, главно около .list(). Изглежда, че дизайнерите никога не са мислили за приложения, които използват големи количества статични данни. Ще гледам това с интерес.   -  person Simon    schedule 28.09.2012


Отговори (4)


Коментарът на Coverdriven „съхранен някъде в таблица“ ме вдъхнови да разреша собствения си проблем, който отлагах известно време.

Това не отговаря на OP, но предлага различен подход и обработва подпапки, които решението на CommonsWare не прави, освен ако не преминете рекурсивно (което разбира се е друго възможно решение). Той е специално насочен към приложения, които имат голям брой активи в подпапки.

Добавих цел за предварително компилиране на ANT, за да изпълня тази команда (в Windows съм)

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

Това създава рекурсивен (/s), barebones (/b) списък на всички файлове, с изключение на записи в директория (/A-d) в моята папка с активи.

След това създадох този клас за статично зареждане на съдържанието на 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(). Много по-бързо!

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: защо системата вече не го прави? -- може би защото броят на разработчиците с вашия случай на употреба е доста малък AFAIK. Google няма безкрайно време за инженеринг, а устройствата с Android нямат безкрайно количество място за съхранение и RAM. Следователно не всеки проблем, който разработчиците могат да решат сами, ще бъде решен на ниво ОС. - 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 файл и да прочетете всички записи, като използвате вградения ZipFile на Java. Той ще ви даде всички имена на файлове с техните пълни пътища. Може би не би трябвало да е трудно да откриете кои директории имате.

Досега това е най-бързият подход, който съм тествал.

заслугата е на ангажимента на @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