每次我重新运行Android时,房间数据库都会重新填充,因此我在退出Android之前所做的任何更改都不会保存

我希望此数据在导入后永久保留,而不预先填充Room数据库 以下是我创建的代码以预填充

@Database(entities = {DeviceInfo.class, AddressInfo.class},version = 1)
public abstract class AppDataBase extends RoomDatabase {
    public abstract DeviceDao deviceDao();
    public abstract AddressDao addressDao();
    private static AppDataBase instance;
    public static synchronized AppDataBase getInstance(Context context){
        if(instance == null){
            instance = Room.databaseBuilder(context.getApplicationContext(), AppDataBase.class,"device.db")
                .createFromAsset("device.db")
                .fallbackToDestructiveMigration()
                .build();
        }

        return instance;
    }
}

以下是更新代码

    @Update
    public void updateAddressInfo(AddressInfo addressInfo);

推荐答案

我相信删除.fallbackToDestructiveMigration()将解决您的问题;另一种方法是确保将预填充数据库的USER_VERSION设置为编码的数据库版本.

  • note only a coded version 1 has been tested. (see below)

然而,实际上不应该有这种需要,因为只有当数据库版本与编码版本不同并且数据库的版本应该设置为编码版本时,才应该调用销毁.

  • i.e. this may well be a bug

一些基本的测试似乎突出了很可能是一个漏洞.也就是说,如果预填充版本设置为0(使用PRAGMA user_version = 0;),则复制的数据库看起来会被销毁并再次复制.然而,如果预填充的数据库的版本(PRAGMA user_version = 1),则该数据库持续存在.


Demonstration


首先创建了一些基本的@Entity个带注释的类,如下所示:

