Android Room-단순 선택 쿼리-기본 스레드에서 데이터베이스에 액세스 할 수 없음 String email = editTextEmail.getText().toString();

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

여기에 빠진 게 있나요? 충돌없이 실행하려면 어떻게해야합니까? 제안 해주세요.



답변

Dale이 말한 것처럼 UI를 잠그는 주 스레드에 대한 데이터베이스 액세스는 오류입니다.

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

또는 자체 파일에 최종 클래스를 만들 수 있습니다.

그런 다음 signUpAction (View view) 메서드에서 실행합니다.

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

경우에 따라 활동이 삭제 될 때 취소 할 수 있도록 활동에 AgentAsyncTask에 대한 참조를 보유 할 수도 있습니다. 그러나 모든 거래를 스스로 중단해야합니다.

또한 Google의 테스트 예에 대한 질문 … 해당 웹 페이지에 다음과 같이 명시되어 있습니다.

데이터베이스 구현을 테스트하는 데 권장되는 접근 방식은 Android 기기에서 실행되는 JUnit 테스트를 작성하는 것입니다. 이러한 테스트는 활동을 만들 필요가 없기 때문에 UI 테스트보다 실행 속도가 더 빠릅니다.

활동 없음, UI 없음.

–편집하다–

궁금한 사람들을 위해 … 다른 옵션이 있습니다. 새로운 ViewModel 및 LiveData 구성 요소를 살펴 보는 것이 좋습니다. LiveData는 Room과 잘 작동합니다.
https://developer.android.com/topic/libraries/architecture/livedata.html

또 다른 옵션은 RxJava / RxAndroid입니다. LiveData보다 강력하지만 복잡합니다.
https://github.com/ReactiveX/RxJava

-편집 2–

많은 사람들이이 답변을 접할 수 있기 때문에 … 요즘 가장 좋은 옵션은 일반적으로 Kotlin Coroutines입니다. 이제 Room에서 직접 지원합니다 (현재 베타 버전).
https://kotlinlang.org/docs/reference/coroutines-overview.html
https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01


답변

권장하지는 않지만 기본 스레드의 데이터베이스에 액세스 할 수 있습니다. allowMainThreadQueries()

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


답변

Kotlin 코 루틴 (명확하고 간결한)

AsyncTask는 정말 투박합니다. 코 루틴은 더 깨끗한 대안입니다 (키워드 몇 개만 뿌리면 동기화 코드가 비 동기화됩니다).

// Step 1: add `suspend` to your fun
suspend fun roomFun(...): Int
suspend fun notRoomFun(...) = withContext(Dispatchers.IO) { ... }

// Step 2: launch from coroutine scope
private fun myFun() {
    lifecycleScope.launch { // coroutine on Main
        val queryResult = roomFun(...) // coroutine on IO
        doStuff() // ...back on Main
    }
}

종속성 (아치 구성 요소에 대한 코 루틴 범위 추가) :

// lifecycleScope:
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha04'

// viewModelScope:
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-alpha04'

-업데이트 :
2019 년 5 월 8 일 : 이제 Room 2.1 suspend
에서 2019 년 9 월 13 일 지원 : 아키텍처 구성 요소 범위 를 사용하도록 업데이트 됨


답변

모든 RxJava 또는 RxAndroid 또는 RxKotlin 애호가를위한

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


답변

대신 핸들러, 비동기 또는 작업 스레드를 사용하여 주 스레드에서 실행할 수 없습니다. 여기에서 샘플 코드를 사용할 수 있으며 여기에서 룸 라이브러리에 대한 기사를 읽으십시오. Android의 Room Library

/**
 *  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()


답변

람다를 사용하면 AsyncTask로 쉽게 실행할 수 있습니다.

 AsyncTask.execute(() -> //run your query here );


답변

Jetbrains Anko 라이브러리를 사용하면 doAsync {..} 메서드를 사용하여 데이터베이스 호출을 자동으로 실행할 수 있습니다. 이것은 mcastro의 대답으로 겪었던 것처럼 보였던 자세한 문제를 처리합니다.

사용 예 :

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

삽입 및 업데이트에 자주 사용하지만 일부 쿼리의 경우 RX 워크 플로를 사용하는 것이 좋습니다.