[英]android - Check if the room database is populated on startup without Livedata
我对 Android 还是很陌生,我想在我的应用程序中有一个数据库。 我被介绍到 Room 文档说这是在 android 中实现数据库的最佳方式。
现在我必须在数据库中预先填充一些数据,并确保在应用程序启动之前填充它。
我看到有很多东西,比如LiveData
、 Repositories
、 ViewModels
和MediatorLiveData
。
但我只想保持简单明了,不使用上述内容如何在应用程序启动之前找到数据库是否已填充。
我收到了大量NullPointerExceptions
。
我正在使用onCreateCallback()
来填充数据库,但是当我尝试从数据库中获取项目时,它会产生NullPointerException
,一段时间后它可能会也可能不会产生相同的警告,问题仍然是什么是最好的方法知道何时完全填充数据库。
这是一个最小的例子
public class MainActivity extends AppCompatActivity {
private TextView nameView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nameView = findViewById(R.id.name);
new NamesAsyncTask().execute();
}
private class NamesAsyncTask extends AsyncTask<Void,Void,String> {
private NameDao mNameDao;
@Override
public String doInBackground(Void... params) {
NameDatabase db = NameDatabase.getDatabase(MainActivity.this);
mNameDao = db.nameDao();
String name = mNameDao.getNameByName("Body").name;
return name;
}
@Override
public void onPostExecute(String name) {
nameView.setText(name);
}
}
}
实体
@Entity(tableName = "name")
public class Name {
@NonNull
@PrimaryKey(autoGenerate = true)
public Integer id;
@NonNull
@ColumnInfo(name = "name")
public String name ;
public Name(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return this.id;
}
public void setId(Integer id ) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
道
@Dao
public interface NameDao {
@Insert
void insertAll(List<Name> names);
@Query("SELECT * from name")
List<Name> getAllNames();
@Query("DELETE FROM name")
void deleteAll();
@Query("SELECT * FROM name WHERE name = :name LIMIT 1")
Name getNameByName(String name);
@Query("SELECT * FROM name WHERE id = :id LIMIT 1")
Name getNameById(int id);
}
数据库
@Database(entities = {Name.class}, version = 1)
public abstract class NameDatabase extends RoomDatabase {
public abstract NameDao nameDao();
private static NameDatabase INSTANCE;
public boolean setDatabaseCreated = false;
public static NameDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (NameDatabase.class) {
if (INSTANCE == null) {
INSTANCE = buildDatabase(context);
INSTANCE.updateDatabaseCreated(context);
}
}
}
return INSTANCE;
}
private static NameDatabase buildDatabase(final Context appContext) {
return Room.databaseBuilder(appContext, NameDatabase.class,
"name_database").addCallback(new Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
Executors.newSingleThreadScheduledExecutor().execute(() -> {
// Add Delay to stimulate a long running opeartion
addDelay();
// Generate the data for pre-population
NameDatabase database = NameDatabase.getDatabase(appContext);
List<Name> names = createNames();
insertData(database, names);
// notify that the database was created and it's ready to be used
database.setDatabaseCreated();
});
}
}
).build();
}
private void updateDatabaseCreated(final Context context) {
if (context.getDatabasePath("name_database").exists()) {
setDatabaseCreated();
}
}
private boolean setDatabaseCreated() {
return this.setDatabaseCreated = true;
}
protected static List<Name> createNames() {
List<Name> cList = new ArrayList<>();
cList.add(new Name(1, "Body"));
cList.add(new Name(2, "Mind"));
cList.add(new Name(3, "Love"));
cList.add(new Name(4, "Community"));
cList.add(new Name(5, "Career"));
cList.add(new Name(6, "Money"));
cList.add(new Name(7, "Fun"));
cList.add(new Name(8, "Home"));
return cList;
}
private static void insertData(final NameDatabase database, final List<Name> names) {
database.runInTransaction(() -> {
database.nameDao().insertAll(names);
});
}
private static void addDelay() {
try {
Thread.sleep(4000);
} catch (InterruptedException ignored) {
}
}
}
给我String name = mNameDao.getNameByName("Body").name;
的异常这条线,当我第一次安装应用程序时,但是如果我关闭应用程序并重新启动它不会再出现异常。 我想是因为数据库还没有被填充。
我读了一篇文章Pre-Populate Database说第一次调用db.getInstance(context);
在我的例子中,数据库将被填充NameDatabase.getDatabase(MainActivity.this)
。
那么我该怎么做才能知道在调用后数据库是否已完成填充?
我想是因为数据库还没有被填充。
正确的。 您已经创建了一个后台线程 ( AsyncTask
)。 该线程通过您的getDatabase()
调用分叉第二个后台线程,因为您的数据库回调通过Executors.newSingleThreadScheduledExecutor().execute()
分叉自己的线程。 您的AsyncTask
不会等待第二个线程。
从回调中删除Executors.newSingleThreadScheduledExecutor().execute()
。 在当前线程(在这种情况下,将是AsyncTask
线程)上初始化您的数据库。 确保您只从后台线程访问数据库,例如让您的数据库访问由存储库管理。
我希望我没有迟到! 在我回答之前,先介绍一下背景。
我也在寻找有关此问题的解决方案。 我想要在我的应用程序启动时出现一个加载屏幕,然后当数据库完成预填充时它就会消失。
我想出了这个(出色的)解决方案:有一个thread
来检查要等待的表的大小。 如果所有实体的大小都不为 0,则通知主 UI 线程。 ( 0也可能是实体插入完成后的大小。这样也更好。)
我要注意的一件事是,您不必将entity
类中的变量设为public
。 您已经为他们准备了getters
/ setters
。 我还删除了您的setDatabaseCreated
布尔变量。 (相信我,我也尝试使用volatile
变量进行检查,但没有用。)
解决方案如下: 创建一个Notifier
类,在数据库完成预填充时通知主 UI 线程。 由此产生的一个问题是内存泄漏。 您的数据库可能需要很长时间来预填充,并且用户可能会进行一些配置(例如旋转设备),这将创建同一活动的多个实例。 但是,我们可以使用WeakReference
解决它。
这是代码......
通知类
public abstract class DBPrePopulateNotifier {
private Activity activity;
public DBPrePopulateNotifier(Activity activity) {
this.activity = activity;
}
public void execute() {
new WaitDBToPrePopulateAsyncTask(this, activity).execute();
}
// This method will be called to set your UI controllers
// No memory leaks will be caused by this because we will use
// a weak reference of the activity
public abstract void onFinished(String name);
private static class WaitDBToPrePopulateAsyncTask extends AsyncTask<Void, Void, String> {
private static final int SLEEP_BY_MILLISECONDS = 500;
private WeakReference<Activity> weakReference;
private DBPrePopulateNotifier notifier;
private WaitDBToPrePopulateAsyncTask(DBPrePopulateNotifier notifier, Activity activity) {
// We use a weak reference of the activity to prevent memory leaks
weakReference = new WeakReference<>(activity);
this.notifier = notifier;
}
@Override
protected String doInBackground(Void... voids) {
int count;
Activity activity;
while (true) {
try {
// This is to prevent giving the pc too much unnecessary load
Thread.sleep(SLEEP_BY_MILLISECONDS);
}
catch (InterruptedException e) {
e.printStackTrace();
break;
}
// We check if the activity still exists, if not then stop looping
activity = weakReference.get();
if (activity == null || activity.isFinishing()) {
return null;
}
count = NameDatabase.getDatabase(activity).nameDao().getAllNames().size();
if (count == 0) {
continue;
}
// Add more if statements here if you have more tables.
// E.g.
// count = NameDatabase.getDatabase(activity).anotherDao().getAll().size();
// if (count == 0) continue;
break;
}
activity = weakReference.get();
// Just to make sure that the activity is still there
if (activity == null || activity.isFinishing()) {
return null;
}
// This is the piece of code you wanted to execute
NameDatabase db = NameDatabase.getDatabase(activity);
NameDao nameDao = db.nameDao();
return nameDao.getNameByName("Body").getName();
}
@Override
protected void onPostExecute(String name) {
super.onPostExecute(name);
// Check whether activity is still alive if not then return
Activity activity = weakReference.get();
if (activity == null|| activity.isFinishing()) {
return;
}
// No need worry about memory leaks because
// the code below won't be executed anyway
// if a configuration has been made to the
// activity because of the return statement
// above
notifier.onFinished(name);
}
}
}
主要活动
public class MainActivity extends AppCompatActivity {
private TextView nameView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nameView = findViewById(R.id.name);
new DBPrePopulateNotifier(this) {
@Override
public void onFinished(String name) {
// You set your UI controllers here
// Don't worry and this won't cause any memory leaks
nameView.setText(name);
}
}.execute();
}
}
如您所见,我们的 Notifier 类中有一个线程,用于检查实体是否不为空。
我没有改变你的其他类的任何信息: Name
, NameDao
和NameDatabase
除了我删除了boolean
变量NameDatabase
并取得private
的变量Name
。
我希望这能完美回答你的问题。 正如你所说,没有LiveData
, Repository
等。
我真的希望我没有迟到!
现在我想写下我在得出最终解决方案之前的尝试。
请记住,我在这里尝试做的是让我的应用程序显示一个进度条(那个无限旋转的圆圈),并在数据库完成预填充后将其收起。
尝试过: 1.螺纹内螺纹
实际上,有一个thread
会检查entity
的大小是否仍然为 0。查询由另一个thread
完成。
结果:失败。 由于我缺乏知识,您无法启动thread
另一内thread
。 线程只能从主thread
启动。
查询要检查的表是否已通过无限循环初始化的thread
。 仅当要检查的表的所有大小都大于 0 时才中断。
结果:解决方案。 这是迄今为止解决这个问题的最优雅和最有效的解决方案。 它不会导致内存泄漏,因为一旦进行了配置,不断循环的thread
就会中断。
数据库类中的static volatile
变量,当thread
完成插入值时,该变量将变为真。
结果:失败。 由于我仍在搜索的未知原因,它不会运行用于初始化数据库的thread
。 我已经尝试了3 个版本的代码实现,但都无济于事。 因此,失败了。
在 UI 线程中定义的listener
然后通过参数传递给repository
。 所有数据库初始化也在repository
。 填充数据库后,它将通知/调用listener
。
结果:失败。 可能导致内存泄漏。
一如既往,快乐编码!
登录 onCreateCallback ofc!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.