Skip to content

UnPeekLiveData v5 设计思路

KunMinX edited this page Jun 13, 2022 · 4 revisions

以下源码设计,及对设计思路理解和解析,属于参与过源码设计讨论有效贡献者及本人共同成果,我们对此享有所有权和最终解释权。

任何个人或组织在引用以下内容时,须注明原作者和链接出处。未经授权不得用于洗稿、广告包装、卖课等商业用途。

Copyright 2019-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;

/**

 * 感谢 “腾讯音乐” 小伙伴 @[zhangjianlaoda](https://github.com/zhangjianlaoda) 贡献的重构优化代码。
 * 
 * 该版本保留 UnPeekLiveData v4 下述几大特点**,且在适当时机基于反射等机制,
 * 彻底解决 UnPeekLiveData v4 下 Observers 无法释放、重复创建,
 * 及 foreverObserver、removeObserver 被禁用等问题,将 UnPeekLiveData 内存性能再往上提升一阶梯。
 * 
 * 同时,该版本使 Observe 等方法方法名和形参列表与官方 API 保持一致,尽可能减少学习成本。
 * 
 * 
 * UnPeekLiveData 的存在是为在 "重回二级页面" 场景下,解决 "数据倒灌" 问题。
 * 对 "数据倒灌" 状况不理解小伙伴,可参考《LiveData 数据倒灌 背景缘由全貌 独家解析》解析
 * 
 * https://xiaozhuanlan.com/topic/6719328450
 * 
 * 
 * 对{@link ProtectedUnPeekLiveDataV4}进行重构,修复了V4版本已知问题:
 * 1、UnPeekLiveDataV4 中 observers HashMap 恒久存在,注册 Observer 越多,占用内存越大,且除非 UnPeekLiveDataV4 被回收,否则恒久存在内存当中
 * 在 removeObserver 方法中移除 map 中对应存储 storeId
 * 
 * 2、无法通过 removeObserver 方法移除指定 Observer(某些场景需要提前 removeObserver)
 * 通过维护外部传入 Observer 与内部代理 Observer 映射关系,在 removeObserver 调用时,通过反射找到真正注册到 LiveData 中 Observer,实现移除
 * 
 * 3、同一个 Observer 对象,注册多次,UnPeekLiveDataV4 内部实际上会注册多个不同 Observer,从而导致重复回调,产生不可预期问题
 * 内部不会每次调用 observe 方法时都新创建一个代理 Observer,而是复用已经存在代理 Observer
 * 注意!!!Kotlin + LiveData + Lambda 由于编译器优化,可能会抛 Cannot add the same observer with different lifecycles 异常
 * 
 * 4、无法使用 observerForever 方法
 * UnPeekLiveData 内部直接持有 forever 类型 Observer
 * 
 * 最终实现对谷歌原生 LiveData 完全无侵入性目的。在多人协作的场景下,其他同学就只需要理解 UnPeekLiveData 能解决粘性事件、数据倒灌问题,其余无需了解,用法上完全跟原生 LiveData 保持一致
 *
 * 
 * 增加一层 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;

/**
 * UnPeekLiveData 的存在是为在 "重回二级页面" 场景下,解决 "数据倒灌" 问题。
 * 对 "数据倒灌" 状况不理解小伙伴,可参考《LiveData 数据倒灌 背景缘由全貌 独家解析》解析
 * 
 * https://xiaozhuanlan.com/topic/6719328450
 * 
 * 
 * 对{@link ProtectedUnPeekLiveDataV4}进行重构,修复了V4版本已知问题:
 * 1、UnPeekLiveDataV4 中 observers HashMap 恒久存在,注册 Observer 越多,占用内存越大,且除非 UnPeekLiveDataV4 被回收,否则恒久存在内存当中
 * 在 removeObserver 方法中移除 map 中对应存储 storeId
 * 
 * 2、无法通过 removeObserver 方法移除指定 Observer(某些场景需要提前 removeObserver)
 * 通过维护外部传入 Observer 与内部代理 Observer 映射关系,在 removeObserver 调用时,通过反射找到真正注册到 LiveData 中 Observer,实现移除
 * 
 * 3、同一个 Observer 对象,注册多次,UnPeekLiveDataV4 内部实际上会注册多个不同 Observer,从而导致重复回调,产生不可预期问题
 * 内部不会每次调用 observe 方法时都新创建一个代理 Observer,而是复用已经存在代理 Observer
 * 注意!!!Kotlin + LiveData + Lambda 由于编译器优化,可能会抛 Cannot add the same observer with different lifecycles 异常
 * 
 * 4、无法使用 observerForever 方法
 * UnPeekLiveData 内部直接持有 forever 类型 Observer
 * 
 * 最终实现对谷歌原生 LiveData 完全无侵入性目的。在多人协作的场景下,其他同学就只需要理解 UnPeekLiveData 能解决粘性事件、数据倒灌问题,其余无需了解,用法上完全跟原生 LiveData 保持一致
 *
 * 
 * 增加一层 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;
  }
}