Android室 – 简单的select查询 – 无法访问主线程上的数据库

我正在试用Room Persistence Library的示例。 我创build了一个实体:

@Entity public class Agent { @PrimaryKey public String guid; public String name; public String email; public String password; public String phone; public String licence; } 

创build一个DAO类:

 @Dao public interface AgentDao { @Query("SELECT COUNT(*) FROM Agent where email = :email OR phone = :phone OR licence = :licence") int agentsCount(String email, String phone, String licence); @Insert void insertAgent(Agent agent); } 

创build数据库类:

 @Database(entities = {Agent.class}, version = 1) public abstract class AppDatabase extends RoomDatabase { public abstract AgentDao agentDao(); } 

在Kotlin中使用下面的子类暴露数据库:

 class MyApp : Application() { companion object DatabaseSetup { var database: AppDatabase? = null } override fun onCreate() { super.onCreate() MyApp.database = Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").build() } } 

在我的活动中实现下面的function:

 void signUpAction(View view) { String email = editTextEmail.getText().toString(); String phone = editTextPhone.getText().toString(); String license = editTextLicence.getText().toString(); AgentDao agentDao = MyApp.DatabaseSetup.getDatabase().agentDao(); //1: Check if agent already exists int agentsCount = agentDao.agentsCount(email, phone, license); if (agentsCount > 0) { //2: If it already exists then prompt user Toast.makeText(this, "Agent already exists!", Toast.LENGTH_LONG).show(); } else { Toast.makeText(this, "Agent does not exist! Hurray :)", Toast.LENGTH_LONG).show(); onBackPressed(); } } 

不幸的是,执行上面的方法时,它会崩溃,下面的堆栈跟踪:

  FATAL EXCEPTION: main Process: com.example.me.MyApp, PID: 31592 java.lang.IllegalStateException: Could not execute method for android:onClick at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:293) at android.view.View.performClick(View.java:5612) at android.view.View$PerformClick.run(View.java:22288) at android.os.Handler.handleCallback(Handler.java:751) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6123) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757) Caused by: java.lang.reflect.InvocationTargetException at java.lang.reflect.Method.invoke(Native Method) at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288) at android.view.View.performClick(View.java:5612) at android.view.View$PerformClick.run(View.java:22288) at android.os.Handler.handleCallback(Handler.java:751) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6123) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757) Caused by: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long periods of time. at android.arch.persistence.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:137) at android.arch.persistence.room.RoomDatabase.query(RoomDatabase.java:165) at com.example.me.MyApp.RoomDb.Dao.AgentDao_Impl.agentsCount(AgentDao_Impl.java:94) at com.example.me.MyApp.View.SignUpActivity.signUpAction(SignUpActivity.java:58) at java.lang.reflect.Method.invoke(Native Method) at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288) at android.view.View.performClick(View.java:5612) at android.view.View$PerformClick.run(View.java:22288) at android.os.Handler.handleCallback(Handler.java:751) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6123) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757) 

看起来像这个问题是关于执行数据库操作主线程。 但是,上述链接中提供的示例testing代码不会在单独的线程上运行:

 @Test public void writeUserAndReadInList() throws Exception { User user = TestUtil.createUser(3); user.setName("george"); mUserDao.insert(user); List<User> byName = mUserDao.findUsersByName("george"); assertThat(byName.get(0), equalTo(user)); } 

我在这里错过了什么? 我怎样才能让它执行没有崩溃? 请build议。

Solutions Collecting From Web of "Android室 – 简单的select查询 – 无法访问主线程上的数据库"

戴尔说,lockingUI的主线程的数据库访问是错误的。

