如何在sd-card上使用SQLiteOpenHelper和数据库?

时间:2011-04-21 09:24:42

标签: android sqlite sd-card

根据此处和Web扩展应用程序中的各种答案,它的继承方法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()。

非常感谢任何帮助。

6 个答案:

答案 0 :(得分:11)

我发现我可以在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;
  }
}

答案 1 :(得分:5)

重写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) {
    }
}

当Roger Keays上述方法停止在Android 4.0.3上工作时,就完成了这项工作。

答案 2 :(得分: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);
}

希望它会对你有所帮助。

答案 3 :(得分:3)

嗯,我猜你不能这样做。如果有人知道,请告诉我们如何。

所以当你打电话时

mySqliteOpenHelper.getReadableDatabase();

如果我们看到implementation,我们就会看到:

 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 / really / full / path)或尝试使用filename连接getDatabasesDir()。 getDatabasesDir()实现使用公共的getDataDirFile(),理论上可能会被覆盖..但你必须检查。

目前我看到两个解决方案:

1)如果你不需要写入访问强制sqlite db进入只读模式,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),这似乎就是它的实现方式。

答案 4 :(得分:0)

调用此函数将调用SqliteOpen助手类中的onCreate方法

    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是包含用于创建数据库

的查询的字符串

答案 5 :(得分:0)

您的数据库保存在其内部存储器中,以便其他应用程序无法访问它并更改/损坏数据。

android数据库的默认路径是/ data / data / APPLICATIONPACKAGENAME / databases /。以下是关于如何将数据库存储在文件中然后在运行时填充它的非常好的指南。

Article