温馨提示:本文翻译自stackoverflow.com,查看原文请点击:database - Overwrite existing shipped Sqlite DB with the new DB version on my next Android App Update
android database sqlite version

database - 在我的下一个Android应用更新中,使用新的数据库版本覆盖现有的Sqlite数据库

发布于 2020-03-27 11:39:51

我想覆盖我的旧应用版本随附的现有数据库,并在下一个应用更新中覆盖新填充的数据库。但是,尽管我想在将DB_version传递给SQLiteOpenHelper类时尝试更改它,但从未调用过onUpgrade()。

public class DataBaseHelper extends SQLiteOpenHelper {

    private static Context mContext;
    private static String DB_PATH = "/data/data/<app Package>/databases/";;
    private static final String DBNAME = "DB.db";
    private static DataBaseHelper sInstance;
    private static final int version = 2;


    public static synchronized DataBaseHelper getInstance(Context context) {
        // Use the application context, which will ensure that you
        // don't accidentally leak an Activity's context.
        if (sInstance == null) {
            sInstance = new DataBaseHelper(context.getApplicationContext());
        }
        return sInstance;
    }

    public DataBaseHelper(Context context, String s){
        super(context, DBNAME, null, version);

    }

    private DataBaseHelper(Context context) {
        super(context, DBNAME, null, version);
        mContext = context;

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            DB_PATH = context.getApplicationInfo().dataDir + "/databases/";
        } else {
            DB_PATH = "/data/data/" + context.getPackageName() + "/databases/";
        }

