Android Room-简单选择查询-无法访问主线程上的数据库 [英] Android Room - simple select query - Cannot access database on the main thread
问题描述
我正在尝试使用 Room Persistence Library 的示例. 我创建了一个实体:
I am trying a sample with Room Persistence Library. I created an Entity:
@Entity
public class Agent {
@PrimaryKey
public String guid;
public String name;
public String email;
public String password;
public String phone;
public String licence;
}
创建了一个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);
}
创建了数据库类:
@Database(entities = {Agent.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract AgentDao agentDao();
}
使用以下Kotlin子类的公开数据库:
Exposed database using below subclass in 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()
}
}
在我的活动中实现了以下功能:
Implemented below function in my activity:
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();
}
}
不幸的是,执行上述方法后,它在以下堆栈跟踪中崩溃:
Unfortunately on execution of above method it crashes with below stack trace:
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)
似乎该问题与在主线程上执行db操作有关.但是,以上链接中提供的示例测试代码未在单独的线程上运行:
Seems like that problem is related to execution of db operation on main thread. However the sample test code provided in above link does not run on a separate thread:
@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));
}
我在这里错过了什么吗?如何使它执行而不会崩溃?请提出建议.
Am I missing anything over here? How can I make it execute without crash? Please suggest.
推荐答案
锁定UI的主线程上的数据库访问是错误,如Dale所说.
Database access on main thread locking the UI is the error, like Dale said.
在扩展AsyncTask的Activity中创建一个静态嵌套类(以防止内存泄漏).
Create a static nested class (to prevent memory leak) in your Activity extending AsyncTask.
private static class AgentAsyncTask extends AsyncTask<Void, Void, Integer> {
//Prevent leak
private WeakReference<Activity> weakActivity;
private String email;
private String phone;
private String license;
public AgentAsyncTask(Activity activity, String email, String phone, String license) {
weakActivity = new WeakReference<>(activity);
this.email = email;
this.phone = phone;
this.license = license;
}
@Override
protected Integer doInBackground(Void... params) {
AgentDao agentDao = MyApp.DatabaseSetup.getDatabase().agentDao();
return agentDao.agentsCount(email, phone, license);
}
@Override
protected void onPostExecute(Integer agentsCount) {
Activity activity = weakActivity.get();
if(activity == null) {
return;
}
if (agentsCount > 0) {
//2: If it already exists then prompt user
Toast.makeText(activity, "Agent already exists!", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(activity, "Agent does not exist! Hurray :)", Toast.LENGTH_LONG).show();
activity.onBackPressed();
}
}
}
或者您可以在自己的文件中创建最终课程.
Or you can create a final class on its own file.
然后在signUpAction(View view)方法中执行它:
Then execute it in the signUpAction(View view) method:
new AgentAsyncTask(this, email, phone, license).execute();
在某些情况下,您可能还希望在活动中保留对AgentAsyncTask的引用,以便在活动被销毁时可以将其取消.但是您必须自己中断任何交易.
In some cases you might also want to hold a reference to the AgentAsyncTask in your activity so you can cancel it when the Activity is destroyed. But you would have to interrupt any transactions yourself.
此外,您对Google测试示例的问题... 他们在该网页上声明:
Also, your question about the Google's test example... They state in that web page:
推荐的测试数据库实现的方法是 编写在Android设备上运行的JUnit测试.因为这些 测试不需要创建活动,它们应该更快 比您的UI测试执行.
The recommended approach for testing your database implementation is writing a JUnit test that runs on an Android device. Because these tests don't require creating an activity, they should be faster to execute than your UI tests.
没有活动,没有用户界面.
No Activity, no UI.
-编辑-
对于想知道的人...您还有其他选择. 我建议看一下新的ViewModel和LiveData组件. LiveData与Room配合使用效果很好. https://developer.android.com/topic/libraries/architecture/livedata.html
For people wondering... You have other options. I recommend taking a look into the new ViewModel and LiveData components. LiveData works great with Room. https://developer.android.com/topic/libraries/architecture/livedata.html
另一个选项是RxJava/RxAndroid.比LiveData更强大,但更复杂. https://github.com/ReactiveX/RxJava
Another option is the RxJava/RxAndroid. More powerful but more complex than LiveData. https://github.com/ReactiveX/RxJava
-编辑2-
由于许多人可能会遇到此答案... 一般来说,当今最好的选择是Kotlin Coroutines.现在,Room可以直接支持它(当前为Beta). https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases /room#2.1.0-beta01
Since many people may come across this answer... The best option nowadays, generally speaking, is Kotlin Coroutines. Room now supports it directly (currently in beta). https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01
这篇关于Android Room-简单选择查询-无法访问主线程上的数据库的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!