[解決済み] Android Room - シンプルな select クエリ - メインスレッドでデータベースにアクセスできません。
質問
を使ったサンプルを試しています。 ルーム永続化ライブラリ . 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 クラスを作成しました。
@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));
}
何か見落としがあるのでしょうか?どうすればクラッシュせずに実行できるでしょうか?提案お願いします。
解決方法は?
UIをロックしているメインスレッドでのデータベースアクセスは、Daleが言ったように、エラーです。
--EDIT 2--
この答えに行き着く人も多いでしょうから...。 現在、一般的に言ってベストな選択肢はKotlinのコルーチンです。Roomが直接サポートするようになりました(現在はベータ版です)。 https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01
--EDIT 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();
場合によっては、アクティビティ内でAgentAsyncTaskへの参照を保持し、アクティビティが破棄されたときにそれをキャンセルできるようにしたいこともあります。しかし、その場合、すべてのトランザクションを自分で中断しなければなりません。
また、Googleのテスト例についてのご質問ですが...。 そのウェブページにはこう書かれています。
<ブロッククオートデータベースの実装をテストするための推奨アプローチは Android端末上で動作するJUnitテストを作成する。なぜなら テストはアクティビティを作成する必要がないため、より高速に実行できるはずです。 は、UIテストよりも実行されます。
No Activity, No UI.
関連
-
adb シェルがデバイスのオフラインを求めるプロンプトを表示する
-
Androidのadbデバイスがオフラインであることが判明
-
armeabi-v7a armeabi arm64-v8a パラメータの意味説明
-
java.lang.NullPointerException: NULLオブジェクト参照で仮想メソッド......を呼び出そうとしました。
-
android.os の NetworkOnMainThreadException。
-
RuntimeException: アクティビティを開始できません ComponentInfo solution
-
JVMのエラーに遭遇しました。Java Runtime Environmentによって致命的なエラーが検出されました。
-
Androidで、onTouchEventでダブルクリックを実装し、ダブルクリックイベントとして判定する方法
-
Android Bluetooth 開発の基本プロセス
-
アンドロイドシェイプ、グラデーション、角丸、ボーダーラインの設定
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
ジャークとして。起動アクティビティを特定できませんでした。デフォルトのアクティビティが見つかりません アクティビティ起動中のエラー
-
を作ってください。*** makeするルールがない エラーの原因、分析、解決策
-
エラーが発生しました。ArrayAdapter は、リソース ID が TextView である必要があります。
-
アンドロイドスタジオでJunitのエラー問題を解決する
-
エラータイプ 3 タイプエラー, Error: アクティビティクラス{}が存在しません。アクティビティ起動時のエラー 解決方法
-
repo: コマンドが見つかりません
-
アンドロイドのエリプサイズを使用する
-
android bluetooth--Bluetooth on、検索、ペアリング、接続
-
Android Studioのgitの使用とgitの設定パス
-
Android Studio常见错误之:Rendering Problems/The following classes could not be instantiated