2011-04-21 55 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