-
Notifications
You must be signed in to change notification settings - Fork 121
UnPeekLiveData v5 设计思路
KunMinX edited this page Jun 12, 2022
·
4 revisions
感谢 “腾讯音乐” 小伙伴 @zhangjianlaoda 贡献的重构优化代码。
该版本保留 UnPeekLiveData v4 下述几大特点,且在适当时机基于反射等机制,彻底解决 UnPeekLiveData v4 下 Observers 无法释放、重复创建,及 foreverObserver、removeObserver 被禁用等问题,将 UnPeekLiveData 内存性能再往上提升一阶梯。
同时,该版本使 Observe 等方法方法名和形参列表与官方 API 保持一致,尽可能减少学习成本。
具体可参见 UnPeekLiveData 最新源码及注释说明。
package com.kunminx.architecture.ui.callback;
/**
* TODO:UnPeekLiveData 的存在是为在 "重回二级页面" 场景下,解决 "数据倒灌" 问题。
* 对 "数据倒灌" 状况不理解小伙伴,可参考《LiveData 数据倒灌 背景缘由全貌 独家解析》解析
*
* https://xiaozhuanlan.com/topic/6719328450
*
*
* 对{@link ProtectedUnPeekLiveDataV4}进行重构,修复了V4版本已知问题:
* TODO: 1、UnPeekLiveDataV4 中 observers HashMap 恒久存在,注册 Observer 越多,占用内存越大,且除非 UnPeekLiveDataV4 被回收,否则恒久存在内存当中
* 在 removeObserver 方法中移除 map 中对应存储 storeId
*
* TODO: 2、无法通过 removeObserver 方法移除指定 Observer(某些场景需要提前 removeObserver)
* 通过维护外部传入 Observer 与内部代理 Observer 映射关系,在 removeObserver 调用时,通过反射找到真正注册到 LiveData 中 Observer,实现移除
*
* TODO: 3、同一个 Observer 对象,注册多次,UnPeekLiveDataV4 内部实际上会注册多个不同 Observer,从而导致重复回调,产生不可预期问题
* 内部不会每次调用 observe 方法时都新创建一个代理 Observer,而是复用已经存在代理 Observer
* 注意!!!Kotlin + LiveData + Lambda 由于编译器优化,可能会抛 Cannot add the same observer with different lifecycles 异常
*
* TODO: 4、无法使用 observerForever 方法
* UnPeekLiveData 内部直接持有 forever 类型 Observer
*
* TODO: 最终实现对谷歌原生 LiveData 完全无侵入性目的。在多人协作的场景下,其他同学就只需要理解 UnPeekLiveData 能解决粘性事件、数据倒灌问题,其余无需了解,用法上完全跟原生 LiveData 保持一致
*
*
* TODO:增加一层 ProtectedUnPeekLiveData,
* 用于限制从 Activity/Fragment 推送数据,推送数据务必通过唯一可信源来分发,
* 如这么说无体会,详见:
* https://xiaozhuanlan.com/topic/6719328450 和 https://xiaozhuanlan.com/topic/0168753249
*
*
* Create by Jim at 2021/4/21
*/
@Deprecated
public class UnPeekLiveDataV5<T> extends ProtectedUnPeekLiveDataV5<T> {
@Override
public void setValue(T value) {
super.setValue(value);
}
@Override
public void postValue(T value) {
super.postValue(value);
}
public static class Builder<T> {
/**
* 是否允许传入 null value
*/
private boolean isAllowNullValue;
public Builder<T> setAllowNullValue(boolean allowNullValue) {
this.isAllowNullValue = allowNullValue;
return this;
}
public UnPeekLiveDataV5<T> create() {
UnPeekLiveDataV5<T> liveData = new UnPeekLiveDataV5<>();
liveData.isAllowNullValue = this.isAllowNullValue;
return liveData;
}
}
}
/*
* Copyright 2018-present KunMinX
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kunminx.architecture.ui.callback;
import androidx.annotation.NonNull;
import androidx.arch.core.internal.SafeIterableMap;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* TODO:UnPeekLiveData 的存在是为在 "重回二级页面" 场景下,解决 "数据倒灌" 问题。
* 对 "数据倒灌" 状况不理解小伙伴,可参考《LiveData 数据倒灌 背景缘由全貌 独家解析》解析
*
* https://xiaozhuanlan.com/topic/6719328450
*
*
* 对{@link ProtectedUnPeekLiveDataV4}进行重构,修复了V4版本已知问题:
* TODO: 1、UnPeekLiveDataV4 中 observers HashMap 恒久存在,注册 Observer 越多,占用内存越大,且除非 UnPeekLiveDataV4 被回收,否则恒久存在内存当中
* 在 removeObserver 方法中移除 map 中对应存储 storeId
*
* TODO: 2、无法通过 removeObserver 方法移除指定 Observer(某些场景需要提前 removeObserver)
* 通过维护外部传入 Observer 与内部代理 Observer 映射关系,在 removeObserver 调用时,通过反射找到真正注册到 LiveData 中 Observer,实现移除
*
* TODO: 3、同一个 Observer 对象,注册多次,UnPeekLiveDataV4 内部实际上会注册多个不同 Observer,从而导致重复回调,产生不可预期问题
* 内部不会每次调用 observe 方法时都新创建一个代理 Observer,而是复用已经存在代理 Observer
* 注意!!!Kotlin + LiveData + Lambda 由于编译器优化,可能会抛 Cannot add the same observer with different lifecycles 异常
*
* TODO: 4、无法使用 observerForever 方法
* UnPeekLiveData 内部直接持有 forever 类型 Observer
*
* TODO: 最终实现对谷歌原生 LiveData 完全无侵入性目的。在多人协作的场景下,其他同学就只需要理解 UnPeekLiveData 能解决粘性事件、数据倒灌问题,其余无需了解,用法上完全跟原生 LiveData 保持一致
*
*
* TODO:增加一层 ProtectedUnPeekLiveData,
* 用于限制从 Activity/Fragment 推送数据,推送数据务必通过唯一可信源来分发,
* 如这么说无体会,详见:
* https://xiaozhuanlan.com/topic/6719328450 和 https://xiaozhuanlan.com/topic/0168753249
*
*
* Create by Jim at 2021/4/21
*/
@Deprecated
public class ProtectedUnPeekLiveDataV5<T> extends LiveData<T> {
protected boolean isAllowNullValue;
private final ConcurrentHashMap<Integer, Boolean> observers = new ConcurrentHashMap<>();
/**
* 保存外部传入的 Observer 与代理 Observer 之间映射关系
*/
private final ConcurrentHashMap<Integer, Integer> observerMap = new ConcurrentHashMap<>();
/**
* 这里会持有永久性注册 Observer 对象,因为是永久性注册,必须调用 remove 才会注销,所有这里持有 Observer 对象不存在内存泄漏问题,
* 因为一旦泄漏,只能说明业务使用方没有 remove
*/
private final ConcurrentHashMap<Integer, Observer<? super T>> foreverObservers = new ConcurrentHashMap<>();
private Observer<? super T> createProxyObserver(@NonNull Observer<? super T> originalObserver, @NonNull Integer observeKey) {
return t -> {
if (!observers.get(observeKey)) {
observers.put(observeKey, true);
if (t != null || isAllowNullValue) {
originalObserver.onChanged(t);
}
}
};
}
@Override
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
// if (owner instanceof Fragment && ((Fragment) owner).getViewLifecycleOwner() != null) {
// /**
// * Fragment 场景下使用 getViewLifeCycleOwner 作为 liveData 订阅者,
// * 如此可确保 "视图实例" 生命周期安全(getView 不为 null),
// * 因而需要注意的是,getViewLifeCycleOwner 使用应在 onCreateView 之后和 onDestroyView 之前。
// *
// * 如这么说无体会,详见《LiveData 鲜为人知 身世背景 和 独特使命》解析
// * https://xiaozhuanlan.com/topic/0168753249
// */
// owner = ((Fragment) owner).getViewLifecycleOwner();
// }
Integer observeKey = System.identityHashCode(observer);
observe(observeKey, owner, observer);
}
@Override
public void observeForever(@NonNull Observer<? super T> observer) {
Integer observeKey = System.identityHashCode(observer);
observeForever(observeKey, observer);
}
private void observe(@NonNull Integer observeKey,
@NonNull LifecycleOwner owner,
@NonNull Observer<? super T> observer) {
if (observers.get(observeKey) == null) {
observers.put(observeKey, true);
}
Observer<? super T> registerObserver;
if (observerMap.get(observeKey) == null) {
registerObserver = createProxyObserver(observer, observeKey);
// 保存外部 Observer 及内部代理 Observer 映射关系
observerMap.put(observeKey, System.identityHashCode(registerObserver));
} else {
// 通过反射拿到真正注册到 LiveData 中 Observer
Integer registerObserverStoreId = observerMap.get(observeKey);
assert registerObserverStoreId != null;
registerObserver = getObserver(this, registerObserverStoreId);
if (registerObserver == null) {
registerObserver = createProxyObserver(observer, observeKey);
// 保存外部 Observer 及内部代理 Observer 映射关系
observerMap.put(observeKey, System.identityHashCode(registerObserver));
}
}
super.observe(owner, registerObserver);
}
private void observeForever(@NonNull Integer observeKey, @NonNull Observer<? super T> observer) {
if (observers.get(observeKey) == null) {
observers.put(observeKey, true);
}
Observer<? super T> registerObserver = foreverObservers.get(observeKey);
if (registerObserver == null) {
registerObserver = createProxyObserver(observer, observeKey);
foreverObservers.put(observeKey, registerObserver);
}
super.observeForever(registerObserver);
}
@Override
public void removeObserver(@NonNull Observer<? super T> observer) {
Integer observeKey = System.identityHashCode(observer);
Observer<? super T> registerObserver = foreverObservers.remove(observeKey);
if (registerObserver == null && observerMap.containsKey(observeKey)) {
// 反射拿到真正注册到 LiveData 中 observer
Integer registerObserverStoreId = observerMap.remove(observeKey);
assert registerObserverStoreId != null;
registerObserver = getObserver(this, registerObserverStoreId);
}
if (registerObserver != null) {
observers.remove(observeKey);
}
super.removeObserver(registerObserver != null ? registerObserver : observer);
}
/**
* 重写的 setValue 方法,默认不接收 null
* 可通过 Builder 配置允许接收
* 可通过 Builder 配置消息延时清理时间
*
* override setValue, do not receive null by default
* You can configure to allow receiving through Builder
* And also, You can configure the delay time of message clearing through Builder
*
* @param value
*/
@Override
protected void setValue(T value) {
if (value != null || isAllowNullValue) {
for (Map.Entry<Integer, Boolean> entry : observers.entrySet()) {
entry.setValue(false);
}
super.setValue(value);
}
}
public void clear() {
super.setValue(null);
}
/**
* 通过反射,获取指定 LiveData 中 Observer 对象
*
* @param liveData 指定的LiveData
* @param identityHashCode 想要获取的Observer对象的identityHashCode {@code System.identityHashCode}
* @return
*/
private Observer<? super T> getObserver(@NonNull LiveData<T> liveData, @NonNull Integer identityHashCode) {
try {
Field field = LiveData.class.getDeclaredField("mObservers");
field.setAccessible(true);
SafeIterableMap<Observer<? super T>, Object> observers
= (SafeIterableMap<Observer<? super T>, Object>) field.get(liveData);
if (observers != null) {
for (Map.Entry<Observer<? super T>, Object> entry : observers) {
Observer<? super T> observer = entry.getKey();
if (System.identityHashCode(observer) == identityHashCode) {
return observer;
}
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return null;
}
}