@Entity
class DeviceInfo {
    @PrimaryKey
    Long id=null;
    String name;
    public void setId(Long id) {
        this.id = id;
    }
    public Long getId() {
        return id;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

及:-

@Entity
class AddressInfo {
    @PrimaryKey
    Long id=null;
    String name;
    public void setId(Long id) {
        this.id = id;
    }
    public Long getId() {
        return id;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

还有约@Dao个带注释的接口:-

@Dao
interface DeviceDao {
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    long insert(DeviceInfo deviceInfo);
    @Query("SELECT * FROM deviceinfo")
    List<DeviceInfo> getAllDeviceInfoRows();
}

及:-

@Dao
interface AddressDao {
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    long insert(AddressInfo addressInfo);
    @Query("SELECT * FROM addressinfo")
    List<AddressInfo> getAllAddressInfoRows();
    @Update
    public void updateAddressInfo(AddressInfo addressInfo);
}

然后修改(以添加回调)@Database注释的AppDataBase类:-

@Database(entities = {DeviceInfo.class, AddressInfo.class},version = 1)
public abstract class AppDataBase extends RoomDatabase {
    public abstract DeviceDao deviceDao();
    public abstract AddressDao addressDao();
    private static AppDataBase instance;
    public static synchronized AppDataBase getInstance(Context context){
        if(instance == null){
            instance = Room.databaseBuilder(context.getApplicationContext(), AppDataBase.class,"device.db")
                    .createFromAsset("device.db")
                    .fallbackToDestructiveMigration()
                    .addCallback(new Callback() {
                        @Override
                        public void onCreate(@NonNull SupportSQLiteDatabase db) {
                            super.onCreate(db);
                            Log.d(MainActivity.TAG,"ONCREATE INVOKED.");
                        }

                        @Override
                        public void onDestructiveMigration(@NonNull SupportSQLiteDatabase db) {
                            super.onDestructiveMigration(db);
                            Log.d(MainActivity.TAG,"ONDESTRUCTIVEMIGRATION INVOKED DBVERSION=" + db.getVersion());
                        }

                        @Override
                        public void onOpen(@NonNull SupportSQLiteDatabase db) {
                            super.onOpen(db);
                            Log.d(MainActivity.TAG,"ONOPEN INVOKED");
                        }
                    })
                    .allowMainThreadQueries() /* added  for brevity */
                    .build();
        }
        return instance;
    }
}
  • 请注意,添加.allowMainThreadQueries是为了利用主线程,从而减少所需的代码.

使用Navicat时,使用以下SQL创建了2个数据库文件,其中一个的User_vesrion为0,另一个为1,使用:-

DROP TABLE IF EXISTS `DeviceInfo`;
DROP TABLE IF EXISTS `AddressInfo`; 
CREATE TABLE IF NOT EXISTS `DeviceInfo` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`));
CREATE TABLE IF NOT EXISTS `AddressInfo` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`));
INSERT OR IGNORE INTO `DeviceInfo` VALUES (100,'PPDEVICE001'),(null,'PPDEVICE002');
INSERT OR IGNORE INTO `AddressInfo` VALUES (100,'PPADDRESS001'),(null,'PPADDRESS002');
PRAGMA user_version = 0;
PRAGMA user_version;
  • 显然,对于第二个文件,Pragma USER_VERSION=1.

The files were copied into the assets folder e.g. :- enter image description here

  • 根据正在进行的测试,从相应的其他文件中删除和复制device.db个文件.

最后是一些测试代码:

public class MainActivity extends AppCompatActivity {
    public static final String TAG = "DBINFO";

    AppDataBase db;
    AddressDao addressDao;
    DeviceDao deviceDao;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        db = AppDataBase.getInstance(this);
        deviceDao = db.deviceDao();
        addressDao = db.addressDao();
        getAll("_S1", db);
        for (AddressInfo ai: addressDao.getAllAddressInfoRows()) {
            ai.setName(ai.getName() + ai.getName());
            addressDao.updateAddressInfo(ai);
        }
        getAll("S2", db);

    }

    void getAll(String tag_suffix, AppDataBase adb) {
        logDBInfo(tag_suffix, adb);
        logDevices(tag_suffix);
        logAddresses(tag_suffix);
    }
    void logDBInfo(String tag_suffix,AppDataBase adb) {
        SupportSQLiteDatabase sdb = adb.getOpenHelper().getWritableDatabase();
        Log.d(TAG + tag_suffix, "DB VERSION is " + sdb.getVersion() + "DB PATH is " + sdb.getPath());
    }

    void logDevices(String tag_suffix) {
        for(DeviceInfo di: deviceDao.getAllDeviceInfoRows()) {
            Log.d(TAG + tag_suffix,"Device ID is " + di.getId() + " NAME is " + di.getName());
        }
    }
    void logAddresses(String tag_suffix) {
        for(AddressInfo ai: addressDao.getAllAddressInfoRows()) {
            Log.d(TAG + tag_suffix,"Address ID is " + ai.getId() + " NAME is " + ai.getName());
        }
    }
}

TESTING

100

最初,device_userversion0.db被复制为device.db.该应用程序卸载后运行(即全新安装).导致日志(log)显示:-

2023-12-20 15:58:01.204 D/DBINFO: ONCREATE INVOKED.
2023-12-20 15:58:01.211 D/DBINFO: ONOPEN INVOKED
2023-12-20 15:58:01.214 D/DBINFO_S1: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
2023-12-20 15:58:01.219 D/DBINFO_S1: Device ID is 100 NAME is PPDEVICE001
2023-12-20 15:58:01.219 D/DBINFO_S1: Device ID is 101 NAME is PPDEVICE002
2023-12-20 15:58:01.220 D/DBINFO_S1: Address ID is 100 NAME is PPADDRESS001
2023-12-20 15:58:01.220 D/DBINFO_S1: Address ID is 101 NAME is PPADDRESS002
2023-12-20 15:58:01.224 D/DBINFOS2: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
2023-12-20 15:58:01.229 D/DBINFOS2: Device ID is 100 NAME is PPDEVICE001
2023-12-20 15:58:01.229 D/DBINFOS2: Device ID is 101 NAME is PPDEVICE002
2023-12-20 15:58:01.231 D/DBINFOS2: Address ID is 100 NAME is PPADDRESS001PPADDRESS001
2023-12-20 15:58:01.231 D/DBINFOS2: Address ID is 101 NAME is PPADDRESS002PPADDRESS002
  • 结果正如预期的那样,数据库已经填充,然后更新了2个地址行(名称现在是原始名称重复).

100

应用程序只需重新运行,输出:-

2023-12-20 16:01:11.520 D/DBINFO: ONCREATE INVOKED.
2023-12-20 16:01:11.527 D/DBINFO: ONOPEN INVOKED
2023-12-20 16:01:11.530 D/DBINFO_S1: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
2023-12-20 16:01:11.533 D/DBINFO_S1: Device ID is 100 NAME is PPDEVICE001
2023-12-20 16:01:11.533 D/DBINFO_S1: Device ID is 101 NAME is PPDEVICE002
2023-12-20 16:01:11.534 D/DBINFO_S1: Address ID is 100 NAME is PPADDRESS001
2023-12-20 16:01:11.534 D/DBINFO_S1: Address ID is 101 NAME is PPADDRESS002
2023-12-20 16:01:11.538 D/DBINFOS2: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
2023-12-20 16:01:11.539 D/DBINFOS2: Device ID is 100 NAME is PPDEVICE001
2023-12-20 16:01:11.539 D/DBINFOS2: Device ID is 101 NAME is PPDEVICE002
2023-12-20 16:01:11.542 D/DBINFOS2: Address ID is 100 NAME is PPADDRESS001PPADDRESS001
2023-12-20 16:01:11.542 D/DBINFOS2: Address ID is 101 NAME is PPADDRESS002PPADDRESS002
  • 即,除了实际运行时间之外,它是相同的,因此更新的数据没有被保留(原本预计地址名称会更长).

100

The line .fallbackToDestructiveMigration has been commented out and the App rerun. enter image description here

这次的输出是:-

2023-12-20 16:05:31.133 D/DBINFO: ONOPEN INVOKED
2023-12-20 16:05:31.136 D/DBINFO_S1: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
2023-12-20 16:05:31.140 D/DBINFO_S1: Device ID is 100 NAME is PPDEVICE001
2023-12-20 16:05:31.140 D/DBINFO_S1: Device ID is 101 NAME is PPDEVICE002
2023-12-20 16:05:31.142 D/DBINFO_S1: Address ID is 100 NAME is PPADDRESS001PPADDRESS001
2023-12-20 16:05:31.142 D/DBINFO_S1: Address ID is 101 NAME is PPADDRESS002PPADDRESS002
2023-12-20 16:05:31.164 D/DBINFOS2: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
2023-12-20 16:05:31.166 D/DBINFOS2: Device ID is 100 NAME is PPDEVICE001
2023-12-20 16:05:31.167 D/DBINFOS2: Device ID is 101 NAME is PPDEVICE002
2023-12-20 16:05:31.169 D/DBINFOS2: Address ID is 100 NAME is PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001
2023-12-20 16:05:31.169 D/DBINFOS2: Address ID is 101 NAME is PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002
  • 即数据是持久存在的,而不是被替换的(地址名称现在是原始名称的4次出现).

101应用程序被卸载并运行3次,其中.fallbackToDestructiveMigration个被注释掉.这次的地址行是:-

D/DBINFOS2: Address ID is 100 NAME is PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001
D/DBINFOS2: Address ID is 101 NAME is PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002
  • 即8个原始名称(如预期)

** 所以在这个阶段不包括.fallback....个解决问题的方法(bug).

Test5 The App is uninstalled and the device.db file is deleted and copied from device_userversion1.db and .fallbackToDestructiveMigration is reinstated as per:- enter image description here

第一次运行的输出为:-

2023-12-20 16:16:23.257 D/DBINFO: ONOPEN INVOKED
2023-12-20 16:16:23.260 D/DBINFO_S1: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
2023-12-20 16:16:23.264 D/DBINFO_S1: Device ID is 100 NAME is PPDEVICE001
2023-12-20 16:16:23.264 D/DBINFO_S1: Device ID is 101 NAME is PPDEVICE002
2023-12-20 16:16:23.266 D/DBINFO_S1: Address ID is 100 NAME is PPADDRESS001
2023-12-20 16:16:23.266 D/DBINFO_S1: Address ID is 101 NAME is PPADDRESS002
2023-12-20 16:16:23.275 D/DBINFOS2: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
2023-12-20 16:16:23.279 D/DBINFOS2: Device ID is 100 NAME is PPDEVICE001
2023-12-20 16:16:23.279 D/DBINFOS2: Device ID is 101 NAME is PPDEVICE002
2023-12-20 16:16:23.283 D/DBINFOS2: Address ID is 100 NAME is PPADDRESS001PPADDRESS001
2023-12-20 16:16:23.283 D/DBINFOS2: Address ID is 101 NAME is PPADDRESS002PPADDRESS002
  • Note that ONCREATE is not called???, whilst previously it was on the first run

100重新运行应用程序,输出:-

2023-12-20 16:19:39.703 D/DBINFO: ONOPEN INVOKED
2023-12-20 16:19:39.705 D/DBINFO_S1: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
2023-12-20 16:19:39.709 D/DBINFO_S1: Device ID is 100 NAME is PPDEVICE001
2023-12-20 16:19:39.709 D/DBINFO_S1: Device ID is 101 NAME is PPDEVICE002
2023-12-20 16:19:39.712 D/DBINFO_S1: Address ID is 100 NAME is PPADDRESS001PPADDRESS001
2023-12-20 16:19:39.712 D/DBINFO_S1: Address ID is 101 NAME is PPADDRESS002PPADDRESS002
2023-12-20 16:19:39.728 D/DBINFOS2: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
2023-12-20 16:19:39.730 D/DBINFOS2: Device ID is 100 NAME is PPDEVICE001
2023-12-20 16:19:39.730 D/DBINFOS2: Device ID is 101 NAME is PPDEVICE002
2023-12-20 16:19:39.731 D/DBINFOS2: Address ID is 100 NAME is PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001
2023-12-20 16:19:39.731 D/DBINFOS2: Address ID is 101 NAME is PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002
  • 数据已按预期更新,而不是重新创建.

100第三次跑:-

2023-12-20 16:21:52.877 D/DBINFO: ONOPEN INVOKED
2023-12-20 16:21:52.879 D/DBINFO_S1: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
2023-12-20 16:21:52.883 D/DBINFO_S1: Device ID is 100 NAME is PPDEVICE001
2023-12-20 16:21:52.883 D/DBINFO_S1: Device ID is 101 NAME is PPDEVICE002
2023-12-20 16:21:52.886 D/DBINFO_S1: Address ID is 100 NAME is PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001
2023-12-20 16:21:52.886 D/DBINFO_S1: Address ID is 101 NAME is PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002
2023-12-20 16:21:52.901 D/DBINFOS2: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
2023-12-20 16:21:52.904 D/DBINFOS2: Device ID is 100 NAME is PPDEVICE001
2023-12-20 16:21:52.904 D/DBINFOS2: Device ID is 101 NAME is PPDEVICE002
2023-12-20 16:21:52.905 D/DBINFOS2: Address ID is 100 NAME is PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001
2023-12-20 16:21:52.905 D/DBINFOS2: Address ID is 101 NAME is PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002
  • Again as expected. So setting the user_version to 1 results in the anticipated results.这就是为什么一个bug被怀疑是(the version of the pre-populated database should not influence runs after the database has been populated).

Android相关问答推荐

Android使用参数编写齐射后请求

derivedStateOf与使用key和MutableState记住

合成-删除图像的部分背景

我到底应该如何分享我的应用程序中的图片?

为什么它显示我的空白屏幕?

Kotlin - 在继续之前如何等待这个协程完成?

解决失败:Landroidx/compose/runtime/PrimitiveSnapshotStateKt

警告:应用必须面向 Android 13(API 级别 33)或更高版本.确实如此

Spinner - onItemLongClick 从未执行

如何在Android Studio中禁用文件中的Github用户名引用?

Android - 如何使 TextInputEditText 的高度恰好为 2 行?

Jetpack Compose with Paging 3 发出太多网络请求

从 HiltViewModel @Injection 访问 Application()

在 react native 中设置 react-native-paper 组件的样式

MediumTopAppBar Material3 只更改大标题

如何将设备屏幕位置转换为发送事件位置?

如何在jetpack compose中创建自定义rememberState?

如何使用 Kotlin 在 Android Wear(Galaxy watch 4)中继续在后台运行应用程序

为什么使用 React Native 和 expo 创建的 APK 体积这么大?

如何在 Kotlin 的片段中制作图像列表?