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

Как использовать SQLiteOpenHelper с базой данных на SD-карте?

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

Это не работает для меня. Предлагаемая конструкция по-прежнему использует базу данных во внутренней памяти. На самом деле метод getDatabasePath() никогда не вызывается SQLiteOpenHelper.

Я хотел бы запустить это.

Вот что я сделал до сих пор:

1.) Расширение приложения:

public class MyApplication extends Application {

  @Override
  public File getDatabasePath(String name) {
    // Just a test
    File file = super.getDatabasePath(name);

    return file;
  }

  @Override
  public void onCreate() {
    // Just a test
    super.onCreate();
  }
}

2.) Добавление расширенного приложения в манифест:

<application
  ...
  android:name="MyApplication" 
  ... >

3.) Расширение и использование SQLiteOpenHelper:

public class MySqliteOpenHelper extends SQLiteOpenHelper {

  public void onCreate(SQLiteDatabase sqliteDatabase) {
    ...
  }

  @Override
  public void onUpgrade(SQLiteDatabase sqliteDatabase, int oldVersion, int newVersion) {
    ...
  }
}

4.) Используя расширенный SQLiteOpenHelper в моих действиях обычным способом:

public class MyActivity extends Activity {

  private MySqliteOpenHelper mySqliteOpenHelper;
  private SQLiteDatabase     sqliteDatabase;

  @Override
  public void onCreate(Bundle bundle) {
    super.onCreate(bundle);
    ...
    mySqliteOpenHelper = new MySqliteOpenHelper(getApplicationContext());
    sqliteDatabase = mySqliteOpenHelper.getReadableDatabase();
    ...
  }

  @Override
  protected void onDestroy() {
    if (mySqliteOpenHelper != null) {
      mySqliteOpenHelper.close();
      mySqliteOpenHelper = null;
    }

    super.onDestroy();
  }
}

Я хочу указать, что расширенный класс Application работает в целом. Я вижу это, потому что вызывается MyApplication.onCreate(). Но MyApplication.getDatabasePath() не вызывается.

Любая помощь приветствуется.

4b9b3361

Ответ 1

Я обнаружил, что могу использовать полный путь в Android 2.2, но в 2.1 метод Context.openOrCreateDatabase() создал исключение. Чтобы обойти это, я завернул этот метод, чтобы напрямую вызвать SQLiteDatabase.openOrCreateDatabase(). Вот конструктор моего расширенного SQLOpenHelper

public class Database extends SQLiteOpenHelper {
  public Database(Context context) {
    super(new ContextWrapper(context) {
        @Override public SQLiteDatabase openOrCreateDatabase(String name, 
                int mode, SQLiteDatabase.CursorFactory factory) {

            // allow database directory to be specified
            File dir = new File(DIR);
            if(!dir.exists()) {
                dir.mkdirs();
            }
            return SQLiteDatabase.openDatabase(DIR + "/" + NAME, null,
                SQLiteDatabase.CREATE_IF_NECESSARY);
        }
    }, NAME, null, VERSION);
    this.context = context;
  }
}

Ответ 2

Перезапись SQLOpenHelper для использования каталога SD-карты, а не контекста, а затем расширение, которое, похоже, работает для меня.

import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteException;
import android.util.Log;

/**
 * SDCardSQLiteOpenhelper is a class that is based on SQLiteOpenHelper except
 * that it does not use the context to get the database. It was written owing to
 * a bug in Android 4.0.3 so that using a ContextWrapper to override
 * openOrCreateDatabase, as was done with Android 2.3.3, no longer worked. <br>
 * <br>
 * The mContext field has been replaced by mDir. It does not use lock on the
 * database as that method is package private to
 * android.database.sqlite.SQLiteDatabase. Otherwise the implementation is
 * similar.<br>
 * <br>
 * 
 * @see android.database.sqlite.SQLiteOpenHelper
 */
public abstract class SDCardSQLiteOpenHelper {
    private static final String TAG = SDCardSQLiteOpenHelper.class
            .getSimpleName();

