繁体   English   中英

android - 检查房间数据库是否在没有 Livedata 的情况下在启动时填充

[英]android - Check if the room database is populated on startup without Livedata

我对 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)

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

我想是因为数据库还没有被填充。

正确的。 您已经创建了一个后台线程 ( 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 类中有一个线程,用于检查实体是否不为空。

我没有改变你的其他类的任何信息: NameNameDaoNameDatabase除了我删除了boolean变量NameDatabase并取得private的变量Name

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

我真的希望我没有迟到!


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

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

尝试过: 1.螺纹内螺纹

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

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

  1. 表的大小循环检查器

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

结果:解决方案 这是迄今为止解决这个问题的最优雅和最有效的解决方案。 它不会导致内存泄漏,因为一旦进行了配置,不断循环的thread就会中断。

  1. 静态变量

数据库类中的static volatile变量,当thread完成插入值时,该变量将变为真。

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

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

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

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


一如既往,快乐编码!

登录 onCreateCallback ofc!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM