Comment utiliser SQLiteOpenHelper avec base de données sur SD-card?

selon diverses réponses ici et dans l'application web extending et c'est la méthode héritée getDatabasePath() permettrait de définir le chemin de stockage de la base de données à partir de l'emplacement de la mémoire interne standard à une carte SD insérée de plus grande taille.

ça ne marche pas pour moi. La construction suggérée utilise toujours la base de données sur la mémoire interne. En fait, la méthode getDatabasePath() n'est jamais appelée par SQLiteOpenHelper.

je voudrais obtenir cela et de exécuter.

Voici ce que j'ai fait jusqu'à présent:

1.) Extension De L'Application:

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.) Ajout de l'Application étendue au Manifeste:

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

3.) L'extension et à l'aide de SQLiteOpenHelper:

public class MySqliteOpenHelper extends SQLiteOpenHelper {

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

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

4.) En utilisant le SQLiteOpenHelper étendu dans Mes activités de la manière habituelle:

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

je tiens à souligner que la classe des applications étendues fonctionne en général. Je peux voir cela parce que MyApplication.onCreate() est appelé. Mais MyApplication.getDatabasePath () n'est pas appelé.

Toute aide est très appréciée.

12
demandé sur Harald Wilhelm 2011-04-21 13:24:41

6 réponses

j'ai trouvé que je pouvais utiliser un chemin complet dans Android 2.2, mais dans 2.1 le contexte.la méthode openOrCreateDatabase () a créé une exception. Pour travailler autour de cela j'ai enveloppé cette méthode pour appeler SQLiteDatabase.openOrCreateDatabase () directement. Voici le constructeur de mon SQLOpenHelper étendu

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;
  }
}
11
répondu Roger Keays 2012-01-20 13:50:57

réécrire SQLOpenHelper pour utiliser le répertoire de carte SD plutôt que le contexte et étendre cela semble fonctionner pour moi.

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

cela a été fait lorsque la méthode décrite ci-dessus par Roger Keays a cessé de travailler sur Android 4.0.3.

5
répondu Kenneth Evans 2012-08-13 00:16:36

ce code a corrigé mon problème similaire, ma classe d'application:

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

j'Espère qu'il vous aidera.

3
répondu ydanneg 2011-12-05 12:48:19

Eh bien, je suppose que vous ne pouvez pas faire cela. Si quelqu'un connaît un moyen, dites-nous comment.

alors quand vous appelez

mySqliteOpenHelper.getReadableDatabase();

tout devrait être ok comme si nous regardions le mise en oeuvre nous voyons que:

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

tout va bien. Mais si nous prenons un oeil à quelques lignes:

return getWritableDatabase();

donc il appelle en fait une autre méthode, et si elle échoue, c'est seulement alors qu'il procède pour utiliser getDatabasePath().

Si nous regardons l' implémentation de getabledatabase - nous pouvons clairement voir qu'il n'utilise pas getDatabasePath mais plutôt:

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

ceci nous amène à voir comment openorcreatedabase est implémentée pour cela nous allons jeter un coup d'oeil à 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);
        }

nous pouvons donc voir que cette méthode d'aide validateFilePath retourne le fichier s'il obtient un chemin complet (comme /some/truly/full/path) ou s'il essaie de concattre getDatabasesDir() avec filename. mise en œuvre de getDatabasesDir() utilise getDataDirFile () qui est public et peut-être en théorie pourrait être écrasé .. mais vous aurez à vérifier.

Actuellement, je vois deux solutions:

1) Si vous n'avez pas besoin d'écrire force sqlite db en mode Lecture seule, getWritableDatabase échouera et getDatabasePath sera appelé

2) Passer dans le chemin complet dans le constructeur SQLiteOpenHelper, et s'assurer que db est writable, quelque chose comme:

public class MyDbOpenHelper extends SQLiteOpenHelper {

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

cela n'a vraiment aucun sens pour moi, mais en regardant les sources android (au moins 2.3.1), il semble que c'est la façon dont il est mis en œuvre.

3
répondu gerasalus 2012-01-07 01:07:21

appeler cette fonction va invoquer la méthode onCreate dans la classe helper SqliteOpen

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

La méthode oncreate est comme ceci

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

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

DATABASE_CREATE est la chaîne qui contient la requête pour créer la base de données

0
répondu droid kid 2011-04-21 09:36:07

votre base de données est conservée dans sa mémoire interne de sorte que les autres applications ne peuvent pas y accéder et modifier/corrompre des données.

le chemin par défaut de la base de données android est /data/data/data/ APPLICATIONPACKAGENAME / databases/. Ce qui suit est un très bon guide sur la façon de stocker votre base de données dans un fichier et puis le remplir à l'exécution.

Article

0
répondu Hades 2011-04-21 10:27:32