        new Handler().post(new Runnable() {

            @Override
            public void run() {
                // If u want to Copy Database from Assets.
                try {
                    CopyAndCreateDataBase();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }


    // If database not exists copy it from the assets
    public void CopyAndCreateDataBase() throws IOException {
        boolean mDataBaseExist = checkDataBase();
        if (!mDataBaseExist) {
            this.getReadableDatabase();
            this.getWritableDatabase();
            this.close();
            try {
                // Copy the database from assests
                copyDataBase();
                String mPath = DB_PATH + DBNAME;
            } catch (IOException mIOException) {
                throw new Error("ErrorCopyingDataBase");
            }
        }
         //FOR DB TESTING PURPOSES
        else{
            Log.d("DB STATUS:", "Database Already Exists!!!!!!!!!");
        }
    }


    // Check that the database exists here: /data/data/yourpackage/databases/DatabaseName
    private boolean checkDataBase() {
        File dbFile = new File(DB_PATH + DBNAME);
        // Log.v("dbFile", dbFile + "   "+ dbFile.exists());
        return dbFile.exists();
    }

    // Copy the database from assets
    private void copyDataBase() throws IOException {
        InputStream mInput = mContext.getAssets().open(DBNAME);
        String outFileName = DB_PATH + DBNAME;
        OutputStream mOutput = new FileOutputStream(outFileName);
        byte[] mBuffer = new byte[1024];
        int mLength;
        while ((mLength = mInput.read(mBuffer)) > 0) {
            mOutput.write(mBuffer, 0, mLength);
        }
        Log.d("DB STATUS:", "Daatabase Created ----------------"); //FOR DB TESTING PURPOSES
        mOutput.flush();
        mOutput.close();
        mInput.close();
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {

    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
        Log.d("Database Versions:", "old:"+i+"\nnew:"+i1);

    }
}

我已经尝试了多种方法来触发onUpgrade()方法,例如调用getReadableDatabase()和getWritableDatabase(),但不幸的是它确实可以解决,我想用新发布的数据库覆盖现有的db。

查看更多

查看更多

提问者
Jawad AlZaabi
被浏览
100
MikeT 2019-07-04 08:49

I believe that your issue (initial issue, please read the entire answer) may be using the handler. Certainly testing the available code results in unanticipated results (database is always reported as existing so never copies the db from the assets, if the database exists never calls the onUpgrade (my guess is that the database is already open due to implicit opens and thus no attempt is made to do the open processing)).

Changing to use :-

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

    /*
    new Handler().post(new Runnable() {

        @Override
        public void run() {
            // If u want to Copy Database from Assets.
            try {
                CopyAndCreateDataBase();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    });
    */

Produces the anticipated results e.g. :-

07-04 06:55:38.836 4365-4365/aso.so56873021recopydb D/DBEXISTS: The database /data/user/0/aso.so56873021recopydb/databases/DB.db was found.
07-04 06:55:38.836 4365-4365/aso.so56873021recopydb D/DB STATUS:: Database Already Exists!!!!!!!!!
07-04 06:55:38.838 4365-4365/aso.so56873021recopydb D/Database Versions:: old:1
    new:2
  • Note the DBEXISTS message was added when testing the code to ascertain the issue.

The testing utilised the following code in an activity :-

public class MainActivity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DataBaseHelper mDBHlpr = DataBaseHelper.getInstance(this);
        Cursor csr = mDBHlpr.getWritableDatabase().query("sqlite_master",null,null,null,null,null,null);
        DatabaseUtils.dumpCursor(csr);
    }
}

Part 2 - Upgrading

In short there are issues in that even though you could call onUpgrade when the version is increased after fixing the previous issue as when onUpgrade is invoked the database has already been opened and copying the new database will effectively be undone as the old database will overwrite the newly copied database and thus the changes get lost.

Instead I suggest checking, copying and overwriting the database be actioned prior to instantiating the database helper e.g. in the getInstance method.

As such I'd suggest the following Database Helper (see comments for changes made) :-

public class DataBaseHelper extends SQLiteOpenHelper {

    private static Context mContext;
    //private static String DB_PATH = "/data/data/<app Package>/databases/"; //NOT NEEDED Contexts getDatabasePath used instead
    private static final String DBNAME = "DB.db";
    private static DataBaseHelper sInstance;
    private static final int version = 2;


    public static synchronized DataBaseHelper getInstance(Context context) {
        // Use the application context, which will ensure that you
        // don't accidentally leak an Activity's context.
        checkDB(context.getApplicationContext(),DBNAME,version); //<<<<<<<<<< do the stuff before DatabaseHelper instantiation
        if (sInstance == null) {
            sInstance = new DataBaseHelper(context.getApplicationContext());
        }
        return sInstance;
    }

    private DataBaseHelper(Context context) {
        super(context, DBNAME, null, version);
        mContext = context;
    }

    // Copy the database from assets
    //<<<<<<<<<< CHANGED TO USE getDatabasepath requiring Context to be passed
    private static void copyDataBase(Context context) throws IOException {
        InputStream mInput = context.getAssets().open(DBNAME);
        String outFileName = context.getDatabasePath(DBNAME).toString();
        OutputStream mOutput = new FileOutputStream(outFileName);
        Log.d("DBCOPY","DB Copy inititaed"); //<<<<<<<<<< ADDED FOR TESTING
        byte[] mBuffer = new byte[1024];
        int mLength;
        int bytescopied = 0;
        while ((mLength = mInput.read(mBuffer)) > 0) {
            mOutput.write(mBuffer, 0, mLength);
            bytescopied = bytescopied + mLength;
        }
        Log.d("DBCOPY", "Database copied. " + String.valueOf(bytescopied) + " bytes copied." ); //<<<<<<<<<< ADDED FOR TESTING
        Log.d("DB STATUS:", "Daatabase Created ----------------"); //FOR DB TESTING PURPOSES
        mOutput.flush();
        mOutput.close();
        mInput.close();
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {

    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
        //<<<<<<<<<< ONLY USE ME FOR SCHEMA CHANGES !!!!NOT!!!! new DB copy
        Log.d("Database Versions:", "old:"+i+"\nnew:"+i1);
    }

    //<<<<<<<<<< NEW 
    private static void checkDB(Context context, String dbname, int version) {
        int dbversion;
        // Note uses the Contexts getDatabasePath
        File dbfile = new File(context.getDatabasePath(dbname).toString());
        if (!dbfile.exists()) {
            //<<<<<<<<<< create the databases directory (or whatever it is the future)
            if (!dbfile.getParentFile().exists()) {
                dbfile.getParentFile().mkdirs();
            }
            //<<<<<<<<<< Now do the copy of the initial DB 
            //<<<<<<<<<< Note new install will copy the latest DB
            try {
                copyDataBase(context);
            } catch (IOException e) {
                e.printStackTrace();
                throw new Error("Error copying initial Database.");
            }
        } 

        // If the database file does exist then retrieve the version
        else {


            SQLiteDatabase db = SQLiteDatabase.openDatabase(context.getDatabasePath(dbname).toString(),null,SQLiteDatabase.OPEN_READWRITE);
            dbversion = db.getVersion();
            Log.d("DBVERSION","The stored database version is " + String.valueOf(dbversion));
            db.close();

            //<<<<<<<<<< if the database's version is less than the coded version then copy the DB
            //<<<<<<<<<< NOTE!!!! always assumes new version = new copy
            //<<<<<<<<<< IF NOt WANTED THEN DO SPECIFIC VERSION CHECKING 
            if (dbversion <  version) {

                //<<<<<<<<<<<< EXAMPLE skip copy on version 10 >>>>>>>>>>
                //<TODO> Remove example check if not needed
                if (dbversion < version && version == 10) {
                    return;
                }
                //<<<<<<<<<< For Android 9+ delete the -wal and -shm files if copying database
                if (new File(context.getDatabasePath(dbname).toString() + "-wal").exists()) {
                    new File(context.getDatabasePath(dbname).toString() + "-wal").delete();
                }
                if (new File(context.getDatabasePath(dbname).toString() + "-shm").exists()) {
                    new File(context.getDatabasePath(dbname).toString() + "-shm").delete();
                }
                try {
                    copyDataBase(context);
                } catch (IOException e) {
                    e.printStackTrace();
                    throw new Error("Error copying upgraded database");
                }
            }
        }
    }
}

Testing

The above has been tested. The testing was based upon two databases (entirely different) using the following in an activity :-

public class MainActivity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DataBaseHelper mDBHlpr = DataBaseHelper.getInstance(this);
        Cursor csr = mDBHlpr.getWritableDatabase().query("sqlite_master",null,null,null,null,null,null);
        DatabaseUtils.dumpCursor(csr);
    }
}

以下是资产文件夹:-

在此处输入图片说明

