Androidルーム-単純な選択クエリ-メインスレッドのデータベースにアクセスできません


126

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クラスを作成しました。

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

私はここで何か不足していますか?クラッシュせずに実行するにはどうすればよいですか?提案してください。


1
この記事はKotlin向けに書かれていますが、根本的な問題を非常によく説明しています。
Peter Lehnhardt

するために見てみましょうstackoverflow.com/questions/58532832/...
nnyerges

この答えを見てください。この回答は私にとって
有効

回答:


59

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-

多くの人々がこの答えに出くわす可能性があるので...今日、一般的に言えば、最良の選択肢はコトリンコルーチンです。Roomはこれを直接サポートするようになりました(現在はベータ版)。 https://kotlinlang.org/docs/reference/coroutines-overview.html https://developer.android.com/jetpack/androidx/releases/room#2.1.0-beta01


31
データベースにアクセスする必要があるたびに、この巨大な非同期タスク(コードサンプルで提案したもの)を作成するはずですか?これは、dbからデータを取得するためのコードではなく、数十行のコードです。新しいクラスの作成も提案しましたが、データベースの挿入/選択の呼び出しごとに新しいAsyncTaskクラスを作成する必要があるということですか?
ピオトレック2017

7
はい、他にいくつかのオプションがあります。新しいViewModelコンポーネントとLiveDataコンポーネントを調べてみてください。LiveDataを使用する場合、AsyncTaskは必要ありません。オブジェクトは、何かが変更されるたびに通知されます。developer.android.com/topic/libraries/architecture/...が developer.android.com/topic/libraries/architecture/... AndroidRxもあります(これはかなりLiveDataが何んが)、および約束。AsyncTaskを使用する場合は、1つのAsyncTaskに複数の操作を含めたり、それぞれを分離したりできるようにアーキテクチャを構築できます。
mcastro 2017

@Piotrek-Kotlinは非同期で組み込まれています(ただし、実験的なフラグが立てられています)。比較的簡単な私の答えを見てください。サミュエルロバートの答えはRxをカバーしています。ここにはLiveDataの答えはありませんが、オブザーバブルが必要な場合は、これがより適切な選択かもしれません。
AjahnCharles

通常のAsyncTaskを使用しても動作しないようですが、違法な状態の例外が発生します
Peterstev Uremgba

@Piotrekあなたはあなたの経験全体でメインスレッドでデータベースアクセスを実行するのに慣れていると言っていますか?
mr5 2018年

142

それはお勧めできませんが、メインスレッドでデータベースにアクセスできます allowMainThreadQueries()

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

9
RoomはallowMainThreadQueries()、UIを長期間ロックする可能性があるため、ビルダーで呼び出さない限り、メインスレッドでデータベースにアクセスできません。非同期クエリ(返されるクエリLiveDataまたはRxJava Flowable)は、必要に応じてバックグラウンドスレッドで非同期にクエリを実行するため、このルールから除外されます。
pRaNaY 2017

4
ローダーからライブデータに変換する前に、Roomが期待どおりに機能するかテストしたいので、これは移行に非常に役立ちます
SammyT

5
@JideGuruTheProgrammerいいえ、できません。場合によっては、アプリが大幅に遅くなる可能性があります。操作は非同期で実行する必要があります。
Alex

@Alexメインスレッドでクエリを実行するケースはありませんか?
Justin Meiners

2
@JustinMeinersこれは単に悪い習慣です。データベースが小さければ問題ありません。
lasec0203

53

コトリンコルーチン(クリア&簡潔)

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

依存関係(archコンポーネントのコルーチンスコープを追加):

// 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日をサポートアーキテクチャコンポーネントスコープを使用するように更新


1
@Query abstract suspend fun count()サスペンドキーワードを使用して、コンパイルエラーが発生しましたか?:この同様の質問にあなたは親切に見ることができますstackoverflow.com/questions/48694449/...
ロビン

@ロビン-はい、そうです。私の間違い; 保護された非サスペンド@Query関数を呼び出すパブリック(注釈なし)DAOメソッドでサスペンドを使用していました。内部@Queryメソッドにもsuspendキーワードを追加すると、コンパイルに失敗します。それは、サスペンドとルームの衝突のフードの下にある賢いように見えます(他の質問で述べたように、コンパイルされたバージョンのサスペンドは、ルームが処理できない継続を返します)。
AjahnCharles、2018