像下面的代码应该在signUpAction(视图视图)方法内工作…

 final AgentDao agentDao = MyApp.DatabaseSetup.getDatabase().agentDao(); new AsyncTask<Void, Void, Integer>() { @Override protected Integer doInBackground(Void... params) { return agentDao.agentsCount(email, phone, license); } @Override protected void onPostExecute(Integer agentsCount) { if (agentsCount > 0) { //2: If it already exists then prompt user Toast.makeText(Activity.this, "Agent already exists!", Toast.LENGTH_LONG).show(); } else { Toast.makeText(Activity.this, "Agent does not exist! Hurray :)", Toast.LENGTH_LONG).show(); onBackPressed(); } } }.execute(); 

*用[你的活动]replaceActivity.this

或者创build一个扩展AsyncTask的新类将会更清洁。

另外,您对Googletesting示例的问题…他们在该网页中声明:

testing数据库实现的推荐方法是编写在Android设备上运行的JUnittesting。 因为这些testing不需要创build一个活动,它们应该比你的UItesting更快执行。

没有活动,没有用户界面。

– 编辑 –

对于想知道的人…你有其他的select。 我build议看看新的ViewModel和LiveData组件。 LiveData可以和Room一起使用。 https://developer.android.com/topic/libraries/architecture/livedata.html

另一个select是RxJava / RxAndroid。 比LiveData更强大但更复杂。 https://github.com/ReactiveX/RxJava

这是不推荐,但你可以通过allowMainThreadQueries()访问主线程上的数据库

 MyApp.database = Room.databaseBuilder(this, AppDatabase::class.java, "MyDatabase").allowMainThreadQueries().build() 

对于所有的RxJava或RxAndroid或RxKotlin的爱好者

 Observable.just(db) .subscribeOn(Schedulers.io()) .subscribe { db -> // database operation } 

错误消息,

无法访问主线程上的数据库,因为它可能会长时间locking用户界面。

相当具有描述性和准确性。 问题是如何避免访问主线程上的数据库。 这是一个巨大的话题,但要开始,阅读有关AsyncTask(点击这里)

– – -编辑 – – – – –

我看你在运行unit testing时遇到了问题。 你有几个select来解决这个问题:

  1. 直接在开发机器上而不是在Android设备(或模拟器)上运行testing。 这适用于以数据库为中心的testing,并且不关心它们是否在设备上运行。

  2. 使用注释@RunWith(AndroidJUnit4.class)在Android设备上运行testing,但不在具有UI的活动中运行。 关于这个的更多细节可以在本教程中find

使用Jetbrains Anko库,您可以使用doAsync {..}方法自动执行数据库调用。 这照顾了你似乎与mcastro的答案有关的详细问题。

用法示例:

  doAsync { Application.database.myDAO().insertUser(user) } 

我经常使用它来插入和更新,但是对于我使用RX工作stream程推荐的select查询。

对于快速查询,您可以允许空间在UI线程上执行它。

 AppDatabase db = Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DATABASE_NAME).allowMainThreadQueries().build(); 

在我的情况下,我不得不找出列表中点击的用户是否存在于数据库中。 如果没有,则创build用户并开始另一个活动

  @Override public void onClick(View view) { int position = getAdapterPosition(); User user = new User(); String name = getName(position); user.setName(name); AppDatabase appDatabase = DatabaseCreator.getInstance(mContext).getDatabase(); UserDao userDao = appDatabase.getUserDao(); ArrayList<User> users = new ArrayList<User>(); users.add(user); List<Long> ids = userDao.insertAll(users); Long id = ids.get(0); if(id == -1) { user = userDao.getUser(name); user.setId(user.getId()); } else { user.setId(id); } Intent intent = new Intent(mContext, ChatActivity.class); intent.putExtra(ChatActivity.EXTRAS_USER, Parcels.wrap(user)); mContext.startActivity(intent); } } 

一个优雅的RxJava / Kotlin解决scheme是使用Completable.fromCallable ,它将给你一个Observable,它不返回一个值,但可以观察和订阅不同的线程。

 public Completable insert(Event event) { return Completable.fromCallable(new Callable<Void>() { @Override public Void call() throws Exception { return database.eventDao().insert(event) } } } 

或者在Kotlin:

 fun insert(event: Event) : Completable = Completable.fromCallable { database.eventDao().insert(event) } 

您可以像通常那样观察和订阅:

 dataManager.insert(event) .subscribeOn(scheduler) .observeOn(AndroidSchedulers.mainThread()) .subscribe(...) 

您不能在主线程上运行它,而是使用处理程序,asynchronous或工作线程。 示例代码可在此处阅读,并阅读房间库上的文章: Android的房间库

/** * Insert and get data using Database Async way */ AsyncTask.execute(new Runnable() { @Override public void run() { //Insert Data AppDatabase.getInstance(context).userDao().insert(new User(1,"James","Mathew")); // Get Data AppDatabase.getInstance(context).userDao().getAllUsers(); } });

如果你想在主线程上运行它不是首选的方式。

你可以使用这个方法在主线程上实现Room.inMemoryDatabaseBuilder()

您可以在主线程上允许数据库访问,但仅用于debugging目的,您不应该在生产中这样做。

这是原因。

注意:除非您在构build器上调用了allowMainThreadQueries(),否则房间不支持主线程上的数据库访问,因为它可能会长时间lockingUI。 asynchronous查询(返回LiveData或Flowable实例的查询)免于此规则,因为它们在需要时asynchronous地在后台线程上运行查询。