  • 原始数据库将OLDDB.db复制到DB.db。NEWDB.db将是后续的新数据库。

测试是在两种设备API 23和API 28(Oreo so Pie +和WAL)上进行的。

阶段1

首先,应用程序以上述文件运行,版本设置为1结果日志包含:-

07-04 10:20:32.120 8154-8154/aso.so56873021recopydb D/DBCOPY: DB Copy inititaed
07-04 10:20:32.120 8154-8154/aso.so56873021recopydb D/DBCOPY: Database copied. 36864 bytes copied.
07-04 10:20:32.120 8154-8154/aso.so56873021recopydb D/DB STATUS:: Daatabase Created ----------------
07-04 10:20:32.140 8154-8154/aso.so56873021recopydb I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@6e2c433
07-04 10:20:32.140 8154-8154/aso.so56873021recopydb I/System.out: 0 {
07-04 10:20:32.140 8154-8154/aso.so56873021recopydb I/System.out:    type=table
07-04 10:20:32.140 8154-8154/aso.so56873021recopydb I/System.out:    name=android_metadata
07-04 10:20:32.140 8154-8154/aso.so56873021recopydb I/System.out:    tbl_name=android_metadata
07-04 10:20:32.140 8154-8154/aso.so56873021recopydb I/System.out:    rootpage=3
07-04 10:20:32.140 8154-8154/aso.so56873021recopydb I/System.out:    sql=CREATE TABLE android_metadata (locale TEXT)
07-04 10:20:32.140 8154-8154/aso.so56873021recopydb I/System.out: }
07-04 10:20:32.140 8154-8154/aso.so56873021recopydb I/System.out: 1 {
07-04 10:20:32.140 8154-8154/aso.so56873021recopydb I/System.out:    type=table
07-04 10:20:32.140 8154-8154/aso.so56873021recopydb I/System.out:    name=room_master_table
07-04 10:20:32.140 8154-8154/aso.so56873021recopydb I/System.out:    tbl_name=room_master_table
07-04 10:20:32.140 8154-8154/aso.so56873021recopydb I/System.out:    rootpage=4
07-04 10:20:32.140 8154-8154/aso.so56873021recopydb I/System.out:    sql=CREATE TABLE room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)
07-04 10:20:32.140 8154-8154/aso.so56873021recopydb I/System.out: }
..........
  • 已复制36K
  • 笔记表room_master_table

API 28设备上的类似结果:-

