Question

As the database in my app grows, it is going to require more and more of the internal phone space. There isn't any sensitive/private data in the DB, so I'm interested in moving it to the SD card.

I'm using SQLiteOpenHelper to assist with the database work. It's my understanding that you can't use this for DB-access on the SD card as you can't define the DB path. However, there are some (very poor) examples on the Internet that suggest you can override this limitation. However I've never gotten one of those code samples to compile.

Is it possible? And if so--HOW! Please note that Froyo's "apps on SD card" feature won't work as it doesn't move internal files.

Was it helpful?

Solution

Just use:

SQLiteDatabase.openDatabase(DB_FULL_PATH, null, SQLiteDatabase.OPEN_READONLY);

where DB_FULL_PATH can be a path to your sdcard, like /sdcard/mydatabase.db

Edit:

This is what I call in my application to access the database....

private static DBUtil dbHelper = null;

public void openDatabase() {
    if(dbHelper == null) {
        dbHelper = new DBUtil(this.context);
        dbHelper.openDataBase(SQLiteDatabase.OPEN_READWRITE);
    }
}
public void closeDatabase() {
    if(dbHelper != null) {
        dbHelper.close();
        dbHelper = null;
    }
}

... and this is the db helper class I'm using, which in fact extends SQLiteOpenHelper, so you will still have everything you want from this class.

package com.myapp.android.db;

import android.content.Context;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import com.myapp.android.MyApp;

import java.io.IOException;

/**
 * Standard database utility class.
 * 
 * TODO: Refactor.
 */
public class DBUtil extends SQLiteOpenHelper {

    /**
     * Database directory.
     * 
     * <p>
     * Example: "/sdcard/myapp/db/"
     * </p>
     */
    public static String DB_DIRECTORY = null;

    /**
     * Name of the database file.
     * 
     * <p>
     * Example: "mydatabase.db"
     * </p>
     * 
     */
    public static String DB_NAME = null;

    /**
     * Full absolute path of the database.
     * 
     * <p>
     * Example: "/sdcard/myapp/db/mydatabase.db"
     * </p>
     */
    public static String DB_FULL_PATH = null;
    static {
        DB_DIRECTORY = MyApp.DATA_REPOSITORY_URI + "/myapp/db/";
        DB_NAME = "mydatabase.db";
        DB_FULL_PATH = DB_DIRECTORY + DB_NAME;
    }

    private SQLiteDatabase myDataBase;

    /**
     * Constructor Takes and keeps a reference of the passed context in order to
     * access to the application assets and resources.
     * 
     * @param context
     */
    public DBUtil(Context context) {
        super(context, DB_NAME, null, 1);

        try {
            this.createDataBase();
        } catch (IOException ioe) {
            throw new Error("Unable to create database");
        }
    }

    /**
     * Creates a empty database on the system and rewrites it with your own
     * database.
     * */
    public void createDataBase() throws IOException {        
        if (!checkDataBase()) this.getWritableDatabase();
    }

    /**
     * Check if the database already exist to avoid re-copying the file each
     * time you open the application.
     * 
     * @return true if it exists, false if it doesn't
     */
    private boolean checkDataBase() {
        SQLiteDatabase checkDB = null;
        try {
            checkDB = SQLiteDatabase.openDatabase(DB_FULL_PATH, null,
                    SQLiteDatabase.OPEN_READONLY);
        } catch (SQLiteException e) {
            // database does't exist yet.
        }
        if (checkDB != null) {
            checkDB.close();
        }
        return checkDB != null ? true : false;
    }

    public void openDataBase(int mode) throws SQLException {        
        try {
            myDataBase = SQLiteDatabase.openDatabase(DB_FULL_PATH, null, mode);
        } catch(IllegalStateException e) {
            // Sometimes, esp. after application upgrade, the database will be non-closed, raising a IllegalStateException
            // below. Try to avoid by simply opening it again.
            Log.d(MyApp.APP, "Database non-closed. Reopening.");
            myDataBase = SQLiteDatabase.openDatabase(DB_FULL_PATH, null, mode);
        }
    }

