web-dev-qa-db-ja.com

java.lang.IllegalStateException:バックグラウンドスレッドでobserveForeverを呼び出せません

誰かが私がここで間違っているところを見つけるのを助けてくれますか?ワーカーからのデータ変更があるたびに、ネットワークデータを継続的に監視し、UIを更新する必要があります。これはandroidxにアップグレードする前に機能していたことに注意してください。

これがWorkerクラスです。

class TestWorker(val context: Context, val params: WorkerParameters): Worker(context, params){

    override fun doWork(): Result {
        Log.d(TAG, "doWork called")
        val networkDataSource = Injector.provideNetworkDataSource(context)
        networkDataSource.fetchData(false)

        return Worker.Result.SUCCESS
    }

    companion object {
        private const val TAG = "MY_WORKER"
    }

}

これは次のように呼ばれます:

fun scheduleRecurringFetchDataSync() {
    Log.d("FETCH_SCHEDULER", "Scheduling started")

    val fetchWork = PeriodicWorkRequest.Builder(TestWorker::class.Java, 1, TimeUnit.MINUTES)
            .setConstraints(constraints())
            .build()
    WorkManager.getInstance().enqueue(fetchWork)
}

private fun constraints(): Constraints{
    return Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .setRequiresBatteryNotLow(true)
            .build()
}

また、データをフェッチして保存するためのUserDaoとUserRepositoryもあります。次のように、UserRepositoryのネットワークデータを監視しています。

class UserRepository (
    private val userDao: UserDao,
    private val networkDataSource: NetworkDataSource,
    private val appExecutors: AppExecutors){

init {
    val networkData= networkDataSource.downloadedData
    networkData.observeForever { newData->
        appExecutors.diskIO().execute {
            userDao.insert(newData.user)
        }
    }}

誰かが私が間違っているところを見つけるのを手伝ってくれる?これにより、次のようなエラーが発生します。

Java.lang.IllegalStateException: Cannot invoke observeForever on a background thread
    at androidx.lifecycle.LiveData.assertMainThread(LiveData.Java:443)
    at androidx.lifecycle.LiveData.observeForever(LiveData.Java:204)
    at com.example.app.data.repo.UserRepository.<init>(UserRepository.kt:17)
    at com.example.app.data.repo.UserRepository$Companion.getInstance(UserRepository.kt:79)
8
FreddCha

これを変える:

_networkData.observeForever { newData->
    appExecutors.diskIO().execute {
        userDao.insert(newData.user)
    }
}
_

これに:

_Handler(Looper.getMainLooper()).post {
    networkData.observeForever { newData->
        appExecutors.diskIO().execute {
            userDao.insert(newData.user)
        }
    }
}
_

説明

通常、observe(..)およびobserveForever(..)は、メインスレッドから呼び出す必要があります。コールバック(Observer<T>.onChanged(T t))は、メインスレッドでのみ可能なUIを変更することが多いためです。 Androidが、監視関数の呼び出しがメインスレッドによって行われたかどうかをチェックする理由です。そうでない場合、例外がスローされます(_IllegalStateException: Cannot invoke observeForever on a background thread_)。あなたの場合_UserRepository.init{}_はバックグラウンドスレッドによって呼び出されるため、例外がスローされます。メインスレッドに戻るには、Handler(Looper.getMainLooper()).post {}を呼び出してRunnableをメインスレッドにポストできます。ただし、コードに注意してくださいオブザーブコールバックの内部もメインスレッドによって実行されます。このコールバック内の高額な処理は、UIを凍結します。

5
user1185087

別のソリューションでは、メインディスパッチャーから次のように呼び出すことができます。

GlobalScope.launch(Dispatchers.Main) {
  // your code here...
}
4
Ercan

プロジェクトでRxJavaを使用している場合、@ user1185087からの詳細で詳細な回答に加えて、ここに解決策があります。それほど短いとは言えないかもしれませんが、プロジェクトですでにRxJavaを使用している場合は、必要なスレッド(この場合は.observeOn(AndroidSchedulers.mainThread())を介したAndroidのUIスレッド)に切り替えるためのエレガントな方法です。

Observable.just(workManager.getStatusById(workRequest.getId()))
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(status -> status.observeForever(workStatus -> {
        // Handling result on UI thread
    }), err -> Log.e(TAG, err.getMessage(), err));
1
heisenberg