2012-09-28 20 views
7

Sto tentando di popolare un ListView con una combinazione di file archiviati sulla scheda SD e memorizzati come risorse nell'APK. Utilizzando TraceView, posso vedere che le prestazioni di AssetManager.list() sono scarse rispetto a File.listFiles(), anche se sto utilizzando un filtro nome file per la scheda SD.Perché AssetManger.list() è lento?

Ecco un metodo semplice che restituisce tutti i file PNG da una cartella sulla scheda 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; 
} 

invoco che il metodo qui e ci vogliono circa 12ms per recuperare i 16 file:

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); 
     ... 

Mentre il metodo am.list impiega 49 ms per recuperare solo i nomi di circa 6 file!

// 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); 
... 

Qualcuno può spiegare perché la performance sarebbe così scarsa? Le prestazioni sono proporzionali al numero di risorse memorizzate nell'APK? Sono consapevole che le risorse sono compresse, ma sto solo recuperando i nomi delle risorse, che pensavo sarebbero state archiviate in una tabella da qualche parte.

+0

si suppone più o meno sapere che cosa c'è nella tua cartella di risorsa – njzk2

+1

AssetManager succhia e le prestazioni sono scadenti. Ho circa 7.5K file in risorse in sottocartelle. Sono attivi perché provengono da una fonte esterna e dal sovraccarico di manutenzione (ridenominazione di file) ecc. E il successo delle prestazioni di metterli in risorse non è accettabile. Per trovare i file in questa struttura, devo cercare ricorsivamente e la performance è terribile, come dici tu, principalmente intorno a .list(). Sembra che i progettisti non abbiano mai pensato alle app che utilizzano grandi quantità di dati statici. Lo guarderò con interesse. – Simon

risposta

1

Qualcuno può spiegare perché la performance sarebbe così scarsa?

Leggere il contenuto di un archivio ZIP (l'APK in cui si trovano le risorse) è più lento di leggere il contenuto di una directory sul filesystem, apparentemente. In astratto, questo non è particolarmente sorprendente, come sospetto che questo sarebbe vero per tutti i principali sistemi operativi.

Leggere in quel dato list() una volta, quindi salvarlo da qualche altra parte per un accesso più rapido (ad es. Database), in particolare in una forma ottimizzata per ricerche future (ad esempio, dove una semplice query del database può darti quello che vuoi, contro dover caricare e "ricorsivamente cercare di nuovo").

+0

Grazie, questa è una risposta ponderata, ma se il caching della lista è così utile, perché il sistema non lo fa già? Le risorse sono già elencate quando sono caricate nell'apk, perché non è possibile memorizzare una lista delle risorse contemporaneamente? Certo, potrei fare ciò che CommonsWare suggerisce e leggerlo una volta all'avvio, ma non ha più senso che venga memorizzato nella cache di AssetManager? – coverdriven

+2

@coverdriven: "perché il sistema non lo fa già?" - Forse perché il numero di sviluppatori con il tuo caso d'uso è piuttosto piccolo AFAIK. Google non ha una quantità infinita di tempo di progettazione e i dispositivi Android non hanno una quantità infinita di spazio di archiviazione e RAM. Quindi, non tutti i problemi che gli sviluppatori possono risolvere da soli verranno risolti a livello di sistema operativo. – CommonsWare

4

Il commento di Coverdriven "conservato in un tavolo da qualche parte" mi ha ispirato a risolvere il mio problema che ho rimandato per un po '.

Questo non risponde al PO, ma offre un approccio diverso e gestisce le sottocartelle che la soluzione di CommonsWare non fa se non si procede ricorsivo (che ovviamente è un'altra possibile soluzione). Si rivolge in particolare alle app che hanno un gran numero di risorse in sottocartelle.

ho aggiunto un obiettivo pre-compilazione ANT per eseguire questo comando (io sono su Windows)

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

Questo crea un ricorsive (/ s), barebone (/ b) elenco di tutti i file, ad esclusione voci della directory (/ Ad) nella mia cartella delle risorse.

Poi ho creato questa classe per caricare staticamente i contenuti di assetfiles in una HashMap, la cui chiave è il nome del file e il valore del percorso completo

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

} 

}

di usarlo, ho chiama semplicemente AssetFiles.getFilename (filename) che restituisce il percorso completo che posso passare ad AssetManager.open(). Molto più veloce!

NB.Non ho ancora finito questa lezione e non è ancora stata indurita, quindi dovrai aggiungere opportuni catch e azioni di eccezione. È anche abbastanza specifico per la mia app in quanto tutte le mie risorse sono in sottocartelle che a loro volta si trovano in una sottocartella della cartella delle risorse (vedi FILES_ROOT) ma facili da adattare alla tua situazione.

Nota anche la necessità di sostituire i backslash, poiché Windows genera l'elenco dei file di risorse, con barre in avanti. Potresti eliminarlo su piattaforme OSX e * nix.

0

Se si dispone di una struttura di directory profonda nelle risorse, è possibile rilevare innanzitutto se un elemento è file o directory e quindi richiamare .list() su di esso (accelera veramente il passaggio attraverso l'albero). Questa è la mia soluzione che ho scoperto per questo:

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

si può avvicinare pacchetto APK in quanto è un file ZIP e leggere tutte le voci utilizzando Java incorporato ZipFile. Ti darà tutti i nomi dei file con i loro percorsi completi. Forse non dovrebbe essere difficile trovare le directory che hai.

Finora questo è l'approccio più veloce che ho provato.

merito va a @ obastemur di commit sul jxcore-android-basics sample project

+0

Disclaimer: Autopromozione. Ho creato un plugin gradle appositamente per aggiungere la sicurezza del tempo di compilazione, che ha il vantaggio di generare automaticamente un elenco per ogni cartella. Dai un'occhiata a github.com/oriley-me/crate –