Problema SQLiteOpenHelper con nombre de ruta de base de datos completo

En mi aplicación, uso …

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

Esto está bien y el camino resultante es …

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

Necesito una base de datos SQLite que quiero almacenar en la tarjeta SD, por lo que 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 ahora todo bien, la primera vez que llamo getReadableDatabase() o getWriteableDatabase() el DB vacío se crea en la tarjeta SD y onCreate() rellena.

Así que este es 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. Sin embargo, tengo un probador que ejecuta v2.1 y cuando myDbHelper intenta crear el DB en el primer uso, falla con lo 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) 

La ruta del directorio de archivos es impar (“/ 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 posibles respuestas …

  1. Aunque es aceptable en v2.2, no se recomienda especificar una ruta totalmente calificada para el nombre de la BD y fallará en versiones anteriores
  2. Las rutas totalmente calificadas son aceptables para el almacenamiento de la tarjeta SD, pero la ruta “/ nand” se interpreta como ‘interna’ y solo las rutas relativas son aceptables en este caso
  3. Algo más de lo que me estoy perdiendo por completo

Si alguno o todos los anteriores se aplican, agradecería que alguien pudiera ayudarme con la forma en que debería abordar esto.

Gracias.

Históricamente, no ha podido usar 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 SQLiteOpenHelper anteriores, no puede usar SQLiteOpenHelper .

¡Lo siento!

Puede usar SQLiteOpenHelper con una ruta personalizada si proporciona un ContextClass personalizado 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í está la clase de DatabaseContext personalizada 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 junio de 2012:
cómo funciona esto (pregunta de @barry):

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() sobreescrito, la base de datos ahora es relativa a un directorio diferente en la tarjeta sd.

Actualización feb 2015:
Después de reemplazar mi antiguo dispositivo Android 2.2 con un nuevo dispositivo Android 4.4 descubrí que mi solución ya no funcionaba. 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 enfoque se usa en más de 200 proyectos de Github

La respuesta de k3b es asombrosa. 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.

Para que este método funcione en estos dispositivos, debe realizar las siguientes modificaciones:

Primero, edite su método existente para que simplemente devuelva 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); } 

Segundo, 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 usado la versión que permite un objeto DatabaseErrorHandler.

la respuesta de user2371653 es muy agradable. pero encontré un problema:

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

esto puede causar crasd, si instala su aplicación en Android 2.x.

entonces podemos modificarlo así

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

porque Android 2.x no tiene la API

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

en mi opinión, encontré una mejor solución en este lado aquí ( base de datos SQLite en la tarjeta SD ) 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 ); } ... } 

Cita del sitio web del usuario:
“Creará la base de datos en la carpeta de la aplicación en la tarjeta sd: / sdcard / Android / data / [your_package_name] / files. De esa forma, la base de datos será vista como parte de la aplicación por Android y eliminada automáticamente si el usuario desinstala la aplicación “.

“Mi aplicación tengo una gran base de datos y en la mayoría de los casos no cabe en la memoria interna de los 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, así que no se preocupe acerca de que la base de datos no está accesible, porque la aplicación no será accesible por sí misma “.