    // private final Context mContext;
    private final String mName;
    private final String mDir;
    private final CursorFactory mFactory;
    private final int mNewVersion;

    private SQLiteDatabase mDatabase = null;
    private boolean mIsInitializing = false;

    /**
     * Create a helper object to create, open, and/or manage a database. This
     * method always returns very quickly. The database is not actually created
     * or opened until one of {@link #getWritableDatabase} or
     * {@link #getReadableDatabase} is called.
     * 
     * @param dir
     *            the directory on the SD card. It must exist and the SD card
     *            must be available. The caller should check this.
     * @param name
     *            of the database file, or null for an in-memory database
     * @param factory
     *            to use for creating cursor objects, or null for the default
     * @param version
     *            number of the database (starting at 1); if the database is
     *            older, {@link #onUpgrade} will be used to upgrade the
     *            database; if the database is newer, {@link #onDowngrade} will
     *            be used to downgrade the database
     */
    public SDCardSQLiteOpenHelper(String dir, String name,
            CursorFactory factory, int version) {
        if (version < 1)
            throw new IllegalArgumentException("Version must be >= 1, was "
                    + version);
        // mContext = context;
        mDir = dir;
        mName = name;
        mFactory = factory;
        mNewVersion = version;
    }

    /**
     * Return the name of the SQLite database being opened, as given to the
     * constructor.
     */
    public String getDatabaseName() {
        return mName;
    }

    /**
     * Create and/or open a database that will be used for reading and writing.
     * The first time this is called, the database will be opened and
     * {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be
     * called.
     * 
     * <p>
     * Once opened successfully, the database is cached, so you can call this
     * method every time you need to write to the database. (Make sure to call
     * {@link #close} when you no longer need the database.) Errors such as bad
     * permissions or a full disk may cause this method to fail, but future
     * attempts may succeed if the problem is fixed.
     * </p>
     * 
     * <p class="caution">
     * Database upgrade may take a long time, you should not call this method
     * from the application main thread, including from
     * {@link android.content.ContentProvider#onCreate
     * ContentProvider.onCreate()}.
     * 
     * @throws SQLiteException
     *             if the database cannot be opened for writing
     * @return a read/write database object valid until {@link #close} is called
     */
    public synchronized SQLiteDatabase getWritableDatabase() {
        if (mDatabase != null) {
            if (!mDatabase.isOpen()) {
                // darn! the user closed the database by calling
                // mDatabase.close()
                mDatabase = null;
            } else if (!mDatabase.isReadOnly()) {
                return mDatabase; // The database is already open for business
            }
        }

        if (mIsInitializing) {
            throw new IllegalStateException(
                    "getWritableDatabase called recursively");
        }

        // If we have a read-only database open, someone could be using it
        // (though they shouldn't), which would cause a lock to be held on
        // the file, and our attempts to open the database read-write would
        // fail waiting for the file lock. To prevent that, we acquire the
        // lock on the read-only database, which shuts out other users.

        boolean success = false;
        SQLiteDatabase db = null;
        // NOT AVAILABLE
        // if (mDatabase != null) {
        // mDatabase.lock();
        // }
        try {
            mIsInitializing = true;
            if (mName == null) {
                db = SQLiteDatabase.create(null);
            } else {
                String path = mDir + "/" + mName;
                // db = mContext.openOrCreateDatabase(mName, 0, mFactory,
                // mErrorHandler);
                db = SQLiteDatabase.openDatabase(path, null,
                        SQLiteDatabase.CREATE_IF_NECESSARY);
            }

            int version = db.getVersion();
            if (version != mNewVersion) {
                db.beginTransaction();
                try {
                    if (version == 0) {
                        onCreate(db);
                    } else {
                        if (version > mNewVersion) {
                            onDowngrade(db, version, mNewVersion);
                        } else {
                            onUpgrade(db, version, mNewVersion);
                        }
                    }
                    db.setVersion(mNewVersion);
                    db.setTransactionSuccessful();
                } finally {
                    db.endTransaction();
                }
            }

            onOpen(db);
            success = true;
            return db;
        } finally {
            mIsInitializing = false;
            if (success) {
                if (mDatabase != null) {
                    try {
                        mDatabase.close();
                    } catch (Exception e) {
                        // Do nothing
                    }
                    // NOT AVAILABLE
                    // mDatabase.unlock();
                }
                mDatabase = db;
            } else {
                // NOT AVAILABLE
                // if (mDatabase != null) {
                // mDatabase.unlock();
                // }
                if (db != null)
                    db.close();
            }
        }
    }

