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

时间:2011-03-16 21:55:43

标签: android

在我的应用中,我使用...

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

这很好,结果路径是......

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

我需要一个我想要存储在SD卡上的SQLite DB,所以我将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()填充它。

所以这就是问题 - 应用程序正在进行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)

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

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

  1. 虽然在v2.2上可以接受,但建议不要为DB名称指定完全限定的路径,并且在早期版本中会失败
  2. SD卡存储可接受完全限定路径,但“/ nand”路径被解释为“内部”,在这种情况下只接受相对路径
  3. 我完全遗漏的其他东西
  4. 如果上述任何一项或全部适用,如果有人可以帮我解决这个问题,我会很感激。

    感谢。

5 个答案:

答案 0 :(得分:40)

如果您提供自定义 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);
  }
}

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

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

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

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

2017年更新:

统计:此问题在more than 200 github projects

中使用

答案 1 :(得分:14)

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

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

抱歉!

答案 2 :(得分:9)

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采用String而不是File,并且我使用了允许DatabaseErrorHandler对象的版本。

答案 3 :(得分:2)

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

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

这可能会导致crasd,如果你的应用程序安装在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)

答案 4 :(得分:1)

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

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] / files。这样,数据库将被android视为应用程序的一部分,并在用户卸载时自动删除应用程序“。

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