In my app, I use...
myFilesDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + packageName + "/files"); myFilesDir.mkdirs();
This is fine and the resulting path is...
/mnt/sdcard/Android/data/com.mycompany.myApp/files
I need a SQLite DB which I want to store on the SD card so I extend SQLiteOpenHelper as follows...
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... } }
So far so good - the first time I call getReadableDatabase()
or getWriteableDatabase()
the empty DB is created on the SD card and onCreate()
populates it.
So here's the problem - the app is in beta testing with maybe 5 or 6 people and, like me, they're running Android v2.2 and everything works fine. I have one tester, however, running v2.1 and when myDbHelper
tries to create the DB on first use, it crashes with the following...
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)
The path for the files directory is an odd one ("/nand") as it's internal memory although not the phone's own internal memory - but it is the path returned by getExternalStorageDirectory()
for this device.
I can see three possible answers...
If any or all of the above apply I'd appreciate it if somebody could help with how I should approach this.
Thanks.
there is no difference between SQLiteOpenHeloper::close() and SQLiteDatabase::close() . SQLiteOpenHeloper::close() is just a wrapper around SQLiteDatabase::close() . but as a rule of thumb, either let SQLiteOpenHelper manages your connections, or don't use it and manage it yourself. see this blog post.
onUpgrade. Called when the database needs to be upgraded. The implementation should use this method to drop tables, add tables, or do anything else it needs to upgrade to the new schema version. The SQLite ALTER TABLE documentation can be found here.
SQLiteOpenHelper class is used for database creation and version management. For performing any database operation, you have to provide the implementation of onCreate() and onUpgrade() methods of SQLiteOpenHelper class.
You can use the SQLiteOpenHelper with a custom path if you provide a custom ContextClass and if you have write access in the target directory.
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); } }
And here is the custom DatabaseContext class that does all the magic:
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; } }
Update june 2012:
how does this work (@barry question):
Normal android apps have their local database files relative to the app folder. By using a customer context with overwritten getDatabasePath()
the database is now relative to a different directory on the sd card.
Update feb 2015:
After replacing my old android-2.2 device with a new android-4.4 device I found out that my solution didn't work anymore. Thanks to @damccull-s answer I was able to fix it. I have updated this answer so this should be a working example again.
Update may 2017:
Statistics: This aproach is used in more than 200 github projects
Historically, you have not been able to use paths with SQLiteOpenHelper
. It only worked on simple filenames. I had not realized that they relaxed that restriction in Android 2.2.
If you wish to use databases on the SD card, and you wish to support Android 2.1 and earlier, you cannot use SQLiteOpenHelper
.
Sorry!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With