Android 具有完全限定的DB路径名的SQLiteOpenHelper问题

Android 具有完全限定的DB路径名的SQLiteOpenHelper问题,android,Android,在我的应用程序中,我使用 myFilesDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/" + packageName + "/files"); myFilesDir.mkdirs(); 这很好,结果路径是 /mnt/sdcard/Android/data/com.mycompany.myApp/files 我需要

在我的应用程序中,我使用

myFilesDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath()
                      + "/Android/data/" + packageName + "/files");
myFilesDir.mkdirs();
这很好,结果路径是

/mnt/sdcard/Android/data/com.mycompany.myApp/files
我需要一个SQLite数据库,我想存储在SD卡上,所以我扩展SQLiteOpenHelper如下

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...
    }
}
到目前为止还不错-我第一次调用
getReadableDatabase()
getWriteableDatabase()
时,在SD卡上创建了空数据库,然后
onCreate()
填充它

所以问题来了——这个应用程序正在测试中,可能有5到6个人,像我一样,他们运行的是安卓v2.2,一切正常。然而,我有一个测试人员运行v2.1,当
myDbHelper
尝试在第一次使用时创建DB时,它会崩溃,如下所示

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)
文件目录的路径是一个奇数(“/nand”),因为它是内部存储器,虽然不是手机自己的内部存储器-但它是此设备的
getExternalStorageDirectory()
返回的路径

我可以看到三个可能的答案

  • 尽管在v2.2上可以接受,但不建议为DB名称指定完全限定路径,并且在早期版本中会失败
  • SD卡存储可接受完全限定路径,但“/nand”路径被解释为“内部”路径,在这种情况下,仅可接受相对路径
  • 还有一些我完全错过的东西
  • 如果以上任何一项或全部适用,如果有人能帮助我解决这个问题,我将不胜感激


    谢谢。

    历史上,您无法将路径与
    SQLiteOpenHelper
    一起使用。它只适用于简单的文件名。我没有意识到他们在Android 2.2中放宽了这一限制

    如果希望使用SD卡上的数据库,并且希望支持Android 2.1及更早版本,则不能使用
    SQLiteOpenHelper


    对不起

    如果提供了自定义上下文类,并且在目标目录中具有写访问权限,则可以使用带有自定义路径的SQLiteOpenHelper

    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);
      }
    }
    
    下面是定制的DatabaseContext类,它发挥了所有的魔力:

    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;
      }
    }
    
    class DatabaseContext扩展了ContextWrapper{
    私有静态最终字符串DEBUG_CONTEXT=“DatabaseContext”;
    公共数据库上下文(上下文库){
    超级(基地);
    }
    @凌驾
    公共文件getDatabasePath(字符串名称){
    文件sdcard=Environment.getExternalStorageDirectory();
    字符串dbfile=sdcard.getAbsolutePath()+File.separator+“databases”+File.separator+name;
    如果(!dbfile.endsWith(“.db”)){
    dbfile+=“.db”;
    }
    文件结果=新文件(dbfile);
    如果(!result.getParentFile().exists()){
    result.getParentFile().mkdirs();
    }
    if(Log.isLoggable(调试上下文,Log.WARN)){
    Log.w(调试上下文,“getDatabasePath(“+name+””)=“+result.getAbsolutePath());
    }
    返回结果;
    }
    /*此版本是为android设备>=api-11调用的。感谢@DamcCall修复此问题*/
    @凌驾
    公共SQLiteDatabase openOrCreateDatabase(字符串名称,int模式,SQLiteDatabase.CursorFactory工厂,DatabaseErrorHandler errorHandler){
    返回openOrCreateDatabase(名称、模式、工厂);
    }
    /*此版本适用于android设备
    2012年6月更新:
    这是如何工作的(@barry question):

    普通android应用程序都有其相对于应用程序文件夹的本地数据库文件。通过使用覆盖了
    getDatabasePath()
    的客户上下文,数据库现在相对于sd卡上的不同目录

    2015年2月更新:
    在用新的android-4.4设备替换旧的android-2.2设备后,我发现我的解决方案不再有效。 多亏@damccall-s的回答,我才得以修复它。我已经更新了这个答案,所以这应该是一个工作的例子了

    2017年5月更新:


    统计学:这一观点被用在了k3b的答案中。它让我工作。但是,在使用API级别11或更高的设备上,您可能会看到它停止工作。这是因为添加了openOrCreateDatabase()方法的新版本。它现在包含以下签名:

    openDatabase(String path, SQLiteDatabase.CursorFactory factory, int flags, DatabaseErrorHandler errorHandler)
    
    这似乎是在某些设备上使用此方法时默认调用的方法

    为了使此方法在这些设备上工作,您需要进行以下更改:

    首先,编辑现有方法,使其只返回调用新方法的结果

    @Override
    public SQLiteDatabase openOrCreateDatabase(String name, int mode,
            CursorFactory factory) {
        return openOrCreateDatabase(name, mode, factory, null);
    
    }
    
    其次,使用以下代码添加新的覆盖

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

    这段代码与k3b的代码非常相似,但请注意SQLiteDatabase.openOrCreateDatabase使用字符串而不是文件,并且我使用了允许DatabaseErrorHandler对象的版本。

    user2371653的Answare非常好。 但我发现了一个问题:

    @Override
    public SQLiteDatabase openOrCreateDatabase(String name, int mode,
            CursorFactory factory) {
        return openOrCreateDatabase(name, mode, factory, null);
    }
    
    如果在android 2.x上安装应用程序,这可能会导致崩溃

    所以我们可以这样修改它

    @Override
    public SQLiteDatabase openOrCreateDatabase(String name, int mode,
            CursorFactory factory) {
        return super.openOrCreateDatabase(getDatabasePath(name).getAbsolutePath(), mode, factory);
    }
    
    因为android 2.x没有api

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

    在我看来,我在这里找到了一个更好的解决方案()并想通知您。注意构造函数中的条目

    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 );
        }
        ...
    }
    
    从用户网站上引用:
    “它将在sdcard:/sdcard/Android/data/[your_package_name]/文件上的应用程序文件夹中创建数据库。这样,Android会将数据库视为应用程序的一部分,并在用户卸载应用程序时自动删除。”

    “我的应用程序有一个很大的数据库,在大多数情况下,它不适合我内部的旧手机