    public void openDataBase() throws SQLException {
        openDataBase(SQLiteDatabase.OPEN_READWRITE);
    }

    public SQLiteDatabase getDb() {
        return myDataBase;
    }

    @Override
    public synchronized void close() {
        if (myDataBase != null)
            myDataBase.close();
        super.close();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
    }

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

}

OTHER TIPS

I found I could use a full path in Android 2.2, but in 2.1 the Context.openOrCreateDatabase() method threw an exception. To work around this I wrapped that method to call SQLiteDatabase.openOrCreateDatabase() directly. Here is the constructor for my extended 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;
  }
}
    String dbPath = DATABASE_NAME;
    File sdcard = Environment.getExternalStorageDirectory();
    if (sdcard != null && sdcard.canWrite()){
        dbPath = sdcard.getAbsolutePath() + "/mypath/onsdcard/" + DATABASE_NAME;
    }
    else {
        dbPath = DATABASE_NAME;
    }
    mDBHelper  = new WorkoutDBOpenHelper(context, dbPath);
    if(null != mDBHelper)
        mDB = mDBHelper.getWritableDatabase();

For me this works, and WorkoutDBOpenHelper extends SQLiteOpenHelper and its constructor simply calls super for SQLiteOpenHelper.

WorkoutDBOpenHelper(Context context, String dbPath) {
    super(context, dbPath, null, DATABASE_VERSION);

}

Please note that SQLiteopenHelper creates the database on the memory card too. However, on app uninstall, the DB will not be deleted from the sdcard.

This is not the answer for how to move an existing internal DB to SDCard, but this way you can choose one option at the creation time. I am working on moving the already existing database from app's "data" directory to sdcard, but there is no direct way. Will update once I figure out something.

This will be ok. I thought

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Log.i("DATABASE EXIST : ", ""+checkDataBase());
    if(!checkDataBase())
        copyDataBase();

    DatabaseHandler dbhandler = new DatabaseHandler(MainActivity.this);
    Cursor cursor = dbhandler.getAllContacts();

    ListView list = (ListView) findViewById(R.id.datalist);
    CustomCursorAdapter cursoradapter = new CustomCursorAdapter(MainActivity.this, cursor);
    list.setAdapter(cursoradapter);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    // Handle action bar item clicks here. The action bar will
    // automatically handle clicks on the Home/Up button, so long
    // as you specify a parent activity in AndroidManifest.xml.
    int id = item.getItemId();
    if (id == R.id.action_settings) {
        return true;
    }
    return super.onOptionsItemSelected(item);
}

private void copyDataBase()
{
    ContextWrapper cw =new ContextWrapper(getApplicationContext());
    String DB_PATH = "/data/data/com.example.copydatabase/databases/";
    String DB_NAME = "testing";

    Log.i("Database", "New database is being copied to device!");
    byte[] buffer = new byte[1024];
    OutputStream myOutput = null;
    int length;
    // Open your local db as the input stream
    InputStream myInput = null;
    try
    {
        myInput = MainActivity.this.getAssets().open(DB_NAME);
        // transfer bytes from the inputfile to the
        // outputfile
        myOutput =new FileOutputStream(DB_PATH+ DB_NAME);
        while((length = myInput.read(buffer)) > 0)
        {
            myOutput.write(buffer, 0, length);
        }
        myOutput.close();
        myOutput.flush();
        myInput.close();
        Log.i("Database", "New database has been copied to device!");

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

public boolean checkDataBase()
{
    String DB_PATH = "/data/data/com.example.copydatabase/databases/";
    String DB_NAME = "testing";
    File dbFile = new File(DB_PATH + DB_NAME);
    return dbFile.exists();
}
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top