2011-04-21 51 views
12

根據這裏和Web擴展應用程序中的各種答案,它是繼承方法getDatabasePath()將允許將數據庫存儲路徑從標準內部存儲位置設置爲更大尺寸的插入SD卡。如何在SD卡上使用SQLiteOpenHelper與數據庫?

這不適合我。建議的結構仍在使用內部存儲器上的數據庫。事實上,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(); 
    } 
} 

我想指出的是,擴展應用程序類在一般營運。我可以看到這個,因爲MyApplication.onCreate()被調用。但不調用MyApplication.getDatabasePath()。

任何幫助,高度讚賞。

+0

在SD卡中保存一個普通的sqlite數據庫文件是不安全的。這裏是一個解決方案的鏈接,如何獲得加密的解決方案:https://github.com/sqlcipher/android-database-sqlcipher/issues/67 – 2013-09-23 09:01:56

回答

0

調用此函數將調用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是包含查詢用於創建數據庫

字符串
+0

我確實有很多數據庫處理內部內存。關鍵是,根據建議,上述步驟應該在外部存儲器(SD卡)上創建數據庫文件 - 並且這不起作用。不管怎麼說,還是要謝謝你。 – 2011-04-21 10:12:57

0

您的數據庫保存在其內部存儲器中,以便其他應用程序無法訪問它並更改/損壞數據。

android數據庫的默認路徑是/ data/data/APPLICATIONPACKAGENAME/databases /。以下是關於如何將數據庫存儲在文件中然後在運行時填充它的一個很好的指導。

Article

+0

必須是我的壞英語,每個人都試圖解釋如何使用數據庫或爲什麼我不應該在SD卡上存儲數據庫文件;-)我只是想知道如何在SD卡上創建數據庫。與此同時,我發現了這個問題,並找到了一種方法。我必須通過原始的SQLiteOpenHelper代碼閱讀,立即查看原因以及如何解決此問題。不管怎麼說,還是要謝謝你。 – 2011-04-21 10:46:12

+0

當您找到解決方案時,您可以將其作爲答案發布。我會對你如何解決你的問題感興趣。 Thx – Flo 2011-04-21 11:03:46

+1

getWritableDatabase()不調用getDatabasePath()。它只在getReadableDatabase()中調用。但getReadableDatabase()本身調用getWriteableDatabase(),如果調用成功,調用getDatabasePath()的部分從不使用。所以我現在做的是複製抽象類SQLiteOpenHelper並更改該行。是的,我知道後果,關於用戶需要單擊「確定」的安全風險,我一般希望繼續使用SQLiteOpenHelper。我不會將其作爲答案發布,因爲這不是我會向任何人推薦的解決方案。 – 2011-04-21 11:18:57

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

嗯,我想你不能那樣做。如果有人知道方法,請告訴我們如何。

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

所有好:

所以,當你調用

mySqliteOpenHelper.getReadableDatabase(); 

好像我們看implementation我們可以看到,它都應該沒問題。但是,如果我們看一看幾行了:

return getWritableDatabase(); 

所以它實際上是調用另一個方法,如果失敗,只是那麼procedes使用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); 
     } 

因此,我們可以看到,如果它得到一個完整路徑(如/一些/真正/全/路徑)或試圖與文件名Concat的getDatabasesDir()返回validateFilePath文件這個輔助方法。 getDatabasesDir()實現使用getDataDirFile(),它是公開的,理論上可能會被覆蓋..但你必須檢查。

目前我看到的兩個解決方案:

1)如果你不需要寫訪問力的SQLite數據庫爲只讀模式,getWritableDatabase將失敗,getDatabasePath將調用
2)的完整路徑傳遞到SQLiteOpenHelper的構造函數,並確保數據庫是可寫的,是這樣的:

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),似乎這是它的實現方式。

+0

謝謝。我在幾個年齡段的 – 2012-01-08 08:06:49

+0

幾個頁面(而不是年齡)評論中寫下了這一點。它運作完美。 – 2012-01-08 08:08:57

+0

謝謝你節省了我的時間..順利工作 – 2012-07-30 12:35:13

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; 
    } 
} 
+0

謝謝!這對我有效。 +1,因爲我必須向下滾動才能看到這個出色的答案。 (+1應該把它移開)。 – braden 2012-05-03 17:59:20

+0

我使用了這個解決方案,它一直工作到設備升級到Android 4.0.3。然後它開始使用內部存儲器,而不是SD卡。在跟蹤中,上面的openOrCreateDatabase方法是_not_調用的,在跟蹤到SQLiteDatabaseHelper時,它出現在錯誤的行上(在2.3.3和4.0.3中,它們有不同的代碼)使得很難看到發生了什麼。通過反覆鍵入F5,我可以訪問ContextImpl.openOrCreateDatabase(),但源代碼不可用。看起來像一個錯誤。底線是這種方法不再起作用。我寫了自己的SQLiteOpenHelper來修復它。 – 2012-08-12 23:48:19

+0

稍後:我爲我的SQLiteOpenHelper添加了代碼作爲答案。 – 2012-08-13 00:19:47

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上工作時完成的。

+0

感謝您的貢獻,但如果您想使用加密數據庫(https://github.com/sqlcipher/android-database-sqlcipher/issues/67),如果您保存在SD卡中更有意義,則不能用你的解決方案。你有沒有找到更優雅的解決方案? – 2013-09-22 23:04:53

+0

謝謝。這有很大幫助。 :) – Sufian 2013-11-09 02:48:29

+0

@GeorgePligor即使您將數據庫保存在手機內存中,用戶也可以使用根設備打開它。我想你可以使用[SecurePreferences](https://github.com/sveinungkb/encrypted-userprefs)進行加密。 – Sufian 2014-11-02 15:19:06