1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package androidx.lifecycle 17 18 import androidx.annotation.RestrictTo 19 import androidx.annotation.VisibleForTesting 20 import androidx.annotation.WorkerThread 21 import androidx.arch.core.executor.ArchTaskExecutor 22 import java.util.concurrent.Executor 23 import java.util.concurrent.atomic.AtomicBoolean 24 25 /** 26 * A LiveData class that can be invalidated & computed when there are active observers. 27 * 28 * It can be invalidated via [invalidate], which will result in a call to [compute] if there are 29 * active observers (or when they start observing) 30 * 31 * This is an internal class for now, might be public if we see the necessity. 32 * 33 * @param <T> The type of the live data 34 */ 35 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX) 36 public abstract class ComputableLiveData<T> 37 @JvmOverloads 38 /** 39 * Creates a computable live data that computes values on the specified executor or the arch IO 40 * thread executor by default. 41 * 42 * @param executor Executor that is used to compute new LiveData values. 43 */ 44 constructor(internal val executor: Executor = ArchTaskExecutor.getIOThreadExecutor()) { 45 46 private val _liveData: LiveData<T?> = 47 object : LiveData<T?>() { onActivenull48 override fun onActive() { 49 executor.execute(refreshRunnable) 50 } 51 } 52 /** The LiveData managed by this class. */ 53 public open val liveData: LiveData<T?> = _liveData 54 internal val invalid = AtomicBoolean(true) 55 internal val computing = AtomicBoolean(false) 56 57 @JvmField 58 @VisibleForTesting <lambda>null59 internal val refreshRunnable = Runnable { 60 var computed: Boolean 61 do { 62 computed = false 63 // compute can happen only in 1 thread but no reason to lock others. 64 if (computing.compareAndSet(false, true)) { 65 // as long as it is invalid, keep computing. 66 try { 67 var value: T? = null 68 while (invalid.compareAndSet(true, false)) { 69 computed = true 70 value = compute() 71 } 72 if (computed) { 73 liveData.postValue(value) 74 } 75 } finally { 76 // release compute lock 77 computing.set(false) 78 } 79 } 80 // check invalid after releasing compute lock to avoid the following scenario. 81 // Thread A runs compute() 82 // Thread A checks invalid, it is false 83 // Main thread sets invalid to true 84 // Thread B runs, fails to acquire compute lock and skips 85 // Thread A releases compute lock 86 // We've left invalid in set state. The check below recovers. 87 } while (computed && invalid.get()) 88 } 89 90 // invalidation check always happens on the main thread 91 @JvmField 92 @VisibleForTesting <lambda>null93 internal val invalidationRunnable = Runnable { 94 val isActive = liveData.hasActiveObservers() 95 if (invalid.compareAndSet(false, true)) { 96 if (isActive) { 97 executor.execute(refreshRunnable) 98 } 99 } 100 } 101 102 /** 103 * Invalidates the LiveData. 104 * 105 * When there are active observers, this will trigger a call to [.compute]. 106 */ invalidatenull107 public open fun invalidate() { 108 ArchTaskExecutor.getInstance().executeOnMainThread(invalidationRunnable) 109 } 110 111 // TODO https://issuetracker.google.com/issues/112197238 computenull112 @WorkerThread protected abstract fun compute(): T 113 } 114