This is the fourth day of my participation in the November Gwen Challenge. Check out the details: The last Gwen Challenge 2021
1) Low-key CoroutinLiveData
liveData {
this.emit("data") / / 1
}.observe(owner){
data -> doSomething(data)/ / 2
}
Copy the code
Start with a piece of code
- The liveData() global method, whose return value is a liveData so we can listen directly for its return value.
- The final argument is a Lambda expression with a receiver whose receiver is
LiveDataScope,
You can call its EMIT method directly in the code block (1) to send values to LiveData. - The CoroutineLiveData method builds a CoroutineLiveData return.
public fun <T> liveData(
context: CoroutineContext = EmptyCoroutineContext,
timeout: Duration.@BuilderInference block: suspend LiveDataScope<T>. () - >Unit
): LiveData<T> = CoroutineLiveData(context, timeout.toMillis(), block)
Copy the code
CoroutineLiveData inherits MediatorLiveData (MediatorLiveData is covered in the next section)
1.1) Is the code in a block executed more than once?
- The code in the block is executed only after LiveData is marked active, and normally only once. The switch from inactive to active status of LiveData occurs when the active observers registered with it change from 0 to 1.
- When the status of LiveData changes from active to inactive, it will initiate a task to cancel the code block, but there will be a time interval, after which LiveData has not returned to active, it will be canceled. The default timeout is 5s.
- Tasks cancelled for the above reasons will be re-executed. Once executed successfully, it will not be executed again.
Internal implementation code has been cut to retain only key parts.
internal class CoroutineLiveData<T>(
context: CoroutineContext = EmptyCoroutineContext,
timeoutInMs: Long = DEFAULT_TIMEOUT,
block: Block<T>
) : MediatorLiveData<T>() {
private var blockRunner: BlockRunner<T>?
init {
val supervisorJob = SupervisorJob(context[Job])
val scope = CoroutineScope(Dispatchers.Main.immediate + context + supervisorJob)
// Comment 3. A BlockRunner object is built when the object is created
blockRunner = BlockRunner( liveData = this,block = block,timeoutInMs = timeoutInMs, scope = scope)
{
blockRunner = null// The last argument to comment 4 is, which is executed at the end of the code block execution.}}override fun onActive(a) {
super.onActive() blockRunner? .maybeRun()// Comment 5 attempts to execute code blocks if active, but not if blockRunner is empty}}internal class BlockRunner<T>(..) {
private var runningJob: Job? = null
@MainThread
fun maybeRun(a) {
if(runningJob ! =null) return // Comment 7. If runningJob is not empty it will not be executed.
runningJob = scope.launch {
block(liveDataScope)// Execute the code block
onDone()
}
}
}
Copy the code
-
The running block of code is wrapped in the BlockRunner inner class. The last argument to the constructor is executed at the end of the block execution, which means that the BlockRunner object is set to NULL. It will not be executed when LiveData is active again.
-
Once the task is cancelled, the runningJob is null at comment 8, and the block is re-executed at Comment 5 when LiveData becomes active again.
1.2) How is the cancelled timeout mechanism implemented?
# BlockRunner
fun cancel(a) { //LiveData onInactive() is called
cancellationJob = scope.launch(Dispatchers.Main.immediate) {
delay(timeoutInMs) // Comment 10
if(! liveData.hasActiveObservers()) { runningJob? .cancel() runningJob =null / / comment 11}}}fun maybeRun(a){ cancellationJob? .cancel()/ / comment 11
cancellationJob = nullrunningJob? .cancel() ... }Copy the code
- Start a fetching coroutine task that first has a wait during which the logic below comment 10 is not executed.
- At comment 11, if there is a cancellation task before the code block is executed, if so, the cancellation task will be terminated
2) Versatile MediatorLiveData
2.1) Use MediatorLiveData to monitor data changes of other LiveData.
- MediatorLiveData #
addSource(LiveData<S> source, Observer<? super S> onChanged)
When the sourceLiveData data changes, the onChange method of the onChanged Observer is called back, and we can choose to set the data to MediatorLiveData in this callback. - Why is the generic declaration of the onChanged parameter
, what does that mean? See another article at juejin.cn/post/702956… public <S> void removeSource(LiveData<S> toRemote)
This method stops listening on a LiveData.
2.2) Internal implementation
private static class Source<V> implements Observer<V> {
final LiveData<V> mLiveData;
final Observer<? super V> mObserver;
int mVersion = START_VERSION;
void plug(a) { mLiveData.observeForever(this); }
void unplug(a) { mLiveData.removeObserver(this); }
@Override
public void onChanged(@Nullable V v) {
if(mVersion ! = mLiveData.getVersion()) {// Compare versionsmVersion = mLiveData.getVersion(); mObserver.onChanged(v); }}}Copy the code
- The internal implementation is also relatively simple. MediatorLiveData’s internal addSource method wraps LiveData and Observer as a Source, as shown in the code section above.
- Source implements an Observer, and its onChanged method will have a method called Observer (onChanged) passed by The addSource method of Tianjian.
3) Small transparent SavingStateLiveData
The location of this class in the SavedStateHandle class of Lifecycle – ViewModel-SavedState is also inherited from MutableLiveData, which differs from MutableLiveData in that it has two more attributes.
private String mKey;
private SavedStateHandle mHandle;
Copy the code
The purpose is to ensure that the Viewmodel in which the LiveData resides can retrieve the data before destruction when it is rebuilt. The specific principle is described in detail in another article. Juejin. Cn/post / 702635…
4) summary
This article introduces the more characteristic subclasses of LiveData in Jetpack.
- CoroutinLiveData comes with the host ViewModel
- MediatorLiveData can listen for changes in multiple LiveData simultaneously.
- SavingStateLiveData Automatically restores the data before destruction during reconstruction.
Statement: forward please comment contact, and note the source!!