Подтвердить что ты не робот

Проблема SQLiteOpenHelper с полным именем пути к базе данных

В моем приложении я использую...

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 людьми, и, как и я, они работают под управлением 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() для этого устройства.

Я вижу три возможных ответа...

  • Хотя допустимо в версии 2.2, указание полного пути для имени базы данных не рекомендуется и будет работать с более ранними версиями
  • Для хранения SD-карты допустимы полные пути, но путь "/nand" интерпретируется как "внутренний", и в этом случае допустимы только относительные пути.
  • Что-то еще, что мне не хватает полностью

Если применимо какое-либо или все вышеперечисленное, я был бы признателен, если бы кто-нибудь мог помочь с тем, как я должен подходить к этому.

Спасибо.

4b9b3361

Ответ 1

Исторически вы не могли использовать пути с SQLiteOpenHelper. Он работал только с простыми именами файлов. Я не понял, что они смягчили это ограничение в Android 2.2.

Если вы хотите использовать базы данных на SD-карте и хотите поддерживать Android 2.1 и более ранние версии, вы не можете использовать SQLiteOpenHelper.

К сожалению!

Ответ 2

Вы можете использовать SQLiteOpenHelper с настраиваемым путем, если вы предоставляете пользовательский ContextClass и если у вас есть доступ на запись в целевой каталог.

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:
 как это работает (@barry question):

Обычные приложения для Android имеют свои файлы локальной базы данных относительно папки приложения. Используя контекст клиента с перезаписанным getDatabasePath(), база данных теперь относится к другому каталогу на SD-карте.

Обновить февраль 2015:
После замены моего старого устройства Android-2.2 новым устройством Android-4.4 я узнал, что мое решение больше не работает. Благодаря ответу @damccull я смог это исправить. Я обновил этот ответ, так что это должен быть рабочий пример снова.

Обновление может 2017:

Статистика: этот aproach используется в более 200 проектов github

Ответ 3

Ответ на

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.

Ответ 4

user2371653 answere очень приятно. но я нашел проблему:

@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)

Ответ 5

на мой взгляд, я нашел лучшее решение на этой стороне здесь (база данных SQLite на SD-карте) и хотел сообщить вам. Обратите внимание на запись в конструкторе.

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-карту "в любом случае так, 't беспокоиться о том, что база данных недоступна, потому что приложение не будет доступно для нее самостоятельно.