2011-03-16 12 views
20

En mi aplicación, utilizo ...SQLiteOpenHelper problema con nombre completo de la ruta BD

myFilesDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() 
         + "/Android/data/" + packageName + "/files"); 
myFilesDir.mkdirs(); 

Esto está muy bien y la trayectoria resultante es ...

/mnt/sdcard/Android/data/com.mycompany.myApp/files 

Necesito un SQLite DB que quiero almacenar en la tarjeta SD de forma extiendo SQLiteOpenHelper de la siguiente manera ...

public class myDbHelper extends SQLiteOpenHelper { 

    public myDbHelper(Context context, String name, CursorFactory factory, int version) { 
     // NOTE I prefix the full path of my files directory to 'name' 
     super(context, myFilesDir + "/" + name, factory, version); 
    } 

    @Override 
    public void onCreate(SQLiteDatabase db) { 
     // Create tables and populate with default data... 
    } 
} 

hasta aquí todo bien - la primera vez que llamo getReadableDatabase() o getWriteableDatabase() el DB vacío se crea en la tarjeta SD y onCreate() lo rellena.

Así que aquí está el problema: la aplicación está en pruebas beta con tal vez 5 o 6 personas y, como yo, están ejecutando Android v2.2 y todo funciona bien. Tengo un probador, sin embargo, v2.1 corriendo y cuando myDbHelper trata de crear la base de datos en el primer uso, se bloquea con el siguiente ...

E/AndroidRuntime(3941): Caused by: java.lang.IllegalArgumentException: File /nand/Android/data/com.mycompany.myApp/files/myApp-DB.db3 contains a path separator 
E/AndroidRuntime(3941): at android.app.ApplicationContext.makeFilename(ApplicationContext.java:1445) 
E/AndroidRuntime(3941): at android.app.ApplicationContext.openOrCreateDatabase(ApplicationContext.java:473) 
E/AndroidRuntime(3941): at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:193) 
E/AndroidRuntime(3941): at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:98) 
E/AndroidRuntime(3941): at android.database.sqlite.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:158) 

El camino para el directorio de archivos es una extraña ("/nand ") ya que es la memoria interna aunque no es la memoria interna del teléfono, pero es la ruta devuelta por getExternalStorageDirectory() para este dispositivo.

puedo ver tres respuestas posibles ...

  1. Aunque aceptable en v2.2, que especifica una ruta de acceso completa para el nombre de base de datos, no se recomienda y se producirá un error en las versiones anteriores
  2. rutas completas son aceptables para el almacenamiento de tarjetas SD pero el camino "/ ny" se interpreta como rutas relativas 'interno' y sólo son aceptables en este caso
  3. otra cosa, que me falta por completo

Si aplica cualquiera o todas las opciones anteriores, agradecería que alguien pudiera ayudarme con la forma en que debería abordar esto.

Gracias.

+0

¿No fue esto antes? Parece que el anterior fue eliminado. – Klaus

+0

@Klaus: Sí, me preguntó ella, pero después de 14 horas que tenía sólo unos 10 puntos de vista y no un solo comentario, así que un poco editado y 'golpeado' por volver a colocar después de que he eliminado el antiguo. – Squonk

Respuesta

13

Históricamente, no ha podido utilizar rutas con SQLiteOpenHelper. Solo funcionó en nombres de archivo simples. No me había dado cuenta de que relajaron esa restricción en Android 2.2.

Si desea utilizar bases de datos en la tarjeta SD, y desea dar soporte a Android 2.1 y versiones anteriores, no puede usar SQLiteOpenHelper.

¡Lo siento!

+1

No hay necesidad de 'Lo siento', usted ha dado una respuesta definitiva y es más o menos lo que esperaba (pero no estaba seguro). Sería un poco doloroso volver a trabajar/repensar mi código, pero al menos ahora sé que eso es lo que tengo que hacer. Muchas gracias. – Squonk

+2

es una pena que los documentos no mencionen esto ... – SteelBytes

+1

Acabo de crear una solución que funcione para mi Android 2.2 (y pueden ser otras versiones). Ver mi respuesta a continuación. – k3b

36

Puede utilizar el SQLiteOpenHelper con una ruta personalizada si usted proporcionar una encargoContextClass y si tiene acceso de escritura en el directorio de destino.

public class DatabaseHelper extends SQLiteOpenHelper { 
    private static final int DATABASE_VERSION = 3; 
    ..... 

    DatabaseHelper(final Context context, String databaseName) { 
    super(new DatabaseContext(context), databaseName, null, DATABASE_VERSION); 
    } 
} 

Y aquí es la costumbre DatabaseContext clase que hace toda la magia:

class DatabaseContext extends ContextWrapper { 

    private static final String DEBUG_CONTEXT = "DatabaseContext"; 

    public DatabaseContext(Context base) { 
    super(base); 
    } 

    @Override 
    public File getDatabasePath(String name) { 
    File sdcard = Environment.getExternalStorageDirectory();  
    String dbfile = sdcard.getAbsolutePath() + File.separator+ "databases" + File.separator + name; 
    if (!dbfile.endsWith(".db")) { 
     dbfile += ".db" ; 
    } 

    File result = new File(dbfile); 

    if (!result.getParentFile().exists()) { 
     result.getParentFile().mkdirs(); 
    } 

    if (Log.isLoggable(DEBUG_CONTEXT, Log.WARN)) { 
     Log.w(DEBUG_CONTEXT, "getDatabasePath(" + name + ") = " + result.getAbsolutePath()); 
    } 

    return result; 
    } 

