Androidでは、Jetpackアーキテクチャコンポーネントと一緒に使用することが公式に推奨されています
(1)コルーチンの紹介
- 実装 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
(2)JetpackアーキテクチャコンポーネントのKTX拡張の導入
- ViewModelScopeの場合は、androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0-beta01以降を使用してください。
- LifecycleScopeの場合は、androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01以降を使用してください。
- liveDataの場合は、androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha01以降を使用してください。
ViewModelScope
アプリケーションの各ViewModelはViewModelScopeを定義します。ViewModelがクリアされている場合、この範囲で開始されたコルーチンは自動的にキャンセルされます。ViewModelがアクティブなときにのみ実行する必要がある作業がある場合、現時点ではコルーチンが非常に役立ちます。たとえば、レイアウトの一部のデータを計算する場合は、作業範囲をViewModelに制限する必要があります。これにより、ViewModelがクリアされた後、システムは作業を自動的にキャンセルして、リソースの消費を回避します。
class MyViewModel: ViewModel() {
init {
viewModelScope.launch {
// Coroutine that will be canceled when the ViewModel is cleared.
}
}
}
LifecycleScope
LifecycleScopeは、Lifecycleオブジェクトごとに定義されます。この範囲内で開始されたコルーチンは、ライフサイクルが破棄されるとキャンセルされます。CoroutineScope of Lifecycleには、lifecycle.coroutineScopeプロパティまたはlifecycleOwner.lifecycleScopeプロパティからアクセスできます。
次の例は、lifecycleOwner.lifecycleScopeを使用して、事前に計算されたテキストを非同期で作成する方法を示しています。
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
val params = TextViewCompat.getTextMetricsParams(textView)
val precomputedText = withContext(Dispatchers.Default) {
PrecomputedTextCompat.create(longTextContent, params)
}
TextViewCompat.setPrecomputedText(textView, precomputedText)
}
}
}
LifecycleScope +ライフサイクルステータス
ライフサイクルは他のメソッドを提供します:lifecycle.whenCreated、lifecycle.whenStarted、lifecycle.whenResumed。ライフサイクルが最低限必要な状態でない場合、これらのブロックで実行されているコルーチンはすべて中断されます。
次の例には、関連するライフサイクルが少なくともSTARTEDの場合にのみ実行されるコードブロックが含まれています。
class MyFragment: Fragment {
init { // Notice that we can safely launch in the constructor of the Fragment.
lifecycleScope.launch {
whenStarted {
// The block inside will run only when Lifecycle is at least STARTED.
// It will start executing when fragment is started and
// can call other suspend methods.
loadingView.visibility = View.VISIBLE
val canAccess = withContext(Dispatchers.IO) {
checkUserAccess()
}
// When checkUserAccess returns, the next line is automatically
// suspended if the Lifecycle is not *at least* STARTED.
// We could safely run fragment transactions because we know the
// code won't run unless the lifecycle is at least STARTED.
loadingView.visibility = View.GONE
if (canAccess == false) {
findNavController().popBackStack()
} else {
showContent()
}
}
// This line runs only after the whenStarted block above has completed.
}
}
}
コルーチンがアクティブなときにライフサイクルが特定のwhenメソッドによって破棄されると、コルーチンは自動的にキャンセルされます。次の例では、ライフサイクルステータスがDESTROYEDになると、finallyブロックが実行されます。
class MyFragment: Fragment {
init {
lifecycleScope.launchWhenStarted {
try {
// Call some suspend functions.
} finally {
// This line might execute after Lifecycle is DESTROYED.
if (lifecycle.state >= STARTED) {
// Here, since we've checked, it is safe to run any
// Fragment transactions.
}
}
}
}
}
LifecycleScope +ライフサイクル状態の使用状況の概要
通过一种声明式的写法(即在Activity或者Fragment的初始化时,声明好各个生命周期需要做的事情),而不是程序性的写法(即在各个生命周期回调方法中处理相应逻辑),更有利于整体代码结构的把握,更易于理解和维护。所以建议大家多使用这种具有声明式的写法处理业务逻辑。
LiveDataでコルーチンを使用する
liveDataビルダー関数を使用して、suspend関数を呼び出し、その結果をLiveDataオブジェクトとして転送できます。
次の例では、loadUser()は別の場所で宣言された一時停止関数です。liveDataビルダー関数を使用してloadUser()を非同期で呼び出し、次に出力()を使用して結果を発行します。
val user: LiveData<User> = liveData {
val data = database.loadUser() // loadUser is a suspend function.
emit(data)
}
LiveDataがアクティブになると、コードブロックの実行が開始され、LiveDataが非アクティブになると、構成可能なタイムアウト後にコードブロックが自動的にキャンセルされます。コードブロックが完了する前にキャンセルされた場合、LiveDataが再びアクティブになった後に再起動します。前回の実行で正常に完了した場合、再起動しません。コードブロックは、自動的にキャンセルされた場合にのみ再起動することに注意してください。コードブロックが他の理由でキャンセルされた場合(たとえば、CancerationExceptionがスローされた場合)、コードブロックは再起動しません。
コードブロックから複数の値を発行することもできます。各発信()呼び出しは、コードブロックの実行を中断します。
val user: LiveData<Result> = liveData {
emit(Result.loading())
try {
emit(Result.success(fetchUser()))
} catch(ioException: Exception) {
emit(Result.error(ioException))
}
}
次の例に示すように、変換でliveDataを使用することもできます。
class MyViewModel: ViewModel() {
private val userId: LiveData<String> = MutableLiveData()
val user = userId.switchMap { id ->
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
emit(database.loadUserById(id))
}
}
}
新しい値を出力するたびにemitSource()関数を呼び出すことにより、LiveDataから複数の値を出力できます。emit()またはemitSource()を呼び出すたびに、以前に追加されたソースが削除されることに注意してください。
class UserDao: Dao {
@Query("SELECT * FROM User WHERE id = :id")
fun getUser(id: String): LiveData<User>
}
class MyRepository {
fun getUser(id: String) = liveData<User> {
val disposable = emitSource(
userDao.getUser(id).map {
Result.loading(it)
}
)
try {
val user = webservice.fetchUser(id)
// Stop the previous emission to avoid dispatching the updated user
// as `loading`.
disposable.dispose()
// Update the database.
userDao.insert(user)
// Re-establish the emission with success type.
emitSource(
userDao.getUser(id).map {
Result.success(it)
}
)
} catch(exception: IOException) {
// Any call to `emit` disposes the previous one automatically so we don't
// need to dispose it here as we didn't get an updated value.
emitSource(
userDao.getUser(id).map {
Result.error(exception, it)
}
)
}
}
}
参照:
Android公式Webサイトの紹介:
Kotlinコルーチンを使用してアプリケーションのパフォーマンスを向上させ、Kotlinコルーチンをアーキテクチャコンポーネントで
使用する