小编典典

Android Room - 简单的选择查询 - 无法访问主线程上的数据库

all

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

@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 中使用以下子类公开数据库:

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()
    }
}

在我的活动中实现了以下功能:

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)聽

似乎该问题与在主线程上执行 db 操作有关。但是,上面链接中提供的示例测试代码不在单独的线程上运行:

@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));
    }

我在这里有什么遗漏吗?我怎样才能让它在没有崩溃的情况下执行?请建议。


阅读 120

收藏
2022-06-30

共1个答案

小编典典

就像 Dale 说的那样,锁定 UI 的主线程上的数据库访问是错误的。

--编辑2–

由于很多人可能会遇到这个答案......现在最好的选择,一般来说,是 Kotlin Coroutines。Room 现在直接支持它(目前处于测试阶段)。
https://kotlinlang.org/docs/reference/coroutines-overview.html

https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01

--编辑1–

对于想知道的人…您还有其他选择。我建议查看新的 ViewModel 和 LiveData 组件。LiveData 与 Room 配合得很好。
https://developer.android.com/topic/libraries/architecture/livedata.html

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

--原答案–

在扩展 AsyncTask 的 Activity 中创建一个静态嵌套类(以防止内存泄漏)。

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();
        }
    }
}

或者您可以在自己的文件上创建最终类。

然后在signUpAction(View view)方法中执行:

new AgentAsyncTask(this, email, phone, license).execute();

在某些情况下,您可能还希望在 Activity 中保留对 AgentAsyncTask 的引用,以便在 Activity
被销毁时取消它。但是您必须自己中断任何交易。

另外,您对 Google 测试示例的问题......他们在该网页中声明:

测试数据库实现的推荐方法是编写在 Android 设备上运行的 JUnit 测试。因为这些测试不需要创建一个活动,所以它们应该比你的 UI
测试执行得更快。

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

2022-06-30