android-检查是否在启动时没有Livedata的情况下填充了会议室数据库

时间:2018-07-14 19:51:18

标签: java android android-room

我是Android的新手,我想在我的应用程序中拥有一个数据库。 我向Room介绍了文档,说这是在android中实现数据库的最佳方法。

现在,我必须在数据库中预先填充一些数据,并确保在应用启动之前已将其填充。

我看到有很多类似LiveDataRepositoriesViewModelsMediatorLiveData之类的东西。

但是我只想保持它简单明了,而不使用所说的东西,如何才能在应用程序启动之前查找数据库是否已被填充。

我正在加载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)将填充数据库。

那我该怎么做才能知道调用后数据库是否已完成填充?

3 个答案:

答案 0 :(得分:0)

  

我认为是因为尚未填充数据库。

正确。您已经派生了一个后台线程(AsyncTask)。该线程正在通过您的getDatabase()调用派生第二个后台线程,因为数据库回调正在通过Executors.newSingleThreadScheduledExecutor().execute()派生其自己的线程。您的AsyncTask不会等待第二个线程。

从您的回调中删除Executors.newSingleThreadScheduledExecutor().execute()。在当前线程(在本例中为AsyncTask线程)上初始化数据库。确保仅从后台线程访问数据库,例如通过存储库来管理数据库访问。

答案 1 :(得分:0)

我希望我不迟到!在我回答之前只有一点背景。

我也在寻找有关此问题的解决方案。我希望在应用程序启动时显示一个加载屏幕,然后在数据库完成预填充后消失。

我想出了一个(出色的)解决方案:有一个thread来检查要等待的表的大小。如果所有实体的大小都不为0,则通知主UI线程。 ( 0 也可能是您实体插入后的大小。这样做也更好。)

我想指出的一件事是,您不必在entitypublic中进行变量设置。您已经有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变量并进行了设置外,我没有在您的其他班级中进行任何更改:NameDatabasebooleanNameDatabase private Name中的变量。

我希望这能完美回答您的问题。如您所说,没有LiveDataRepository

我真的希望我不会迟到!


现在,我想写下最终解决方案之前的尝试。

请记住,我要在这里做的是让我的应用程序显示进度条(无限旋转的圆圈),并在数据库完成预填充后将其丢弃。

尝试: 1. 线程内的线程

实际上,有一个thread检查entity的大小是否仍为0。查询由另一个thread完成。

结果:失败。由于我缺乏知识,您无法在另一个thread中启动thread。线程只能从主thread启动。

  1. 表的大小循环检查器

一个thread,用于查询要检查的表是否已通过无限循环初始化。仅在要检查的表的所有大小都大于0时中断。

结果:解决方案。到目前为止,这是解决此问题的最优雅,最可行的解决方案。不会造成内存泄漏,因为一旦完成配置,不断循环的thread就会中断。

  1. 静态变量

数据库类中的一个static volatile变量,当thread完成插入值后,该变量将变为true。

结果:失败。由于我仍在搜索的未知原因,它不会运行thread来初始化数据库。我尝试了代码实现的 3个版本,但无济于事。因此,失败了。

  1. 初始化数据库,然后通知

在用户界面线程中定义的listener,然后通过参数传递给repository。所有数据库初始化也都在repository中完成。填充数据库后,它将通知/调用listener

结果:失败。可能导致内存泄漏。


一如既往,祝您编程愉快!

答案 2 :(得分:-1)

登录onCreateCallback ofc!