LiveData 相关问题是面试高频题,因为他具有生命周期感知能力,支持黏性事件,采用了观察者模式,某种程度上也可以用作事件总线。
根据官方文档我们知道它的主要作用:数据持有存储器类、可观察类、具有生命周期感知能力。
问题来了:
-
LiveData 怎么感知生命周期感知?需要取消注册吗?
-
setValue 和 postValue 有什么区别
-
设置相同的值,订阅的观察者们会收到同样的值吗
-
粘性事件原理,怎么防止数据倒灌
-
observeForever怎么用
基本使用
====
MainRepository
class MainRepository {
suspend fun getNameList(): List {
// 获取数据切到工作线程,模拟网络请求过程
return withContext(Dispatchers.IO) {
listOf(“张三”, “李四”)
}
}
}
MainViewModel
class MainViewModel: ViewModel() {
private val nameList = MutableLiveData<List>()
val nameListResult: LiveData<List> = nameList
private val mainRepository = MainRepository()
fun getNames() {
// 使用协程模拟请求过程
viewModelScope.launch {
nameList.value = mainRepository.getNameList()
}
}
}
MainActivity
class MainActivity : AppCompatActivity() {
// 创建 ViewModel 方式 1
// 通过 kotlin 委托特性创建 ViewModel
// 需添加依赖 implementation ‘androidx.activity:activity-ktx:1.2.3’
// viewModels() 内部也是通过 创建 ViewModel 方式 2 来创建的 ViewModel
private val mainViewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate (savedInstanceState)
setContentView(R.layout.activity_main)
// 创建 ViewModel 方式 2
val mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
// 通过 LiveData.observe 订阅观察者
mainViewModel.nameListResult.observe(this, {
Log.i(“MainActivity”, “mainViewModel: nameListResult: $it”)
})
mainViewModel.getNames()
}
}
打开app -> 正常看到日志
18:03:02.575 : mainViewModel: nameListResult: [张三, 李四]
源码解析
====
LiveData 通过 observe 方法来订阅观察者,以此为查看源码的入口:
方法注释包含的知识很多,请认真阅读注释 (百度翻译过来的)。
private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =
new SafeIterableMap<>();
/**
-
1、在给定 owner 的生命周期内,将给定的观察者添加到观察者列表中。
-
2、事件在主线程上调度。
-
3、如果LiveData已经有数据集,它将被交付给观察者。
-
-
4、仅当所有者处于{@link Lifecycle.State#STARTED}或{@link Lifecycle.State#RESUME}状态
*(活动)时,观察者才会接收事件。
-
-
5、如果所有者移动到{@link Lifecycle.State#DETROYED}状态,观察者将自动被删除。
-
-
6、当数据在{@code owner}未激活时发生更改时,它将不会收到任何更新。
-
7、如果它再次激活,它将自动接收最后可用的数据。
-
-
8、只要给定的LifecycleOwner未被销毁,LiveData就会保留对观察者和所有者的强引用。
-
9、销毁后,LiveData将删除对所有者的引用。
-
-
10、如果给定的所有者已经处于{@link Lifecycle.State#DESTROYED}状态,LiveData将忽略该调用。
-
-
11、如果给定的所有者、观察者元组已经在列表中,则忽略该调用。
-
12、如果观察者已经列表中,LiveData将抛出@link IllegalArgumentException}。
-
@param owner 控制观察者的生命周期所有者
-
@param observer 将接收事件的观察者
*/
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
assertMainThread(“observe”);
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
// owner 的状态是 DESTROYED 则不往下走。对应方法注释的第 10 点。
return;
}
// 创建 owner 和 observer 的包装对象 LifecycleBoundObserver
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
// observer 对象作为 Key,包装对象 wrapper 作为 value,存进 Map 结构对象 mObservers
// putIfAbsent 方法:若 Map 里有这个key,则返回对应的 Value 值。
// 仅当 key 不存在时才会将 key value 存进 Map
// put 进去的是 LifecycleBoundObserver 类型对象,返回的是 ObserverWrapper 对象,
// 不难猜出 LifecycleBoundObserver 是 ObserverWrapper 的子类或者实现类
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
// 若 existing 不为null, 并且 existing 中的 owner 对象和 observe 方法传入的对象不是同一个对象,
// 则抛出异常,不可以添加同一个 Observer 到两个不同的生命周期对象。对应方法注释的第 12 点。
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException(“Cannot add the same observer”
- " with different lifecycles");
}
if (existing != null) {
// 若 existing 不为 null,则忽略该调用。对应方法注释的第 12 点。
return;
}
// 将 owner 和 observer 的包装对象添加到 owner.getLifecycle()。对应方法注释的第 1 点。
owner.getLifecycle().addObserver(wrapper);
}
第一个问题中 LiveData 怎么感知生命周期感知?
我们可以回答了,调用 observe 方法时,会调用 owner.getLifecycle().addObserver 已达到感知生命周期的目的。 observe 方法的内容很少,接着看下 owner 和 observer 的包装对象 LifecycleBoundObserver
LiveData 的内部类
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
@NonNull
final LifecycleOwner mOwner;
// 构造函数 owner 赋值给属性 mOwner, observer 则调用了 super
LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
super(observer);
mOwner = owner;
}
@Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
/**
- 当 source (也就是 mOwner) 的生命周期改变时会回调此方法
*/
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
// 获取当前 mOwner 的生命周期状态
Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
if (currentState == DESTROYED) {
// 如果是已销毁状态,则调用 LiveData#removeObserver 方法进行移除 mObserver (mObserver 是 LifecycleBoundObserver 的父类 ObserverWrapper 的属性,通过 ObserverWrapper 的构造函数来赋值),对应 LiveData#observe 方法注释的第 12 点。
removeObserver(mObserver);
return;
}
Lifecycle.State prevState = null;
while (prevState != currentState) {
prevState = currentState;
activeStateChanged(shouldBeActive());
currentState = mOwner.getLifecycle().getCurrentState();
}
}
@Override
boolean isAttachedTo(LifecycleOwner owner) {
return mOwner == owner;
}
@Override
void detachObserver() {
// 从 mOwner 中移除此观察者
mOwner.getLifecycle().removeObserver(this);
}
}
onStateChanged 方法中注释如果是已销毁状态,则调用 LiveData#removeObserver 方法进行移除 mObserver (mObserver 是 LifecycleBoundObserver 的父类 ObserverWrapper 的属性,通过 ObserverWrapper 的构造函数来赋值),对应 LiveData#observe 方法注释的第 12 点。 同时第一个问题已经得到了解决: **LiveData 怎么感知生命周期感知?需要取消注册吗?**LiveData#observe 看完啦,该看 setValue 和 postValue 了。
/**
-
Sets the value. If there are active observers, the value will be dispatched to them.
-
设置值。如果存在活动的观察者,则会将值分派给他们。
-
-
This method must be called from the main thread. If you need set a value from a background
-
thread, you can use {@link #postValue(Object)}
-
必须从主线程调用此方法。如果需要从后台线程设置值,可以使用{@link#postValue(Object)}
-
@param value The new value
*/
@MainThread
protected void setValue(T value) {
// 检查当前线程是否是主线程,若非主线程则会抛异常
assertMainThread(“setValue”);
// 很关键的 mVersion ,在这里进行 + 1 操作
mVersion++;
// 将新值赋值给属性 mData
mData = value;
// 分发值 (后面再看它具体实现)
dispatchingValue(null);
}
关键哟:setValue 必须在主线程中调用
/**
-
Posts a task to a main thread to set the given value. So if you have a following code
-
executed in the main thread:
-
将任务发布到主线程以设置给定值。因此,如果在主线程中执行以下代码:
-
liveData.postValue(“a”);
-
liveData.setValue(“b”);
-
The value “b” would be set at first and later the main thread would override it with
-
the value “a”.
-
首先设置值“b”,然后主线程将用值“a”覆盖它。(等我们看完了 postValue 具体怎么做的,这个官方小示例就能明白了)
-
-
If you called this method multiple times before a main thread executed a posted task, only
-
the last value would be dispatched.
-
如果在主线程执行已发布任务之前多次调用此方法,则只会调度最后一个值。
-
@param value The new value
*/
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
// 新值赋值给属性 mPendingData
mPendingData = value;
}
// 当 mPendingData == NOT_SET 时,才会往下走
if (!postTask) {
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
看似是把 mPostValueRunnable 这 Runnable 对象发送到主线程?
private final Runnable mPostValueRunnable = new Runnable() {
@SuppressWarnings(“unchecked”)
@Override
public void run() {
Object newValue;
synchronized (mDataLock) {
// 将刚赋值新值的 mPendingData 赋值给对象 newValue
newValue = mPendingData;
// 将 mPendingData 重置为 NOT_SET
// 回头看看 postValue 会明白为啥要这么做
mPendingData = NOT_SET;
}
// 嘶 ~ 在这儿调用了 setValue,
setValue((T) newValue);
}
};
这时候看出来 postValue 最终还是调用了 setValue 再看下 postToMainThread
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public class ArchTaskExecutor extends TaskExecutor {
@NonNull
private TaskExecutor mDelegate;
private ArchTaskExecutor() {
mDefaultTaskExecutor = new DefaultTaskExecutor();
mDelegate = mDefaultTaskExecutor;
}
@Override
public void postToMainThread(Runnable runnable) {
mDelegate.postToMainThread(runnable);
}
}
由 DefaultTaskExecutor 对象调用的 postToMainThread
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
public class DefaultTaskExecutor extends TaskExecutor {
@Nullable
private volatile Handler mMainHandler;
@Override
public void postToMainThread(Runnable runnable) {
if (mMainHandler == null) {
synchronized (mLock) {
if (mMainHandler == null) {
// MainLooper 主线程的 Looper 呢
mMainHandler = createAsync(Looper.getMainLooper());
}
}
}
//noinspection ConstantConditions
mMainHandler.post(runnable);
}
private static Handler createAsync(@NonNull Looper looper) {
if (Build.VERSION.SDK_INT >= 28) {
return Handler.createAsync(looper);
}
if (Build.VERSION.SDK_INT >= 16) {
try {
return Handler.class.getDeclaredConstructor(Looper.class, Handler.Callback.class,
boolean.class)
.newInstance(looper, null, true);
} catch (IllegalAccessException ignored) {
} catch (InstantiationException ignored) {
} catch (NoSuchMethodException ignored) {
} catch (InvocationTargetException e) {
return new Handler(looper);
}
}
return new Handler(looper);
}
}
最终是通过 Handler 的 post ~~ 太熟悉了。
看代码得知 Handler 的 Looper 是 MainLooper
So LiveData#postValue 最终使用主线程将新 value 分发给观察者。意味着我们可以在任何线程调用 postValue,而不用担心线程问题。因为 LiveData 内部做好了线程转换。
第二个问题:2. setValue 和 postValue 有什么区别
简单来说:setValue 只能在主线程使用,而 postValue 不限制线程。
由上知道了:setValue 调用了 dispatchingValue, postValue 调用了 setValue 所以最终也是调用 dispatchingValue
void dispatchingValue(@Nullable ObserverWrapper initiator) {
// mDispatchingValue 是一个标记,为 true 表示正在分发 value,
if (mDispatchingValue) {
mDispatchInvalidated = true;
return;
}
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if (initiator != null) {
// 1.
considerNotify(initiator);
initiator = null;
} else {
// 2.
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}
从 setValue 中看到调用 dispatchingValue 时传入的参数是 null 所以我们先看注释第 2 点的情况,使用迭代器获取 ObserverWrapper 对象传到 considerNotify 方法。
private void considerNotify(ObserverWrapper observer) {
// 观察者处于不活跃状态时,不往下走了
if (!observer.mActive) {
return;
}
// Check latest state b4 dispatch. Maybe it changed state but we didn’t get the event yet.
//
// we still first check observer.active to keep it as the entrance for events. So even if
// the observer moved to an active state, if we’ve not received that event, we better not
// notify for a more predictable notification order.
// 分发的时候判断当前观察者是否不活跃了
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
推荐学习资料
-
Android进阶学习全套手册
-
Android对标阿里P7学习视频
-
BAT TMD大厂Android高频面试题
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**
[外链图片转存中…(img-BNLuq5WR-1713771075886)]
[外链图片转存中…(img-bNE94dbm-1713771075888)]
[外链图片转存中…(img-atqD0Oeo-1713771075889)]
[外链图片转存中…(img-zfOoKcFm-1713771075890)]
[外链图片转存中…(img-4WYbsR0p-1713771075891)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
[外链图片转存中…(img-pQrKJbgF-1713771075892)]
推荐学习资料
-
Android进阶学习全套手册
[外链图片转存中…(img-ynxcOfX4-1713771075893)]
-
Android对标阿里P7学习视频
[外链图片转存中…(img-R7I78ZYO-1713771075895)]
-
BAT TMD大厂Android高频面试题
[外链图片转存中…(img-Q3rcPVes-1713771075896)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!