繁体   English   中英

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

[英]How using SQLiteOpenHelper with database on sd-card?

根据此处和Web扩展应用程序中的各种答案,它继承的方法getDatabasePath()允许将数据库存储路径从标准内部存储器位置设置为更大尺寸的插入SD卡。

这不适合我。 建议的构造仍在内部存储器上使用数据库。 事实上,SQLiteOpenHelper永远不会调用方法getDatabasePath()。

我想让它运行起来。

这是我到目前为止所做的:

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()。

任何帮助都非常感谢。

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

重写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上工作时,就完成了这项工作。

这段代码修复了我的类似问题,我的应用程序类:

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

希望它会对你有所帮助。

好吧,我想你不能这样做。 如果有人知道方法,请告诉我们如何。

所以当你打电话的时候

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 / really / full / path)或尝试用文件名连接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),它似乎就是它的实现方式。

调用此函数将调用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是包含用于创建数据库的查询的字符串

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

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

文章

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM