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

时间:2022-01-23 22:43:11

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

我需要一个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...
    }
}

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.

到目前为止一切顺利 - 我第一次调用getReadableDatabase()或getWriteableDatabase()时,在SD卡上创建了空DB,onCreate()填充了它。

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

所以这就是问题 - 应用程序正在进行beta测试,可能有5或6个人,和我一样,他们正在运行Android v2.2,一切正常。我有一个测试人员,但是,运行v2.1并且当myDbHelper尝试在第一次使用时创建数据库时,它崩溃了以下...

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.

文件目录的路径是奇数(“/ nand”),因为它的内部存储器虽然不是电话自己的内部存储器 - 但它是getExternalStorageDirectory()为此设备返回的路径。

I can see three possible answers...

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

  1. Although acceptable on v2.2, specifying a fully qualified path for DB name isn't recommended and will fail on earlier versions
  2. 尽管在v2.2上可以接受,但不建议为DB名称指定完全限定的路径,并且在早期版本中将失败

  3. Fully qualified paths are acceptable for SD card storage but the "/nand" path is being interpreted as 'internal' and only relative paths are acceptable in this case
  4. SD卡存储可接受完全限定的路径,但“/ nand”路径被解释为“内部”,在这种情况下只能接受相对路径

  5. Something else which I'm missing completely
  6. 还有一些我完全错过的东西

If any or all of the above apply I'd appreciate it if somebody could help with how I should approach this.

如果上述任何一项或全部适用,我会很感激,如果有人可以帮我解决这个问题。

Thanks.

5 个解决方案

#1


13  

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.

从历史上看,您无法使用SQLiteOpenHelper的路径。它只适用于简单的文件名。我没有意识到他们在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.

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

Sorry!

#2


39  

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.

如果提供自定义ContextClass并且在目标目录中具有写访问权限,则可以将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);
  }
}

And here is the custom DatabaseContext class that does all the magic:

这是自定义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;
  }
}

Update june 2012:
how does this work (@barry question):

2012年6月更新:这是如何工作的(@barry问题):

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.

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

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.

更新2015年2月:用新的android-4.4设备替换旧的android-2.2设备后,我发现我的解决方案不再适用了。感谢@ damccull的答案,我能够解决它。我已经更新了这个答案,所以这应该是一个有效的例子。

Update may 2017:

2017年更新:

Statistics: This aproach is used in more than 200 github projects

统计:这个方法用于200多个github项目

#3


8  

k3b's answer is awesome. It got me working. However, on devices using API level 11 or higher, you may see it stop working. This is because a new version of the openOrCreateDatabase() method was added. It now contains the following signature:

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

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

This seems to be the method called by default on some devices with this method available.

这似乎是在某些使用此方法的设备上默认调用的方法。

In order to make this method work on these devices, you need to make the following alterations:

为了使此方法适用于这些设备,您需要进行以下更改:

First, edit your existing method so that it simply returns the result of a call to the new method.

首先,编辑现有方法,以便它只返回对新方法的调用结果。

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

}

Second, add the new override with the following code.

其次,使用以下代码添加新覆盖。

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

    return result;
}

This code is very similar to k3b's code, but note that SQLiteDatabase.openOrCreateDatabase takes a String instead of a File, and I've used the version of it that allows for a DatabaseErrorHandler object.

此代码与k3b的代码非常相似,但请注意,SQLiteDatabase.openOrCreateDatabase采用String而不是File,并且我使用了允许DatabaseErrorHandler对象的版本。

#4


2  

user2371653's answere is very nice. but I found a issue:

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

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

this may cause crasd, if install your app at android 2.x

如果在Android 2.x上安装你的应用程序,这可能会导致crasd

so we can modify it like this

所以我们可以像这样修改它

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

because android 2.x does not has the api

因为android 2.x没有api

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

#5


1  

in my opinion I found a better solution on this side here (SQLite database on SD card) and wanted to inform you. Notice the entry in the constructor.

在我看来,我在这边找到了一个更好的解决方案(SD卡上的SQLite数据库),并想通知你。注意构造函数中的条目。

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

Quote from the user website:
"It will create the database in the app's folder on the sdcard: /sdcard/Android/data/[your_package_name]/files. In that way the database will be seen as part of the app by android and removed automatically if the user uninstalls the app."

