Kotlin coroutines udostępnia interfejs API, który umożliwia
kodu asynchronicznego. Dzięki współprogramom Kotlin można zdefiniować
CoroutineScope
Pomaga to zarządzać czasem uruchamiania programów. Każda asynchroniczna
w określonym zakresie.
Komponenty uwzględniające cykl życia zapewniają
obsługę pierwszej klasy współrzędnych zakresów logicznych w aplikacji oraz
warstwa interoperacyjności z LiveData
.
Z tego artykułu dowiesz się, jak skutecznie korzystać z współrzędnych z uwzględnieniem cyklu życia
Dodaj zależności KTX
Wbudowane zakresy współrzędne opisane w tym temacie są zawarte w sekcji KTX dla każdego odpowiedniego komponentu. Pamiętaj, aby dodać odpowiednie zależności podczas korzystania z tych zakresów.
- W przypadku
ViewModelScope
użyjandroidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0
lub więcej. - W przypadku
LifecycleScope
użyjandroidx.lifecycle:lifecycle-runtime-ktx:2.4.0
lub więcej. - W przypadku
liveData
użyjandroidx.lifecycle:lifecycle-livedata-ktx:2.4.0
lub więcej.
Zakresy współprogramów uwzględniające cykl życia
Komponenty uwzględniające cykl życia definiują poniższe wbudowane zakresy, których możesz używać w aplikacji.
Zakres modelu widoku
ViewModelScope
jest zdefiniowane dla każdego
ViewModel
w aplikacji. Dowolne
współdziała uruchomiona w tym zakresie jest automatycznie anulowana, jeśli ViewModel
jest wyczyszczony. Corutyny przydają się, gdy masz pracę, która ma zostać
wykonywane tylko wtedy, gdy interfejs ViewModel
jest aktywny. Na przykład, jeśli obliczasz
danych dla układu, zakres pracy powinien obejmować interfejs ViewModel
, tak aby, jeśli
Pole ViewModel
zostało wyczyszczone, a zadanie jest anulowane automatycznie, aby uniknąć użycia
i zasobami Google Cloud.
Aby uzyskać dostęp do CoroutineScope
, ViewModel
:
viewModelScope
właściwości ViewModel, jak w tym przykładzie:
class MyViewModel: ViewModel() {
init {
viewModelScope.launch {
// Coroutine that will be canceled when the ViewModel is cleared.
}
}
}
Zakres cyklu życia
LifecycleScope
jest zdefiniowane dla każdego
Lifecycle
. Dowolny współprogram
Uruchomienie w tym zakresie zostaje anulowane po zniszczeniu obiektu Lifecycle
. Dostępne opcje
uzyskać dostęp do funkcji CoroutineScope
w Lifecycle
za pomocą
usługi lifecycle.coroutineScope
lub lifecycleOwner.lifecycleScope
.
Poniższy przykład pokazuje, jak używać atrybutu lifecycleOwner.lifecycleScope
do
asynchronicznie utwórz wstępnie obliczony tekst:
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)
}
}
}
Współrzędne uwzględniające cykl życia z możliwością ponownego uruchomienia
Mimo że lifecycleScope
zapewnia prawidłowy sposób anulowania subskrypcji
długotrwałe operacje automatycznie, gdy Lifecycle
ma wartość DESTROYED
,
może się zdarzyć, że zechcesz rozpocząć wykonywanie kodu
blokuj, gdy Lifecycle
jest w określonym stanie, i anuluj, gdy jest w
inny stan. Możesz na przykład rejestrować przepływ, gdy
Lifecycle
wynosi STARTED
i anuluj kolekcję, gdy będzie STOPPED
. Ten
metoda podejścia przetwarza emisyjność przepływu tylko wtedy, gdy interfejs użytkownika jest widoczny na ekranie,
oszczędzanie zasobów i potencjalne ryzyko awarii aplikacji.
W takich przypadkach Lifecycle
i LifecycleOwner
zapewniają zawieszenie
repeatOnLifecycle
API, który spełnia te potrzeby. Ten przykład zawiera
blok kodu, który jest uruchamiany za każdym razem, gdy powiązany blok kodu Lifecycle
znajduje się w
STARTED
ma stan i anuluje, gdy Lifecycle
ma wartość STOPPED
:
class MyFragment : Fragment() {
val viewModel: MyViewModel by viewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Create a new coroutine in the lifecycleScope
viewLifecycleOwner.lifecycleScope.launch {
// repeatOnLifecycle launches the block in a new coroutine every time the
// lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Trigger the flow and start listening for values.
// This happens when lifecycle is STARTED and stops
// collecting when the lifecycle is STOPPED
viewModel.someDataFlow.collect {
// Process item
}
}
}
}
}
Kolekcja przepływów z uwzględnieniem cyklu życia
Jeśli potrzebujesz tylko zbierać dane z uwzględnieniem cyklu życia w pojedynczym procesie, możesz
użyj
Flow.flowWithLifecycle()
, by uprościć kod:
viewLifecycleOwner.lifecycleScope.launch {
exampleProvider.exampleFlow()
.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
.collect {
// Process the value.
}
}
Jeśli jednak musisz przeprowadzić zbieranie z uwzględnieniem cyklu życia w wielu przepływach w
równolegle, musisz zbierać każdy przepływ w innych współrzędnych. W takim przypadku
lepiej użyć bezpośrednio funkcji repeatOnLifecycle()
:
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Because collect is a suspend function, if you want to
// collect multiple flows in parallel, you need to do so in
// different coroutines.
launch {
flow1.collect { /* Process the value. */ }
}
launch {
flow2.collect { /* Process the value. */ }
}
}
}
Zawieszanie współrzędnych uwzględniających cykl życia
Mimo że CoroutineScope
zapewnia prawidłowy sposób anulowania subskrypcji
długotrwałe operacje automatycznie, możesz mieć inne przypadki,
aby zawiesić wykonywanie bloku kodu, chyba że Lifecycle
jest w określonej
stanu. Aby na przykład uruchomić polecenie FragmentTransaction
, musisz poczekać na
Lifecycle
ma wartość co najmniej STARTED
. W takich przypadkach Lifecycle
udostępnia
dodatkowe metody: lifecycle.whenCreated
, lifecycle.whenStarted
i
lifecycle.whenResumed
Każde uruchomienie współzależności wewnątrz tych bloków jest zawieszane, jeśli
Lifecycle
nie jest przynajmniej w minimalnym wymaganym stanie.
Przykład poniżej zawiera blok kodu, który działa tylko wtedy, gdy powiązany
Lifecycle
jest co najmniej w stanie 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.
}
}
}
Jeśli Lifecycle
zostanie zniszczony, gdy włączona jest współprogram
when
, współprogram zostanie automatycznie anulowany. W przykładzie poniżej
blok finally
działa, gdy stan Lifecycle
wynosi DESTROYED
:
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.
}
}
}
}
}
Używanie współrzędnych z LiveData
Podczas korzystania z LiveData
może być konieczne
asynchronicznie. Możesz na przykład chcieć pobrać plik
preferencjami użytkownika i wyświetlać je w Twoim interfejsie. W
w takich przypadkach możesz użyć funkcji konstruktora liveData
, aby wywołać suspend
.
, udostępniając wynik jako obiekt LiveData
.
W poniższym przykładzie loadUser()
to funkcja zawieszenia zadeklarowana w innym miejscu. Używaj
funkcję konstruktora liveData
, aby asynchronicznie wywołać loadUser()
, a następnie
użyj emit()
do wygenerowania wyniku:
val user: LiveData<User> = liveData {
val data = database.loadUser() // loadUser is a suspend function.
emit(data)
}
Element składowy liveData
służy jako
podstawowy obiekt równoczesności strukturalnej
między współrzędnymi a LiveData
. Blok kodu rozpoczyna się, gdy:
Usługa LiveData
staje się aktywna i zostaje automatycznie anulowana po skonfigurowaniu
gdy LiveData
przestanie być aktywne. Jeśli zostanie anulowana przed
zostanie uruchomiony ponownie, jeśli LiveData
stanie się ponownie aktywny. Jeśli
udało się ukończyć poprzednie uruchomienie i nie uruchamia się ponownie. Pamiętaj, że
jest uruchamiany ponownie tylko w przypadku automatycznego anulowania. Jeśli blokada zostanie anulowana w przypadku innego
(np. zgłoszenie CancellationException
), nie uruchamia się ponownie.
Możesz także emitować wiele wartości z bloku. Każde połączenie emit()
jest zawieszane
wykonywanie bloku do momentu ustawienia w wątku głównym wartości LiveData
.
val user: LiveData<Result> = liveData {
emit(Result.loading())
try {
emit(Result.success(fetchUser()))
} catch(ioException: Exception) {
emit(Result.error(ioException))
}
}
Możesz też połączyć liveData
z
Transformations
, jak widać tutaj
następujący przykład:
class MyViewModel: ViewModel() {
private val userId: LiveData<String> = MutableLiveData()
val user = userId.switchMap { id ->
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
emit(database.loadUserById(id))
}
}
}
Możesz wysyłać wiele wartości z LiveData
, wywołując funkcję emitSource()
za każdym razem, gdy chcesz wygenerować nową wartość. Pamiętaj, że każde wywołanie funkcji emit()
lub emitSource()
usuwa wcześniej dodane źródło.
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)
}
)
}
}
}
Więcej informacji o współrzędnych znajdziesz na tych stronach:
- Zwiększanie wydajności aplikacji dzięki współrzędnym Kotlin
- Omówienie konwersji
- Wątki w CooutineWorker
Dodatkowe materiały
Aby dowiedzieć się więcej o używaniu współrzędnych z komponentami uwzględniającymi cykl życia, skontaktuj się z poniższe dodatkowe materiały.
Próbki
Blogi
- Coroutines na Androidzie: wzorce aplikacji
- Proste współprogramy w Androidzie: viewModelScope
- Testowanie 2 kolejnych emisji LiveData w współrzędnych
Polecane dla Ciebie
- Uwaga: tekst linku wyświetla się, gdy JavaScript jest wyłączony
- Omówienie LiveData
- Obsługa cykli życia za pomocą komponentów uwzględniających cykl życia
- Wczytywanie i wyświetlanie danych z podziałem na strony