2019-07-04 10:22:33.082 4532-4532/aso.so56873021recopydb D/DBCOPY: DB Copy inititaed
2019-07-04 10:22:33.083 4532-4532/aso.so56873021recopydb D/DBCOPY: Database copied. 36864 bytes copied.
2019-07-04 10:22:33.083 4532-4532/aso.so56873021recopydb D/DB STATUS:: Daatabase Created ----------------
2019-07-04 10:22:33.088 4532-4532/aso.so56873021recopydb I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@2f7f7bc
2019-07-04 10:22:33.089 4532-4532/aso.so56873021recopydb I/System.out: 0 {

第二阶段

应用程序再次运行没有更改结果(即没有数据库副本):-

07-04 10:24:13.642 8244-8244/? D/DBVERSION: The stored database version is 1
07-04 10:24:13.644 8244-8244/? I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@8c743f0

并在API 28设备上:

2019-07-04 10:26:24.531 4620-4620/? D/DBVERSION: The stored database version is 1
2019-07-04 10:26:24.536 4620-4620/? I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@877f345

第三阶段

资产文件夹中的文件更改为:-

在此处输入图片说明

应用无需更改版本即可重新运行:-

没有副本和原始数据:

07-04 10:30:01.838 8369-8369/? D/DBVERSION: The stored database version is 1
07-04 10:30:01.840 8369-8369/? I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@8c743f0
07-04 10:30:01.840 8369-8369/? I/System.out: 0 {
07-04 10:30:01.840 8369-8369/? I/System.out:    type=table
07-04 10:30:01.840 8369-8369/? I/System.out:    name=android_metadata
07-04 10:30:01.840 8369-8369/? I/System.out:    tbl_name=android_metadata
07-04 10:30:01.840 8369-8369/? I/System.out:    rootpage=3
07-04 10:30:01.840 8369-8369/? I/System.out:    sql=CREATE TABLE android_metadata (locale TEXT)
07-04 10:30:01.840 8369-8369/? I/System.out: }
07-04 10:30:01.840 8369-8369/? I/System.out: 1 {
07-04 10:30:01.840 8369-8369/? I/System.out:    type=table
07-04 10:30:01.840 8369-8369/? I/System.out:    name=room_master_table

并在API 28上:-

2019-07-04 10:31:11.591 4757-4757/aso.so56873021recopydb D/DBVERSION: The stored database version is 1
2019-07-04 10:31:11.596 4757-4757/aso.so56873021recopydb I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@877f345
2019-07-04 10:31:11.596 4757-4757/aso.so56873021recopydb I/System.out: 0 {
2019-07-04 10:31:11.596 4757-4757/aso.so56873021recopydb I/System.out:    type=table
2019-07-04 10:31:11.596 4757-4757/aso.so56873021recopydb I/System.out:    name=android_metadata
2019-07-04 10:31:11.596 4757-4757/aso.so56873021recopydb I/System.out:    tbl_name=android_metadata
2019-07-04 10:31:11.597 4757-4757/aso.so56873021recopydb I/System.out:    rootpage=3
2019-07-04 10:31:11.597 4757-4757/aso.so56873021recopydb I/System.out:    sql=CREATE TABLE android_metadata (locale TEXT)
2019-07-04 10:31:11.597 4757-4757/aso.so56873021recopydb I/System.out: }
2019-07-04 10:31:11.597 4757-4757/aso.so56873021recopydb I/System.out: 1 {
2019-07-04 10:31:11.597 4757-4757/aso.so56873021recopydb I/System.out:    type=table
2019-07-04 10:31:11.597 4757-4757/aso.so56873021recopydb I/System.out:    name=room_master_table

阶段4

version increased from 1 to 2 :-

07-04 10:38:42.679 8857-8857/? D/DBVERSION: The stored database version is 1
07-04 10:38:42.679 8857-8857/? D/DBCOPY: DB Copy inititaed
07-04 10:38:42.680 8857-8857/? D/DBCOPY: Database copied. 77824 bytes copied.
07-04 10:38:42.680 8857-8857/? D/DB STATUS:: Daatabase Created ----------------
07-04 10:38:42.683 8857-8857/? D/Database Versions:: old:1
    new:2
07-04 10:38:42.688 8857-8857/? I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@f2b3d69
07-04 10:38:42.688 8857-8857/? I/System.out: 0 {
07-04 10:38:42.688 8857-8857/? I/System.out:    type=table
07-04 10:38:42.688 8857-8857/? I/System.out:    name=android_metadata
07-04 10:38:42.688 8857-8857/? I/System.out:    tbl_name=android_metadata
07-04 10:38:42.688 8857-8857/? I/System.out:    rootpage=3
07-04 10:38:42.689 8857-8857/? I/System.out:    sql=CREATE TABLE android_metadata (locale TEXT)
07-04 10:38:42.689 8857-8857/? I/System.out: }
07-04 10:38:42.689 8857-8857/? I/System.out: 1 {
07-04 10:38:42.689 8857-8857/? I/System.out:    type=table
07-04 10:38:42.689 8857-8857/? I/System.out:    name=shops
07-04 10:38:42.689 8857-8857/? I/System.out:    tbl_name=shops
07-04 10:38:42.689 8857-8857/? I/System.out:    rootpage=4
07-04 10:38:42.689 8857-8857/? I/System.out:    sql=CREATE TABLE shops (_id INTEGER  PRIMARY KEY , shoporder INTEGER  DEFAULT 1000 , shopname TEXT , shopstreet TEXT , shopcity TEXT , shopstate TEXT , shopnotes TEXT )
07-04 10:38:42.689 8857-8857/? I/System.out: }
........
  • 注意复制了76K,现在可以购买表

并在API 28上:-

2019-07-04 10:40:31.799 5010-5010/aso.so56873021recopydb D/DBVERSION: The stored database version is 1
2019-07-04 10:40:31.800 5010-5010/aso.so56873021recopydb D/DBCOPY: DB Copy inititaed
2019-07-04 10:40:31.802 5010-5010/aso.so56873021recopydb D/DBCOPY: Database copied. 77824 bytes copied.
2019-07-04 10:40:31.802 5010-5010/aso.so56873021recopydb D/DB STATUS:: Daatabase Created ----------------
2019-07-04 10:40:31.826 5010-5010/aso.so56873021recopydb D/Database Versions:: old:1
    new:2
...........

阶段5

应用再次运行(即帖子复制已完成):-

07-04 10:41:53.653 8947-8947/? D/DBVERSION: The stored database version is 2
07-04 10:41:53.655 8947-8947/? I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@8c743f0
07-04 10:41:53.655 8947-8947/? I/System.out: 0 {
07-04 10:41:53.655 8947-8947/? I/System.out:    type=table
07-04 10:41:53.655 8947-8947/? I/System.out:    name=android_metadata
07-04 10:41:53.655 8947-8947/? I/System.out:    tbl_name=android_metadata
07-04 10:41:53.655 8947-8947/? I/System.out:    rootpage=3
07-04 10:41:53.655 8947-8947/? I/System.out:    sql=CREATE TABLE android_metadata (locale TEXT)
07-04 10:41:53.655 8947-8947/? I/System.out: }
07-04 10:41:53.655 8947-8947/? I/System.out: 1 {
07-04 10:41:53.655 8947-8947/? I/System.out:    type=table
07-04 10:41:53.655 8947-8947/? I/System.out:    name=shops
07-04 10:41:53.655 8947-8947/? I/System.out:    tbl_name=shops

并在API 28上:-

2019-07-04 10:42:41.296 5098-5098/aso.so56873021recopydb D/DBVERSION: The stored database version is 2
2019-07-04 10:42:41.306 5098-5098/aso.so56873021recopydb I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@877f345
2019-07-04 10:42:41.307 5098-5098/aso.so56873021recopydb I/System.out: 0 {
2019-07-04 10:42:41.308 5098-5098/aso.so56873021recopydb I/System.out:    type=table
2019-07-04 10:42:41.308 5098-5098/aso.so56873021recopydb I/System.out:    name=android_metadata
2019-07-04 10:42:41.308 5098-5098/aso.so56873021recopydb I/System.out:    tbl_name=android_metadata
2019-07-04 10:42:41.308 5098-5098/aso.so56873021recopydb I/System.out:    rootpage=3
2019-07-04 10:42:41.308 5098-5098/aso.so56873021recopydb I/System.out:    sql=CREATE TABLE android_metadata (locale TEXT)
2019-07-04 10:42:41.308 5098-5098/aso.so56873021recopydb I/System.out: }
2019-07-04 10:42:41.308 5098-5098/aso.so56873021recopydb I/System.out: 1 {
2019-07-04 10:42:41.308 5098-5098/aso.so56873021recopydb I/System.out:    type=table
2019-07-04 10:42:41.308 5098-5098/aso.so56873021recopydb I/System.out:    name=shops
2019-07-04 10:42:41.309 5098-5098/aso.so56873021recopydb I/System.out:    tbl_name=shops