理にかなっています。代わりに、コルーチン関数を使用して呼び出します。
ロビン

1
@Robin-参考までに、部屋2.1でサスペンドのサポートを追加しました:)
AjahnCharles

どうやら何もありませんlaunchキーワードはもう、次のような、スコープで起動GlobalScope.launch
nasch

48

すべてのためのRxJavaまたはRxAndroidまたはRxKotlinそこに愛好家

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

3
このコードをメソッド内に配置した場合、データベース操作から結果を返す方法は?
Eggakin Baconwalker 2017年

@EggakinBaconwalker私が今しているoverride fun getTopScores(): Observable<List<PlayerScore>> { return Observable .fromCallable({ GameApplication.database .playerScoresDao().getTopScores() }) .applySchedulers() }ところapplySchedulers()にいますfun <T> Observable<T>.applySchedulers(): Observable<T> = this.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread())
noloman '23

これはIntentServiceでは機能しません。なぜなら、スレッドが完了すると、IntentServiceが実行されるからです。
Umang Kothari 2018年

1
@UmangKothari IntentService#onHandleIntentこのメソッドはワーカースレッドで実行されるため、Roomデータベース操作を実行するためのスレッドメカニズムは必要ないため、オンの場合でも例外は発生しません
Samuel Robert

@SamuelRobert、はい、私の意見に同意します。それは私の心を滑らせました。
Umang Kothari 2018年

27

メインスレッドで実行することはできません。代わりに、ハンドラー、非同期スレッド、または作業スレッドを使用してください。サンプルコードはこちらから入手でき、ルームライブラリに関する記事はこちらから: Androidのルームライブラリ

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


このメソッドを使用してデータを取得する場合(この場合はgetAllUsers()のみ)、このメソッドからデータを返すにはどうすればよいですか?「run」の中に「return」という単語を入れると、エラーが表示されます。
Eggakin Baconwalker 2017

1
どこかにインターフェイスメソッドを作成し、匿名クラスを追加して、ここからデータを取得します。
リズバン2017年

1
これは、挿入/更新のための最も簡単なソリューションです。
Beer Me

12

ラムダを使用すると、AsyncTaskで簡単に実行できます

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

2
これは便利です、ありがとう。ちなみに、Kotlinはさらに簡単です:AsyncTask.execute {}
alexrnov

1
しかし、この方法を使用して結果をどのように取得しますか?
leeCoder

11

Jetbrains Ankoライブラリを使用すると、doAsync {..}メソッドを使用してデータベース呼び出しを自動的に実行できます。これにより、mcastroの回答で抱えていたと思われる冗長性の問題が解決されます。

使用例:

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

私はこれを挿入と更新に頻繁に使用しますが、選択したクエリにはRXワークフローを使用することをお勧めします。



6

リクエストはバックグラウンドで実行する必要があります。簡単な方法は、Executorsを使用することです。

Executors.newSingleThreadExecutor().execute { 
   yourDb.yourDao.yourRequest() //Replace this by your request
}

どのように結果を返しますか?
leeCoder

5

エレガントな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)
        }
    }
}

またはコトリンで:

fun insert(event: Event) : Completable = Completable.fromCallable {
    database.eventDao().insert(event)
}

通常のように、観察してサブスクライブできます。

dataManager.insert(event)
    .subscribeOn(scheduler)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(...)

5

メインスレッドでデータベースアクセスを許可することはできますが、デバッグ目的でのみ、本番環境では許可しないでください。

これが理由です。

注:Roomは、UIを長期間ロックする可能性があるため、ビルダーでallowMainThreadQueries()を呼び出していない限り、メインスレッドでのデータベースアクセスをサポートしていません。非同期クエリ(LiveDataまたはFlowableのインスタンスを返すクエリ)は、必要に応じてバックグラウンドスレッドで非同期にクエリを実行するため、このルールから除外されます。


4

単にあなたはそれを解決するためにこのコードを使うことができます:

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()独自のコードで置き換えることができます。


4

asyncTaskは非推奨であるため、executorサービスを使用できます。または、他の回答で説明されているように、LiveDataで ViewModelを使用することもできます。

executorサービスを使用するには、以下のようなものを使用できます。

