Android Room-简单选择查询-无法访问主线程上的数据库 [英] Android Room - simple select query - Cannot access database on the main thread

查看:1148
本文介绍了Android Room-简单选择查询-无法访问主线程上的数据库的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