2012-09-28 38 views
7

Estoy intentando llenar un ListView con una mezcla de archivos almacenados en la tarjeta SD Y almacenado como activo en el APK. Usando TraceView, puedo ver que el rendimiento de AssetManager.list() es pobre en comparación con File.listFiles(), aunque estoy usando un filtro de nombre de archivo para la tarjeta SD.¿Por qué AssetManger.list() es tan lento?

Aquí es un método simple que devuelve todos los archivos PNG de una carpeta en la tarjeta 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 ese método aquí y se tarda unos 12 ms para recuperar 16 archivos:

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

Considerando que el método am.list toma 49ms para recuperar solo los nombres de aproximadamente 6 archivos!

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

¿Alguien puede explicar por qué el rendimiento sería tan pobre? ¿El rendimiento es proporcional a la cantidad de activos almacenados en el APK? Soy consciente de que los activos están comprimidos, pero solo estoy obteniendo los nombres de los activos, que pensé que se almacenarían en una tabla en alguna parte.

+0

que se supone más o menos saber lo que está en su carpeta de recursos – njzk2

+1

AssetManager chupa y el rendimiento es pobre. Tengo aproximadamente 7.5K archivos en activos en subcarpetas. Están en activos porque provienen de una fuente externa y la sobrecarga de mantenimiento (cambio de nombre de los archivos), etc. y el rendimiento alcanzado al ponerlos en los recursos no es aceptable. Para encontrar archivos en esta estructura, tengo que buscar recursivamente y el rendimiento es horrible, como dices, principalmente alrededor de .list(). Parece que los diseñadores nunca pensaron en aplicaciones que usan grandes cantidades de datos estáticos. Lo veré con interés. – Simon

Respuesta

1

¿Alguien puede explicar por qué el rendimiento sería tan pobre?

Al leer los contenidos de un archivo ZIP (el APK donde se encuentran los activos) es más lento que leer los contenidos de un directorio en el sistema de archivos, al parecer. En abstracto, esto no es especialmente sorprendente, ya que sospecho que esto sería cierto para todos los principales sistemas operativos.

Lea en ese list() datos una vez, guárdelo en otro lugar para un acceso más rápido (por ejemplo, base de datos), particularmente en un formulario optimizado para búsquedas futuras (por ejemplo, donde una simple consulta de base de datos puede darle lo que desea, frente a tener que cargar y "buscar recursivamente" de nuevo).

+0

Gracias, esta es una respuesta bien pensada, pero si el almacenamiento en caché de la lista es tan útil, ¿por qué el sistema ya no lo hace? Los recursos ya están enumerados cuando se empacan en la aplicación, ¿por qué no se puede almacenar una lista de los activos al mismo tiempo? De acuerdo, podría hacer lo que sugiere CommonsWare y leerlo una vez al inicio, pero ¿no tiene más sentido que esto se guarde en caché dentro del AssetManager? – coverdriven

+2

@coverdriven: "¿por qué el sistema ya no lo hace?" - Tal vez porque el número de desarrolladores con su caso de uso es bastante pequeño AFAIK. Google no tiene una cantidad infinita de tiempo de ingeniería, y los dispositivos Android no tienen una cantidad infinita de espacio de almacenamiento y RAM. Por lo tanto, no todos los problemas que los desarrolladores puedan resolver por sí mismos se resolverán en el nivel del sistema operativo. – CommonsWare

4

El comentario de Coverdriven "almacenados en una mesa en alguna parte" me inspiró a resolver mi propio problema, que he pospuesto por un tiempo.

Esto no responde al OP, pero ofrece un enfoque diferente y maneja subcarpetas que la solución de CommonsWare no aplica a menos que vaya recursivo (que por supuesto es otra posible solución). Está específicamente dirigido a aplicaciones que tienen una gran cantidad de activos en subcarpetas.

he añadido un objetivo pre-construcción ANT para ejecutar este comando (estoy en Windows)

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

Esto crea una recursivas (/ s), barebones (/ b) la lista de todos los archivos, excluyendo entradas de directorio (/ Ad) en mi carpeta de activos.

Entonces creé esta clase para cargar estáticamente el contenido de assetfiles en un mapa hash, la clave de los cuales es el nombre del archivo y el valor de la ruta completa

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

} 

}

Para usarlo, me simplemente llame a AssetFiles.getFilename (filename) que devuelve la ruta completa que puedo pasar a AssetManager.open(). ¡Mucho más rápido!

NB.No he terminado esta clase y aún no está reforzada, por lo que deberá agregar capturas y acciones de excepción apropiadas. También es bastante específico para mi aplicación, ya que todos mis activos están en subcarpetas que a su vez están ubicadas en una subcarpeta de la carpeta de activos (ver FILES_ROOT) pero son fáciles de adaptar a su situación.

Tenga en cuenta también la necesidad de reemplazar las barras invertidas, ya que Windows genera los assetfiles el listado, con barras diagonales. Puede eliminar esto en las plataformas OSX y * nix.

0

Si tiene un árbol profundo de directorios en los activos, primero puede detectar si un elemento es un archivo o directorio y luego llamar a .list() (realmente acelera el recorrido por el árbol). Esta es mi solución que he descubierto para esto:

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

usted puede acercarse paquete APK, ya que es un archivo ZIP y leer todas las entradas con ZipFile orden interna de Java. Le dará todos los nombres de archivo con sus rutas completas. Tal vez no debería ser difícil encontrar qué directorios tienes.

Hasta ahora, este es el enfoque más rápido que he probado.

mérito es de @ obastemur a comprometernos en jxcore-android-basics sample project

+0

Descargo de responsabilidad: Auto promoción. Creé un plugin de gradle específicamente para agregar seguridad de tiempo de compilación, que tiene la ventaja adicional de generar automáticamente una lista para cada carpeta. Compruébelo en github.com/oriley-me/crate –