Android Room-단순 선택 쿼리-기본 스레드에서 데이터베이스에 액세스 할 수 없음
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()
모든 RxJava 또는 RxAndroid 또는 RxKotlin 애호가를 위해
Observable.just(db)
.subscribeOn(Schedulers.io())
.subscribe { db -> // database operation }
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 일 지원 : 아키텍처 구성 요소 범위 를 사용하도록 업데이트되었습니다 .
대신 핸들러, 비동기 또는 작업 스레드를 사용하여 주 스레드에서 실행할 수 없습니다. 여기에서 샘플 코드를 사용할 수 있으며 여기에서 룸 라이브러리에 대한 기사를 읽으십시오. 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()
Jetbrains Anko 라이브러리를 사용하면 doAsync {..} 메서드를 사용하여 데이터베이스 호출을 자동으로 실행할 수 있습니다. 이것은 mcastro의 답변으로 겪었던 것처럼 보였던 자세한 문제를 처리합니다.
사용 예 :
doAsync {
Application.database.myDAO().insertUser(user)
}
삽입 및 업데이트에 자주 사용하지만 일부 쿼리의 경우 RX 워크 플로를 사용하는 것이 좋습니다.
우아한 RxJava / Kotlin 솔루션은 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(...)
람다를 사용하면 AsyncTask로 쉽게 실행할 수 있습니다.
AsyncTask.execute(() -> //run your query here );
백그라운드에서 요청을 실행해야합니다. 간단한 방법은 Executors를 사용하는 것입니다 .
Executors.newSingleThreadExecutor().execute {
yourDb.yourDao.yourRequest() //Replace this by your request
}
오류 메시지,
오랫동안 UI를 잠글 수 있으므로 주 스레드의 데이터베이스에 액세스 할 수 없습니다.
매우 설명적이고 정확합니다. 문제는 주 스레드에서 데이터베이스에 액세스하지 않는 방법입니다. 그것은 거대한 주제이지만 시작하려면 AsyncTask 에 대해 읽어보십시오 (여기를 클릭하십시오)
-----편집하다----------
단위 테스트를 실행할 때 문제가있는 것 같습니다. 이 문제를 해결할 수있는 몇 가지 방법이 있습니다.
Android 기기 (또는 에뮬레이터)가 아닌 개발 머신에서 직접 테스트를 실행하세요. 이것은 데이터베이스 중심의 테스트에서 작동하며 장치에서 실행되는지 여부에 실제로 신경 쓰지 않습니다.
주석
@RunWith(AndroidJUnit4.class)
을 사용하여 Android 기기에서 테스트를 실행하지만 UI가있는 활동에서는 실행하지 않습니다. 이에 대한 자세한 내용은 이 튜토리얼에서 찾을 수 있습니다.
비동기 작업에 더 익숙한 경우 :
new AsyncTask<Void, Void, Integer>() {
@Override
protected Integer doInBackground(Void... voids) {
return Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, DATABASE_NAME)
.fallbackToDestructiveMigration()
.build()
.getRecordingDAO()
.getAll()
.size();
}
@Override
protected void onPostExecute(Integer integer) {
super.onPostExecute(integer);
Toast.makeText(HomeActivity.this, "Found " + integer, Toast.LENGTH_LONG).show();
}
}.execute();
간단히이 코드를 사용하여 해결할 수 있습니다.
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
appDb.daoAccess().someJobes();//replace with your code
}
});
또는 람다에서 다음 코드를 사용할 수 있습니다.
Executors.newSingleThreadExecutor().execute(() -> appDb.daoAccess().someJobes());
appDb.daoAccess().someJobes()
자신의 코드로 바꿀 수 있습니다 .
빠른 쿼리를 위해 UI 스레드에서 실행할 수있는 공간을 허용 할 수 있습니다.
AppDatabase db = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, DATABASE_NAME).allowMainThreadQueries().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);
}
}
주 스레드에서 데이터베이스 액세스를 허용 할 수 있지만 디버깅 목적으로 만 프로덕션에서는이 작업을 수행하면 안됩니다.
참고 : Room은 오랜 시간 동안 UI를 잠글 수 있으므로 빌더에서 allowMainThreadQueries ()를 호출하지 않는 한 기본 스레드에서 데이터베이스 액세스를 지원하지 않습니다. 비동기 쿼리 (LiveData 또는 Flowable의 인스턴스를 반환하는 쿼리)는 필요할 때 백그라운드 스레드에서 쿼리를 비동기 적으로 실행하므로이 규칙에서 제외됩니다.
업데이트 : DAO 내에서 @RawQuery 및 SupportSQLiteQuery를 사용하여 쿼리를 작성하려고 할 때도이 메시지를 받았습니다.
@Transaction
public LiveData<List<MyEntity>> getList(MySettings mySettings) {
//return getMyList(); -->this is ok
return getMyList(new SimpleSQLiteQuery("select * from mytable")); --> this is an error
솔루션 : ViewModel 내부에 쿼리를 작성하고 DAO에 전달하십시오.
public MyViewModel(Application application) {
...
list = Transformations.switchMap(searchParams, params -> {
StringBuilder sql;
sql = new StringBuilder("select ... ");
return appDatabase.rawDao().getList(new SimpleSQLiteQuery(sql.toString()));
});
}
또는...
기본 스레드에서 직접 데이터베이스에 액세스해서는 안됩니다. 예를 들면 다음과 같습니다.
public void add(MyEntity item) {
appDatabase.myDao().add(item);
}
업데이트, 추가 및 삭제 작업에는 AsyncTask를 사용해야합니다.
예:
public class MyViewModel extends AndroidViewModel {
private LiveData<List<MyEntity>> list;
private AppDatabase appDatabase;
public MyViewModel(Application application) {
super(application);
appDatabase = AppDatabase.getDatabase(this.getApplication());
list = appDatabase.myDao().getItems();
}
public LiveData<List<MyEntity>> getItems() {
return list;
}
public void delete(Obj item) {
new deleteAsyncTask(appDatabase).execute(item);
}
private static class deleteAsyncTask extends AsyncTask<MyEntity, Void, Void> {
private AppDatabase db;
deleteAsyncTask(AppDatabase appDatabase) {
db = appDatabase;
}
@Override
protected Void doInBackground(final MyEntity... params) {
db.myDao().delete((params[0]));
return null;
}
}
public void add(final MyEntity item) {
new addAsyncTask(appDatabase).execute(item);
}
private static class addAsyncTask extends AsyncTask<MyEntity, Void, Void> {
private AppDatabase db;
addAsyncTask(AppDatabase appDatabase) {
db = appDatabase;
}
@Override
protected Void doInBackground(final MyEntity... params) {
db.myDao().add((params[0]));
return null;
}
}
}
선택 작업에 LiveData를 사용하는 경우 AsyncTask가 필요하지 않습니다.
별도의 스레드에서 데이터베이스 작업을 수행하십시오. 이렇게 (Kotlin) :
Thread {
//Do your database´s operations here
}.start()
You can use Future and Callable. So you would not be required to write a long asynctask and can perform your queries without adding allowMainThreadQueries().
My dao query:-
@Query("SELECT * from user_data_table where SNO = 1")
UserData getDefaultData();
My repository method:-
public UserData getDefaultData() throws ExecutionException, InterruptedException {
Callable<UserData> callable = new Callable<UserData>() {
@Override
public UserData call() throws Exception {
return userDao.getDefaultData();
}
};
Future<UserData> future = Executors.newSingleThreadExecutor().submit(callable);
return future.get();
}
'Programing' 카테고리의 다른 글
RuntimeException : 콘텐츠에는 id 속성이 'android.R.id.list'인 ListView가 있어야합니다. (0) | 2020.09.10 |
---|---|
SecurityException을 발생시키지 않고 런타임에 권한을 어떻게 확인할 수 있습니까? (0) | 2020.09.10 |
jquery-매우 큰 테이블에서 모든 행을 제거하는 가장 빠른 방법 (0) | 2020.09.10 |
목록의 모든 요소가 고유한지 확인 (0) | 2020.09.10 |
전송 끝 점이 연결되지 않았습니다. (0) | 2020.09.10 |