我是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)
将填充数据库。
那我该怎么做才能知道调用后数据库是否已完成填充?
答案 0 :(得分:0)
我认为是因为尚未填充数据库。
正确。您已经派生了一个后台线程(AsyncTask
)。该线程正在通过您的getDatabase()
调用派生第二个后台线程,因为数据库回调正在通过Executors.newSingleThreadScheduledExecutor().execute()
派生其自己的线程。您的AsyncTask
不会等待第二个线程。
从您的回调中删除Executors.newSingleThreadScheduledExecutor().execute()
。在当前线程(在本例中为AsyncTask
线程)上初始化数据库。确保仅从后台线程访问数据库,例如通过存储库来管理数据库访问。
答案 1 :(得分:0)
我希望我不迟到!在我回答之前只有一点背景。
我也在寻找有关此问题的解决方案。我希望在应用程序启动时显示一个加载屏幕,然后在数据库完成预填充后消失。
我想出了一个(出色的)解决方案:有一个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);
}
}
}
MainActivity
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
完成插入值后,该变量将变为true。
结果:失败。由于我仍在搜索的未知原因,它不会运行thread
来初始化数据库。我尝试了代码实现的 3个版本,但无济于事。因此,失败了。
在用户界面线程中定义的listener
,然后通过参数传递给repository
。所有数据库初始化也都在repository
中完成。填充数据库后,它将通知/调用listener
。
结果:失败。可能导致内存泄漏。
一如既往,祝您编程愉快!
答案 2 :(得分:-1)
登录onCreateCallback ofc!