    /**
     * Create and/or open a database. This will be the same object returned by
     * {@link #getWritableDatabase} unless some problem, such as a full disk,
     * requires the database to be opened read-only. In that case, a read-only
     * database object will be returned. If the problem is fixed, a future call
     * to {@link #getWritableDatabase} may succeed, in which case the read-only
     * database object will be closed and the read/write object will be returned
     * in the future.
     * 
     * <p class="caution">
     * Like {@link #getWritableDatabase}, this method may take a long time to
     * return, so you should not call it from the application main thread,
     * including from {@link android.content.ContentProvider#onCreate
     * ContentProvider.onCreate()}.
     * 
     * @throws SQLiteException
     *             if the database cannot be opened
     * @return a database object valid until {@link #getWritableDatabase} or
     *         {@link #close} is called.
     */
    public synchronized SQLiteDatabase getReadableDatabase() {
        if (mDatabase != null) {
            if (!mDatabase.isOpen()) {
                // darn! the user closed the database by calling
                // mDatabase.close()
                mDatabase = null;
            } else {
                return mDatabase; // The database is already open for business
            }
        }

        if (mIsInitializing) {
            throw new IllegalStateException(
                    "getReadableDatabase called recursively");
        }

        try {
            return getWritableDatabase();
        } catch (SQLiteException e) {
            if (mName == null)
                throw e; // Can't open a temp database read-only!
            Log.e(TAG, "Couldn't open " + mName
                    + " for writing (will try read-only):", e);
        }

        SQLiteDatabase db = null;
        try {
            mIsInitializing = true;
            // String path = mContext.getDatabasePath(mName).getPath();
            String path = mDir + "/" + mName;

            db = SQLiteDatabase.openDatabase(path, mFactory,
                    SQLiteDatabase.OPEN_READONLY);
            if (db.getVersion() != mNewVersion) {
                throw new SQLiteException(
                        "Can't upgrade read-only database from version "
                                + db.getVersion() + " to " + mNewVersion + ": "
                                + path);
            }

            onOpen(db);
            Log.w(TAG, "Opened " + mName + " in read-only mode");
            mDatabase = db;
            return mDatabase;
        } finally {
            mIsInitializing = false;
            if (db != null && db != mDatabase)
                db.close();
        }
    }

    /**
     * Close any open database object.
     */
    public synchronized void close() {
        if (mIsInitializing)
            throw new IllegalStateException("Closed during initialization");

        if (mDatabase != null && mDatabase.isOpen()) {
            mDatabase.close();
            mDatabase = null;
        }
    }

    /**
     * Called when the database is created for the first time. This is where the
     * creation of tables and the initial population of the tables should
     * happen.
     * 
     * @param db
     *            The database.
     */
    public abstract void onCreate(SQLiteDatabase db);

    /**
     * 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.
     * 
     * <p>
     * The SQLite ALTER TABLE documentation can be found <a
     * href="http://sqlite.org/lang_altertable.html">here</a>. If you add new
     * columns you can use ALTER TABLE to insert them into a live table. If you
     * rename or remove columns you can use ALTER TABLE to rename the old table,
     * then create the new table and then populate the new table with the
     * contents of the old table.
     * 
     * @param db
     *            The database.
     * @param oldVersion
     *            The old database version.
     * @param newVersion
     *            The new database version.
     */
    public abstract void onUpgrade(SQLiteDatabase db, int oldVersion,
            int newVersion);