public class DbHelper {

    private final Executor executor = Executors.newSingleThreadExecutor();

    public void fetchData(DataFetchListener dataListener){
        executor.execute(() -> {
                Object object = retrieveAgent(agentId);
                new Handler(Looper.getMainLooper()).post(() -> {
                        dataListener.onFetchDataSuccess(object);
                });
        });
    }
}

メインルーパーが使用されるため、onFetchDataSuccessコールバックからUI要素にアクセスできます。


3

エラーメッセージ、

メインスレッドでデータベースにアクセスできません。長期間UIをロックする可能性があるためです。

非常に説明的で正確です。問題は、メインスレッドでデータベースにアクセスしないようにする方法です。これは大きなトピックですが、開始するには、AsyncTaskについて読んでください(ここをクリック)

-----編集----------

単体テストを実行すると問題が発生するようです。これを修正するには、いくつかの選択肢があります。

  1. Androidデバイス(またはエミュレーター)ではなく、開発マシンで直接テストを実行します。これは、データベース中心のテストで機能し、デバイスで実行されているかどうかを実際に気にしません。

  2. アノテーション@RunWith(AndroidJUnit4.class) を使用 してAndroidデバイスでテストを実行しますが、UIを使用するアクティビティでは実行しません。これに関する詳細は、このチュートリアルにあります


私はあなたの要点を理解しました。私の前提は、JUnitを介してdb操作をテストしようとしたときにも同じことが当てはまるということです。ただし、developer.android.com / topic / libraries / architecture / room.htmlでは、サンプルテストメソッドwriteUserAndReadInListがバックグラウンドスレッドで挿入クエリを呼び出しません。ここに何か欠けていますか?提案してください。
Devarshi

これが問題のあるテストであるという事実を見逃してしまい申し訳ありません。回答を編集して、さらに情報を追加します。
デイルウィルソン

3

非同期タスクに慣れている場合:

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

2

更新: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は必要ありません。


1

クイッククエリの場合は、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);
        }
    }

1

FutureとCallableを使用できます。したがって、長いasynctaskを記述する必要はなく、allowMainThreadQueries()を追加せずにクエリを実行できます。

私のdaoクエリ:-

@Query("SELECT * from user_data_table where SNO = 1")
UserData getDefaultData();

私のリポジトリメソッド:-

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

Androidではクエリをメインスレッドで実行できないため、これがCallable / Futureを使用している理由です。上記の質問で尋ねられたとおり
初心者の

1
つまり、回答のコードはバックグラウンドスレッドでクエリを作成しますが、メインスレッドはブロックされ、クエリが完了すると待機します。つまり、結局のところ、それはそれほど良くはありませんallowMainThreadQueries()。メインスレッドはどちらの場合でもブロックされます
eugeneek '17

0

私の意見では、正しいことは、RxJavaを使用してクエリをIOスレッドに委任することです。

私が今遭遇した同等の問題の解決策の例があります。

((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.VISIBLE);//Always good to set some good feedback
        Completable.fromAction(() -> {
            //Creating view model requires DB access
            homeViewModel = new ViewModelProvider(this, factory).get(HomeViewModel.class);
        }).subscribeOn(Schedulers.io())//The DB access executes on a non-main-thread thread
        .observeOn(AndroidSchedulers.mainThread())//Upon completion of the DB-involved execution, the continuation runs on the main thread
        .subscribe(
                () ->
                {
                    mAdapter = new MyAdapter(homeViewModel.getExams());
                    recyclerView.setAdapter(mAdapter);
                    ((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.INVISIBLE);
                },
                error -> error.printStackTrace()
        );

そして、ソリューションを一般化したい場合:

((ProgressBar) view.findViewById(R.id.progressBar_home)).setVisibility(View.VISIBLE);//Always good to set some good feedback
        Completable.fromAction(() -> {
            someTaskThatTakesTooMuchTime();
        }).subscribeOn(Schedulers.io())//The long task executes on a non-main-thread thread
        .observeOn(AndroidSchedulers.mainThread())//Upon completion of the DB-involved execution, the continuation runs on the main thread
        .subscribe(
                () ->
                {
                    taskIWantToDoOnTheMainThreadWhenTheLongTaskIsDone();
                },
                error -> error.printStackTrace()
        );
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.