从用户网站引用:“它将在sdcard上的应用程序文件夹中创建数据库:/ sdcard / Android / data / [your_package_name] / files。这样,数据库将被android视为应用程序的一部分并被删除如果用户卸载应用程序,则会自动执行。“

"I my app I have a large database and it will in most cases not fit on old phones internal memory, e.g. HTC Desire. It runs great on the sdcard, and most apps are "moved to sdcard" themselves anyway so don't worry about the database not being accessible, because the app won't be accessible it self."

“我的应用程序我有一个大型数据库,它在大多数情况下都不适合旧手机的内存,例如HTC Desire。它在SD卡上运行得很好,而且大多数应用程序都”自动转移到SD卡“所以不要担心关于数据库无法访问,因为该应用程序无法自行访问。“

#1


13  

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.

从历史上看,您无法使用SQLiteOpenHelper的路径。它只适用于简单的文件名。我没有意识到他们在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.

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

Sorry!

#2


39  

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.

如果提供自定义ContextClass并且在目标目录中具有写访问权限,则可以将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);
  }
}

And here is the custom DatabaseContext class that does all the magic:

这是自定义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;
  }
}

Update june 2012:
how does this work (@barry question):

2012年6月更新:这是如何工作的(@barry问题):

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.

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

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.

更新2015年2月:用新的android-4.4设备替换旧的android-2.2设备后,我发现我的解决方案不再适用了。感谢@ damccull的答案,我能够解决它。我已经更新了这个答案,所以这应该是一个有效的例子。

Update may 2017:

2017年更新:

Statistics: This aproach is used in more than 200 github projects

统计:这个方法用于200多个github项目

#3


8  

k3b's answer is awesome. It got me working. However, on devices using API level 11 or higher, you may see it stop working. This is because a new version of the openOrCreateDatabase() method was added. It now contains the following signature:

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

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

This seems to be the method called by default on some devices with this method available.

这似乎是在某些使用此方法的设备上默认调用的方法。

In order to make this method work on these devices, you need to make the following alterations:

为了使此方法适用于这些设备,您需要进行以下更改:

First, edit your existing method so that it simply returns the result of a call to the new method.

首先,编辑现有方法,以便它只返回对新方法的调用结果。

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

}

Second, add the new override with the following code.

其次,使用以下代码添加新覆盖。

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

    return result;
}

This code is very similar to k3b's code, but note that SQLiteDatabase.openOrCreateDatabase takes a String instead of a File, and I've used the version of it that allows for a DatabaseErrorHandler object.

此代码与k3b的代码非常相似,但请注意,SQLiteDatabase.openOrCreateDatabase采用String而不是File,并且我使用了允许DatabaseErrorHandler对象的版本。

#4


2  

user2371653's answere is very nice. but I found a issue:

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

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

this may cause crasd, if install your app at android 2.x

如果在Android 2.x上安装你的应用程序,这可能会导致crasd

so we can modify it like this

所以我们可以像这样修改它

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

because android 2.x does not has the api

因为android 2.x没有api

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

#5


1  

in my opinion I found a better solution on this side here (SQLite database on SD card) and wanted to inform you. Notice the entry in the constructor.

在我看来,我在这边找到了一个更好的解决方案(SD卡上的SQLite数据库),并想通知你。注意构造函数中的条目。

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

Quote from the user website:
"It will create the database in the app's folder on the sdcard: /sdcard/Android/data/[your_package_name]/files. In that way the database will be seen as part of the app by android and removed automatically if the user uninstalls the app."

从用户网站引用:“它将在sdcard上的应用程序文件夹中创建数据库:/ sdcard / Android / data / [your_package_name] / files。这样,数据库将被android视为应用程序的一部分并被删除如果用户卸载应用程序,则会自动执行。“

"I my app I have a large database and it will in most cases not fit on old phones internal memory, e.g. HTC Desire. It runs great on the sdcard, and most apps are "moved to sdcard" themselves anyway so don't worry about the database not being accessible, because the app won't be accessible it self."

“我的应用程序我有一个大型数据库,它在大多数情况下都不适合旧手机的内存,例如HTC Desire。它在SD卡上运行得很好,而且大多数应用程序都”自动转移到SD卡“所以不要担心关于数据库无法访问,因为该应用程序无法自行访问。“