    /**
     * Called when the database needs to be downgraded. This is stricly similar
     * to onUpgrade() method, but is called whenever current version is newer
     * than requested one. However, this method is not abstract, so it is not
     * mandatory for a customer to implement it. If not overridden, default
     * implementation will reject downgrade and throws SQLiteException
     * 
     * @param db
     *            The database.
     * @param oldVersion
     *            The old database version.
     * @param newVersion
     *            The new database version.
     */
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        throw new SQLiteException("Can't downgrade database from version "
                + oldVersion + " to " + newVersion);
    }

    /**
     * Called when the database has been opened. The implementation should check
     * {@link SQLiteDatabase#isReadOnly} before updating the database.
     * 
     * @param db
     *            The database.
     */
    public void onOpen(SQLiteDatabase db) {
    }
}

Это было сделано, когда метод, описанный выше Роджером Кейсом, прекратил работу над Android 4.0.3.

Ответ 3

Этот код исправил мою аналогичную проблему, мой класс приложения:

@Override
public File getDatabasePath(String name) {
    File result = new File(getExternalFilesDir(null), name);
    return result;
}

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

Надеюсь, это поможет вам.

Ответ 4

Ну, я думаю, вы не можете этого сделать. Если кто-то знает способ, сообщите нам, как.

Итак, когда вы вызываете

mySqliteOpenHelper.getReadableDatabase();

Все должно быть хорошо, если мы посмотрим на реализацию, мы видим, что:

 String path = mContext.getDatabasePath(mName).getPath();

Все хорошо. Но если взглянуть на несколько строк:

return getWritableDatabase();

Таким образом, он на самом деле вызывает другой метод, и если он терпит неудачу, только тогда он пытается использовать getDatabasePath().
Если мы посмотрим на реализацию getWritableDatabase - мы можем ясно видеть, что он не использует getDatabasePath, а вместо этого:

db = mContext.openOrCreateDatabase(mName, 0, mFactory);

Это позволяет нам увидеть, как реализована openOrCreateDatabase, для чего мы рассмотрим ContextImpl.java

 if (name.charAt(0) == File.separatorChar) {
            String dirPath = name.substring(0, name.lastIndexOf(File.separatorChar));
            dir = new File(dirPath);
            name = name.substring(name.lastIndexOf(File.separatorChar));
            f = new File(dir, name);
        } else {
            dir = getDatabasesDir();
            f = makeFilename(dir, name);
        }

Итак, мы можем видеть, что этот вспомогательный метод validateFilePath возвращает File, если он получает полный путь (например,/some/true/full/path) или пытается выполнить concat getDatabasesDir() с именем файла. Функция getDatabasesDir() использует getDataDirFile(), которая является общедоступной и, возможно, теоретически может быть перезаписана.. но вы должны были бы проверить.

В настоящее время я вижу два решения:

1) Если вам не нужна сила доступа для чтения sqlite db в режиме readonly, getWritableDatabase завершится с ошибкой, и getDatabasePath получит вызов
2) Передайте полный путь в конструктор SQLiteOpenHelper и убедитесь, что db доступен для записи, например:

public class MyDbOpenHelper extends SQLiteOpenHelper {

    public MyDbOpenHelper(final Context context) {
        super(context, Environment.getExternalStorageDirectory()
                + "/path/to/database/on/sdcard/database.sqlite", null, 1);
    }

Это действительно бессмысленно для меня, но, глядя на источники Android (как минимум 2.3.1), похоже, что так оно и реализуется.

Ответ 5

Вызов этой функции вызовет метод onCreate в вспомогательном классе SqliteOpen

    public dbOperation open() throws SQLException 
    {
        db = DBHelper.getWritableDatabase();
        return this;
    }

Метод oncreate похож на этот

       public void onCreate(SQLiteDatabase db) 
        {
            try {
                db.execSQL(DATABASE_CREATE);

            } catch (Exception e) {
                    e.printStackTrace();
            }
        }

DATABASE_CREATE - это строка, содержащая запрос на создание базы данных

Ответ 6

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

Путь по умолчанию для базы данных Android является /data/data/APPLICATIONPACKAGENAME/databases/. Ниже приведено довольно хорошее руководство по хранению базы данных в файле, а затем заполнению ее во время выполнения.

Статья