    /* this version is called for android devices >= api-11. thank to @damccull for fixing this. */ 
    @Override 
    public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) { 
    return openOrCreateDatabase(name,mode, factory); 
    } 

    /* this version is called for android devices < api-11 */ 
    @Override 
    public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) { 
    SQLiteDatabase result = SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null); 
    // SQLiteDatabase result = super.openOrCreateDatabase(name, mode, factory); 
    if (Log.isLoggable(DEBUG_CONTEXT, Log.WARN)) { 
     Log.w(DEBUG_CONTEXT, "openOrCreateDatabase(" + name + ",,) = " + result.getPath()); 
    } 
    return result; 
    } 
} 

actualización de junio de 2012:
cómo funciona esto (@barry pregunta):

Las aplicaciones normales de Android tienen sus archivos de base de datos locales relativos a la carpeta de la aplicación. Al utilizar un contexto de cliente con getDatabasePath() sobrescrito, la base de datos ahora está relacionada con un directorio diferente en la tarjeta sd.

actualización Feb 2015:
Después de reemplazar mi viejo androide 2.2 dispositivo con un nuevo dispositivo Android-4.4 descubrí que mi solución no funciona más. Gracias a la respuesta de @ damccull-s pude solucionarlo. He actualizado esta respuesta, por lo que este debería ser un ejemplo de trabajo nuevamente.

actualización de mayo de 2017:

Estadísticas: Este abordaje se utiliza en respuesta more than 200 github projects

+0

Buen ejemplo. Recurrí a usar la memoria interna para la base de datos, pero puedo volver a visitarla usando la tarjeta SD en algún momento en el futuro. Gracias por compartir tu código: lo marcaré como favorito y lo probaré la próxima vez que actualice la aplicación. – Squonk

+0

Gracias. buen ejemplo. Tuve el mismo problema en Motorola Mileston. – kakopappa

+0

tengo un problema en esta fila: super (new DatabaseContext (context), databaseName, null, DATABASE_VERSION); el error es "Ningún caso de que encierra tipo MoviesProvider está disponible debido a alguna invocación intermedia constructor" –

7

de K3b es impresionante. Me consiguió trabajando. Sin embargo, en dispositivos que usan API nivel 11 o superior, es posible que vea que deja de funcionar. Esto se debe a que se agregó una nueva versión del método openOrCreateDatabase(). Ahora contiene la siguiente firma:

openDatabase(String path, SQLiteDatabase.CursorFactory factory, int flags, DatabaseErrorHandler errorHandler) 

Este parece ser el método llamado por defecto en algunos dispositivos con este método disponible.

Con el fin de hacer que este método funcione en estos dispositivos, es necesario hacer las siguientes alteraciones:

En primer lugar, modificar su método existente para que simplemente devuelve el resultado de una llamada al nuevo método.

@Override 
public SQLiteDatabase openOrCreateDatabase(String name, int mode, 
     CursorFactory factory) { 
    return openOrCreateDatabase(name, mode, factory, null); 

} 

En segundo lugar, agregue la nueva anulación con el siguiente código.

@Override 
public SQLiteDatabase openOrCreateDatabase(String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler) { 
    SQLiteDatabase result = SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name).getAbsolutePath(),null,errorHandler); 

    return result; 
} 

Este código es muy similar al código de k3b, pero tenga en cuenta que SQLiteDatabase.openOrCreateDatabase toma una cadena en lugar de un archivo, y he utilizado la versión de la misma que permita un objeto DatabaseErrorHandler.

+2

+1 Esto solucionó mi aplicación después de la actualización de Android 2.2 a Android 4.4. He agregado esto a mi respuesta para que otros usuarios tengan un ejemplo de trabajo – k3b

1

La respuesta de user2371653 es muy agradable. pero me encontré con un problema:

@Override 
public SQLiteDatabase openOrCreateDatabase(String name, int mode, 
     CursorFactory factory) { 
    return openOrCreateDatabase(name, mode, factory, null); 
} 

esto puede causar crasd, si instalar la aplicación en Android 2.x

para que podamos modificarlo como esto

@Override 
public SQLiteDatabase openOrCreateDatabase(String name, int mode, 
     CursorFactory factory) { 
    return super.openOrCreateDatabase(getDatabasePath(name).getAbsolutePath(), mode, factory); 
} 

porque androide 2.x no tiene la API

openOrCreateDatabase(String name, int mode, 
CursorFactory factory, DatabaseErrorHandler errorHandler) 
0

en mi opinión he encontrado una solución mejor en este lado aquí (SQLite database on SD card) y quería informarle. Observe la entrada en el constructor.

public class TestDB extends SQLiteOpenHelper { 
    private static final String DATABASE_NAME = "usertest.db"; 
    private static final int DATABASE_VERSION = 1; 

    public TestDB (Context context){ 
     super(context, context.getExternalFilesDir(null).getAbsolutePath() + "/" + DATABASE_NAME, null, DATABASE_VERSION); 
    } 
    ... 
} 

Presupuesto de la página web del usuario:
"Se va a crear la base de datos en la carpeta de la aplicación en la tarjeta sd:./Sdcard/Android/data/[your_package_name]/Archivos De esa manera se verá la base de datos como parte de la aplicación de Android y eliminado automáticamente si el usuario desinstala la aplicación ".

"I my app Tengo una base de datos grande y en la mayoría de los casos no cabe en la memoria interna de teléfonos viejos, por ejemplo HTC Desire. Funciona muy bien en la tarjeta SD, y la mayoría de las aplicaciones se mueven a sdcard de todos modos no se preocupe porque la base de datos no esté accesible, porque la aplicación no será accesible por sí misma ".

+0

¿Puede darnos información sobre qué versión de Android funcionó? Intenté esto con mi dispositivo android-api-8 y no funcionó. puede ser que solo funciona para api-s más nuevas – k3b

+0

@ k3b: Lo siento, estaba usando api lvl 14. – KingAlex1985

Cuestiones relacionadas