当前位置: 首页 > news >正文

曹县网站建设wordpress基于什么

曹县网站建设,wordpress基于什么,网站板块设计有哪些,微信清粉网站开发作者#xff1a;唐子玄 1. LiveData 如何感知生命周期的变化#xff1f; 先总结#xff0c;再分析#xff1a; Jetpack 引入了 Lifecycle#xff0c;让任何组件都能方便地感知界面生命周期的变化。只需实现 LifecycleEventObserver 接口并注册给生命周期对象即可。LiveDa… 作者唐子玄 1. LiveData 如何感知生命周期的变化 先总结再分析 Jetpack 引入了 Lifecycle让任何组件都能方便地感知界面生命周期的变化。只需实现 LifecycleEventObserver 接口并注册给生命周期对象即可。LiveData 的数据观察者在内部被包装成另一个对象实现了 LifecycleEventObserver 接口它同时具备了数据观察能力和生命周期观察能力。 常规的观察者模式中只要被观察者发生变化就会无条件地通知所有观察者。比如java.util.Observable public class Observable {private boolean changed false;private VectorObserver obs;public void notifyObservers(Object arg) {Object[] arrLocal;synchronized (this) {if (!hasChanged())return;arrLocal obs.toArray();clearChanged();}// 无条件地遍历所有观察者并通知for (int i arrLocal.length-1; i0; i--)((Observer)arrLocal[i]).update(this, arg);} } // 观察者 public interface Observer {void update(Observable o, Object arg); }LiveData 在常规的观察者模式上附加了条件若生命周期未达标即使数据发生变化也不通知观察者。这是如何实现的 生命周期 生命周期是一个对象从构建到消亡过程中的各个状态的统称。 比如 Activity 的生命周期用如下函数依次表达 onCreate() onStart() onResume() onPause() onStop() onDestroy()要观察生命周期就不得不继承 Activity 重写这些方法想把生命周期的变化分发给其他组件就很麻烦。 于是 Jetpack 引入了 Lifecycle以让任何组件都可方便地感知生命周期的变化 public abstract class Lifecycle {AtomicReference();// 添加生命周期观察者public abstract void addObserver(LifecycleObserver observer);// 移除生命周期观察者public abstract void removeObserver(LifecycleObserver observer);// 获取当前生命周期状态public abstract State getCurrentState();// 生命周期事件public enum Event {ON_CREATE,ON_START,ON_RESUME,ON_PAUSE,ON_STOP,ON_DESTROY,ON_ANY;}// 生命周期状态public enum State {DESTROYED,INITIALIZED,CREATED,STARTED,RESUMED;}// 判断至少到达了某生命周期状态public boolean isAtLeast(State state) {return compareTo(state) 0;} }Lifecycle 即是生命周期对应的类提供了添加/移除生命周期观察者的方法在其内部还定义了全部生命周期的状态及对应事件。 生命周期状态是有先后次序的分别对应着由小到大的 int 值。 生命周期拥有者 描述生命周期的对象已经有了如何获取这个对象需要个统一的接口不然直接在 Activity 或者 Fragment 中新增一个方法吗这个接口叫LifecycleOwner public interface LifecycleOwner {Lifecycle getLifecycle(); }Activity 和 Fragment 都实现了这个接口。 只要拿到 LifecycleOwner就能拿到 Lifecycle然后就能注册生命周期观察者。 生命周期 数据观察者 生命周期观察者是一个接口 // 生命周期观察者空接口用于表征一个类型 public interface LifecycleObserver {} // 生命周期事件观察者 public interface LifecycleEventObserver extends LifecycleObserver {void onStateChanged(LifecycleOwner source, Lifecycle.Event event); }要观察生命周期只要实现LifecycleEventObserver接口并注册给LifeCycle即可。 除了生命周期观察者外LiveData 场景中还有一个数据观察者 // 数据观察者 public interface ObserverT {// 数据发生变化时回调void onChanged(T t); }数据观察者 会和 生命周期拥有者 进行绑定 public abstract class LiveDataT {// 数据观察者容器private SafeIterableMapObserver? super T, ObserverWrapper mObservers new SafeIterableMap();public void observe(LifecycleOwner owner, // 被绑定的生命周期拥有者Observer? super T observer // 数据观察者) {...// 将数据观察者包装成 LifecycleBoundObserverLifecycleBoundObserver wrapper new LifecycleBoundObserver(owner, observer);// 存储观察者到 map 结构ObserverWrapper existing mObservers.putIfAbsent(observer, wrapper);...// 注册生命周期观察者。owner.getLifecycle().addObserver(wrapper);} }在观察 LiveData 时需传入两个参数生命周期拥有者和数据观察者。这两个对象经过LifecycleBoundObserver的包装被绑定在了一起 class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {// 持有生命周期拥有者final LifecycleOwner mOwner;LifecycleBoundObserver(LifecycleOwner owner, Observer? super T observer) {super(observer);mOwner owner;}// 生命周期变化回调Overridepublic void onStateChanged(LifecycleOwner source, Lifecycle.Event event) { ...activeStateChanged(shouldBeActive())...} }// 观察者包装类型 private abstract class ObserverWrapper {// 持有原始数据观察者final Observer? super T mObserver;// 注入数据观察者ObserverWrapper(Observer? super T observer) {mObserver observer;}// 尝试将最新值分发给当前数据观察者void activeStateChanged(boolean newActive) {...}... }LifecycleBoundObserver 实现了LifecycleEventObserver接口并且它被注册给了绑定的生命周期对象遂具备了生命周期感知能力。同时它还持有了数据观察者所以它还具备了数据观察能力。 2. LiveData 是如何避免内存泄漏的 先总结再分析 LiveData 的数据观察者通常是匿名内部类它持有界面的引用可能造成内存泄漏。LiveData 内部会将数据观察者进行封装使其具备生命周期感知能力。当生命周期状态为 DESTROYED 时自动移除观察者。 内存泄漏是因为长生命周期的对象持有了短生命周期对象阻碍了其被回收。 观察 LiveData 数据的代码通常这样写 class LiveDataActivity : AppCompatActivity() {private val viewModel by lazy {ViewModelProviders.of(thisLiveDataActivity).get(MyViewModel::class.java)}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)viewModel.livedata.observe(thisLiveDataActivity) {// 观察 LiveData 数据更新匿名内部类}} }Observer 作为界面的匿名内部类它会持有界面的引用同时 Observer 被 LiveData 持有LivData 被 ViewModel 持有而 ViewModel 的生命周期比 Activity 长。 最终的持有链如下NonConfigurationInstances 持有 ViewModelStore 持有 ViewModel 持有 LiveData 持有 Observer 持有 Activity。 所以得在界面生命周期结束的时候移除 Observer这件事情LiveData 帮我们做了。 在 LiveData 内部 Observer 会被包装成LifecycleBoundObserver class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {final LifecycleOwner mOwner;LifecycleBoundObserver(LifecycleOwner owner, Observer? super T observer) {super(observer);mOwner owner;}Overridepublic void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {// 获取当前生命周期Lifecycle.State currentState mOwner.getLifecycle().getCurrentState();// 若生命周期为 DESTROYED 则移除数据观察者并返回if (currentState DESTROYED) {removeObserver(mObserver);return}...}... }3. LiveData 是粘性的吗若是它是怎么做到的 先总结再分析 LiveData 的值被存储在内部的字段中直到有更新的值覆盖所以值是持久的。两种场景下 LiveData 会将存储的值分发给观察者。一是值被更新此时会遍历所有观察者并分发之。二是新增观察者或观察者生命周期发生变化至少为 STARTED此时只会给单个观察者分发值。LiveData 的观察者会维护一个“值的版本号”用于判断上次分发的值是否是最新值。该值的初始值是-1每次更新 LiveData 值都会让版本号自增。LiveData 并不会无条件地将值分发给观察者在分发之前会经历三道坎1. 数据观察者是否活跃。2. 数据观察者绑定的生命周期组件是否活跃。3. 数据观察者的版本号是否是最新的。“新观察者”被“老值”通知的现象叫“粘性”。因为新观察者的版本号总是小于最新版号且添加观察者时会触发一次老值的分发。 如果把 sticky 翻译成“持久的”会更好理解一些。数据是持久的意味着它不是转瞬即逝的不会因为被消费了就不见了它会一直在那。而且当新的观察者被注册时持久的数据会将最新的值分发给它。 “持久的数据”是怎么做到的 显然是被存起来了。以更新 LiveData 数据的方法为切入点找找线索 public abstract class LiveDataT {// 存储数据的字段private volatile Object mData;// 值版本号private int mVersion;// 更新值protected void setValue(T value) {assertMainThread(setValue);// 版本号自增mVersion;// 存储值mData value;// 分发值dispatchingValue(null);} }setValue() 是更新 LiveData 值时必然会调用的一个方法即使是通过 postValue() 更新值最终也会走这个方法。 LiveData 持有一个版本号字段用于标识“值的版本”就像软件版本号一样这个数字用于判断“当前值是否是最新的”若版本号小于最新版本号则表示当前值需要更新。 LiveData 用一个 Object 字段mData存储了“值”。所以这个值会一直存在直到被更新的值覆盖。 LiveData 分发值即是通知数据观察者 public abstract class LiveDataT {// 用键值对方式持有一组数据观察者private SafeIterableMapObserver? super T, ObserverWrapper mObservers new SafeIterableMap();void dispatchingValue(ObserverWrapper initiator) {...// 指定分发给单个数据观察者if (initiator ! null) {considerNotify(initiator);initiator null;} // 遍历所有数据观察者分发值else {for (IteratorMap.EntryObserver? super T, ObserverWrapper iterator mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {considerNotify(iterator.next().getValue());}}...}// 真正地分发值private void considerNotify(ObserverWrapper observer) {// 1. 若观察者不活跃则不分发给它if (!observer.mActive) {return;}// 2. 根据观察者绑定的生命周期再次判断它是否活跃若不活跃则不分发给它if (!observer.shouldBeActive()) {observer.activeStateChanged(false);return;}// 3. 若值已经是最新版本则不分发if (observer.mLastVersion mVersion) {return;}// 更新观察者的最新版本号observer.mLastVersion mVersion;// 真正地通知观察者observer.mObserver.onChanged((T) mData);}}分发值有两种情况“分发给单个观察者”和“分发给所有观察者”。当 LiveData 值更新时需分发给所有观察者。 所有的观察者被存在一个 Map 结构中分发的方式是通过遍历 Map 并逐个调用considerNotify()。在这个方法中需要跨过三道坎才能真正地将值分发给数据观察者分别是 数据观察者是否活跃。数据观察者绑定的生命周期组件是否活跃。数据观察者的版本号是否是最新的。 跨过三道坎后会将最新的版本号存储在观察者的 mLastVersion 字段中即版本号除了保存在LiveData.mVersion还会在每个观察者中保存一个副本mLastVersion最后才将之前暂存的mData的值分发给数据观察者。 每个数据观察者都和一个组件的生命周期对象绑定见第一节当组件生命周期发生变化时会尝试将最新值分发给该数据观察者。 每一个数据观察者都会被包装见第一节包装类型为ObserverWrapper // 原始数据观察者 public interface ObserverT {void onChanged(T t); }// 观察者包装类型 private abstract class ObserverWrapper {// 持有原始数据观察者final Observer? super T mObserver;// 当前观察者是否活跃boolean mActive;// 当前观察者最新值版本号初始值为 -1int mLastVersion START_VERSION;// 注入原始观察者ObserverWrapper(Observer? super T observer) {mObserver observer;}// 当数据观察者绑定的组件生命周期变化时尝试将最新值分发给当前观察者void activeStateChanged(boolean newActive) {// 若观察者活跃状态未变则不分发值if (newActive mActive) {return;}// 更新活跃状态mActive newActive;// 若活跃则将最新值分发给当前观察者if (mActive) {dispatchingValue(this);}}// 是否活跃供子类重写abstract boolean shouldBeActive(); }观察者的包装类型通过组合的方式持有了一个原始观察者并在此基础上为其扩展了活跃状态和版本号的概念。 观察者包装类型是抽象的是否活跃由子类定义 class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {final LifecycleOwner mOwner;LifecycleBoundObserver(LifecycleOwner owner, Observer? super T observer) {super(observer);mOwner owner;}// 当与观察者绑定的生命周期组件至少为STARTED时表示观察者活跃Overrideboolean shouldBeActive() {return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);}Overridepublic void onStateChanged( LifecycleOwner source, Lifecycle.Event event) {Lifecycle.State currentState mOwner.getLifecycle().getCurrentState();// 当生命周期状态发生变化则尝试将最新值分发给数据观察者while (prevState ! currentState) {prevState currentState;// 调用父类方法进行分发activeStateChanged(shouldBeActive());currentState mOwner.getLifecycle().getCurrentState();}} }总结一下LiveData 有两次机会通知观察者与之对应的有两种分发值的方式 当值更新时遍历所有观察者将最新值分发给它们。当与观察者绑定组件的生命周期发生变化时将最新的值分发给指定观察者。 假设这样一种场景LiveData 的值被更新了一次随后它被添加了一个新的数据观察者与之绑定组件的生命周期也正好发生了变化变化到RESUMED即数据更新在添加观察者之前此时更新值会被分发到新的观察者吗 会首先更新值会被存储在 mData 字段中。 其次在添加观察者时会触发一次生命周期变化 // androidx.lifecycle.LifecycleRegistry public void addObserver(NonNull LifecycleObserver observer) {State initialState mState DESTROYED ? DESTROYED : INITIALIZED;ObserverWithState statefulObserver new ObserverWithState(observer, initialState);...// 将生命周期事件分发给新进的观察者statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));... }// LifecycleBoundObserver 又被包了一层 static class ObserverWithState {State mState;GenericLifecycleObserver mLifecycleObserver;ObserverWithState(LifecycleObserver observer, State initialState) {mLifecycleObserver Lifecycling.getCallback(observer);mState initialState;}void dispatchEvent(LifecycleOwner owner, Event event) {State newState getStateAfter(event);mState min(mState, newState);// 分发生命周期事件给 LifecycleBoundObservermLifecycleObserver.onStateChanged(owner, event);mState newState;} }最后这次尝试必然能跨过三道坎因为新建观察者版本号总是小于 LiveData 的版本号-1 0LiveData.mVersion 经过一次值更新后自增为0。 这种“新观察者”会被“老值”通知的现象称为粘性。 4. 粘性的 LiveData 会造成什么问题怎么解决 购物车-结算场景假设有一个购物车界面点击结算后跳转到结算界面结算界面可以回退到购物车界面。这两个界面都是 Fragment。 结算界面和购物车界面通过共享ViewModel的方式共享商品列表 class MyViewModel:ViewModel() {// 商品列表val selectsListLiveData MutableLiveDataListString()// 更新商品列表fun setSelectsList(goods:ListString){selectsListLiveData.value goods} }下面是俩 Fragment 界面依托的 Activity class StickyLiveDataActivity : AppCompatActivity() {// 用 DSL 构建视图private val contentView by lazy {ConstraintLayout {layout_id containerlayout_width match_parentlayout_height match_parent}}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(contentView)// 加载购物车界面supportFragmentManager.beginTransaction().add(container.toLayoutId(), TrolleyFragment()).commit()} }其中使用了 DSL 方式声明性地构建了布局。 购物车页面如下 class TrolleyFragment : Fragment() {// 获取与宿主 Activity 绑定的 ViewModelprivate val myViewModel by lazy { ViewModelProvider(requireActivity()).get(MyViewModel::class.java) }override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {return ConstraintLayout {layout_width match_parentlayout_height match_parent// 向购物车添加两件商品onClick {myViewModel.setSelectsList(listOf(meet,water))}TextView {layout_id balancelayout_width wrap_contentlayout_height wrap_contenttext balancegravity gravity_center// 跳转结算页面onClick {parentFragmentManager.beginTransaction().replace(container.toLayoutId(), BalanceFragment()).addToBackStack(trolley).commit()}}}}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)// 观察商品列表变化myViewModel.selectsListLiveData.observe(viewLifecycleOwner) { goods -// 若商品列表超过2件商品则 toast 提示已满goods.takeIf { it.size 2 }?.let {Toast.makeText(context,购物车已满,Toast.LENGTH_LONG).show()}}} }在 onViewCreated() 中观察购物车的变化如果购物车超过 2 件商品则 toast 提示。 下面是结算页面 class BalanceFragment:Fragment() {private val myViewModel by lazy { ViewModelProvider(requireActivity()).get(MyViewModel::class.java) }override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {return ConstraintLayout {layout_width match_parentlayout_height match_parent}}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)// 结算界面获取购物列表的方式也是观察商品 LiveDatamyViewModel.selectsListLiveData.observe(viewLifecycleOwner) {...}} }跑一下 demo当跳转到结算界面后点击返回购物车toast 会再次提示购物车已满。 因为在跳转结算页面之前购物车列表 LiveData 已经被更新过。当购物车页面重新展示时onViewCreated()会再次执行这样一个新观察者被添加因为 LiveData 是粘性的所以上一次购物车列表会分发给新观察者这样 toast 逻辑再一次被执行。 解决方案一带消费记录的值 // 一次性值 open class OneShotValueout T(private val value: T) {// 值是否被消费private var handled false// 获取值如果值未被处理则返回否则返回空fun getValue(): T? {return if (handled) {null} else {handled truevalue}}// 获取上次被处理的值fun peekValue(): T value }在值的外面套一层新增一个标记位标识是否被处理过。 用这个方法重构下 ViewModel class MyViewModel:ViewModel() {// 已选物品列表val selectsListLiveData MutableLiveDataOneShotValueListString()// 更新已选物品fun setSelectsList(goods:ListString){selectsListLiveData.value OneShotValue(goods)} }观察购物车的逻辑也要做修改 class TrolleyFragment : Fragment() {override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)myViewModel.selectsListLiveData.observe(viewLifecycleOwner) { goods -goods.getValue()?.takeIf { it.size 2 }?.let {Toast.makeText(context,购物车满了,Toast.LENGTH_LONG).show()}}} }重复弹 toast 的问题是解决了但引出了一个新的问题当购物车满弹出 toast 时购物车列表已经被消费掉了导致结算界面就无法再消费了。 这时候只能用peekValue()来获取已经被消费的值 class BalanceFragment:Fragment() {override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)myViewModel.selectsListLiveData.observe(viewLifecycleOwner) {val list it.peekValue()// 使用 peekValue() 获取购物车列表}} }bug 全解完了。但不觉得这样处理有一些拧巴吗 用“一次性值”封装 LiveData 的值以去除其粘性。使用该方案得甄别出哪些观察者需要粘性值哪些观察者需要非粘性事件。当观察者很多的时候就很难招架了。若把需要粘性处理和非粘性处理的逻辑写在一个观察者中就 GG还得新建观察者将它们分开。 解决方案二带有最新版本号的观察者 通知观察者前需要跨过三道坎详见第三节其中有一道坎是版本号的比对。若新建的观察者版本号小于最新版本号则表示观察者落后了需要将最新值分发给它。 LiveData 源码中新建观察者的版本号总是 -1。 // 观察者包装类型 private abstract class ObserverWrapper {// 当前观察者最新值版本号初始值为 -1int mLastVersion START_VERSION;... }若能够让新建观察者的版本号被最新版本号赋值那版本号对比的那道坎就过不了新值就无法分发到新建观察者。 所以得通过反射修改 mLastVersion 字段。 该方案除了倾入性强之外把 LiveData 粘性彻底破坏了。但有的时候我们还是想利用粘性的。。。 解决方案三SingleLiveEvent 这是谷歌给出的一个解决方案 public class SingleLiveEventT extends MutableLiveDataT {// 标志位用于表达值是否被消费private final AtomicBoolean mPending new AtomicBoolean(false);public void observe(LifecycleOwner owner, final ObserverT observer) {// 中间观察者super.observe(owner, new ObserverT() {Overridepublic void onChanged(Nullable T t) {// 只有当值未被消费过时才通知下游观察者if (mPending.compareAndSet(true, false)) {observer.onChanged(t);}}});}public void setValue(Nullable T t) {// 当值更新时置标志位为 truemPending.set(true);super.setValue(t);}public void call() {setValue(null);} }专门设立一个 LiveData它不具备粘性。它通过新增的“中间观察者”拦截上游数据变化然后在转发给下游。拦截之后通常可以做一点手脚比如增加一个标记位mPending是否消费过的判断若消费过则不转发给下游。 在数据驱动的 App 界面下存在两种值1. 非暂态数据 2. 暂态数据 demo 中用于提示“购物车已满”的数据就是“暂态数据”这种数据是一次性的转瞬即逝的可以消费一次就扔掉。 demo 中购物车中的商品列表就是“非暂态数据”它的生命周期要比暂态数据长一点在购物车界面和结算界面存活的期间都应该能被重复消费。 SingleLiveEvent 的设计正是基于对数据的这种分类方法即暂态数据使用 SingleLiveEvent非暂态数据使用常规的 LiveData。 这样尘归尘土归土的解决方案是符合现实情况的。将 demo 改造一下 class MyViewModel : ViewModel() {// 非暂态购物车列表 LiveDataval selectsListLiveData MutableLiveDataListString()// 暂态购物车列表 LiveDataval singleListLiveData SingleLiveEventListString()// 更新购物车列表同时更新暂态和非暂态fun setSelectsList(goods: ListString) {selectsListLiveData.value goodssingleListLiveData.value goods} }在购物车界面做相应的改动 class TrolleyFragment : Fragment() {override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)// 只观察非暂态购物车列表myViewModel.singleListLiveData.observe(viewLifecycleOwner) { goods -goods.takeIf { it.size 2 }?.let {Toast.makeText(context,full,Toast.LENGTH_LONG).show()}}} }但该方案有局限性若为 SingleLiveEvent 添加多个观察者则当第一个观察者消费了数据后其他观察者就没机会消费了。因为mPending是所有观察者共享的。 解决方案也很简单为每个中间观察者都持有是否消费过数据的标记位 open class LiveEventT : MediatorLiveDataT() {// 持有多个中间观察者private val observers ArraySetObserverWrapperin T()MainThreadoverride fun observe(owner: LifecycleOwner, observer: Observerin T) {observers.find { it.observer observer }?.let { _ -return}// 构建中间观察者val wrapper ObserverWrapper(observer)observers.add(wrapper)super.observe(owner, wrapper)}MainThreadoverride fun observeForever(observer: Observerin T) {observers.find { it.observer observer }?.let { _ -return}val wrapper ObserverWrapper(observer)observers.add(wrapper)super.observeForever(wrapper)}MainThreadoverride fun removeObserver(observer: Observerin T) {if (observer is ObserverWrapper observers.remove(observer)) {super.removeObserver(observer)return}val iterator observers.iterator()while (iterator.hasNext()) {val wrapper iterator.next()if (wrapper.observer observer) {iterator.remove()super.removeObserver(wrapper)break}}}MainThreadoverride fun setValue(t: T?) {// 通知所有中间观察者有新数据observers.forEach { it.newValue() }super.setValue(t)}// 中间观察者private class ObserverWrapperT(val observer: ObserverT) : ObserverT {// 标记当前观察者是否消费了数据private var pending falseoverride fun onChanged(t: T?) {// 保证只向下游观察者分发一次数据if (pending) {pending falseobserver.onChanged(t)}}fun newValue() {pending true}} }解决方案四Kotlin Flow 限于篇幅原因及主题的原因主题是 LiveData直接给出代码当前做法有问题 class MyViewModel : ViewModel() {// 商品列表流val selectsListFlow MutableSharedFlowListString()// 更新商品列表fun setSelectsList(goods: ListString) {viewModelScope.launch {selectsListFlow.emit(goods)}} }购物车代码如下 class TrolleyFragment : Fragment() {private val myViewModel by lazy { ViewModelProvider(requireActivity()).get(MyViewModel::class.java) }override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 1.先产生数据myViewModel.setSelectsList(listOf(food_meet, food_water, book_1))}override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)// 2.再订阅商品列表流lifecycleScope.launch {myViewModel.selectsListFlow.collect { goods -goods.takeIf { it.size 2 }?.let {Log.v(ttaylor, 购物车满)}}}} }数据生产在订阅之前订阅后并不会打印 log。 如果这样修改 SharedFlow 的构建参数则可以让其变得粘性 class MyViewModel : ViewModel() {val selectsListFlow MutableSharedFlowListString(replay 1) }replay 1 表示会将最新的那个数据通知给新进的订阅者。 这只是解决了粘性/非粘性之间方便切换的问题并未解决仍需多个流的问题。带下一篇继续深入分析。 5. 什么情况下 LiveData 会丢失数据 先总结再分析 在高频数据更新的场景下使用 LiveData.postValue() 时会造成数据丢失。因为“设值”和“分发值”是分开执行的之间存在延迟。值先被缓存在变量中再向主线程抛一个分发值的任务。若在这延迟之间再一次调用 postValue()则变量中缓存的值被更新之前的值在没有被分发之前就被擦除了。 下面是 LiveData.postValue() 的源码 public abstract class LiveDataT {// 暂存值字段volatile Object mPendingData NOT_SET;private final Runnable mPostValueRunnable new Runnable() {Overridepublic void run() {Object newValue;synchronized (mDataLock) {// 同步地获取暂存值newValue mPendingData;mPendingData NOT_SET;}// 分发值setValue((T) newValue);}};protected void postValue(T value) {boolean postTask;synchronized (mDataLock) {postTask mPendingData NOT_SET;// 暂存值mPendingData value;}...// 向主线程抛 runnableArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);} }6. 在 Fragment 中使用 LiveData 需注意些什么 先总结再分析 在 Fragment 中观察 LiveData 时使用viewLifecycleOwner而不是this。因为 Fragment 和 其中的 View 生命周期不完全一致。LiveData 内部判定生命周期为 DESTROYED 时才会移除数据观察者。存在一种情况当 Fragment 之间切换时被替换的 Fragment 不执行 onDestroy()当它再次展示时会再次订阅 LiveData于是乎就多出一个订阅者。 还是购物-结算的场景购物车和结算页都是两个 Fragment将商品列表存在共享 ViewMode 的 LiveData 中购物车及结算页都观察它结算页除了用它列出购物清单之外还可以通过更改商品数量来修改 LiveData。当从结算页返回购物车页面时购物车界面得刷新商品数量。 上述场景若购物车页面观察 LiveData 时使用this会发生什么 // 购物车界面 class TrolleyFragment : Fragment() {private val myViewModel by lazy { ViewModelProvider(requireActivity()).get(MyViewModel::class.java) }override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?,savedInstanceState: Bundle?): View? {return ConstraintLayout {layout_width match_parentlayout_height match_parentonClick {parentFragmentManager.beginTransaction().replace(container.toLayoutId(), BalanceFragment()).addToBackStack(trolley)// 将购物车页面添加到 back stack.commit()}}}// 不得不增加这个注释因为 this 会飘红SuppressLint(FragmentLiveDataObserve)override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)// 将 this 作为生命周期拥有者传给 LiveDatamyViewModel.selectsListLiveData.observe(this, object : ObserverListString {override fun onChanged(t: ListString?) {Log.v(ttaylor, 商品数量发生变化)}})} }这样写this会飘红AndroidStudio 不推荐使用它作为生命周期拥有者不得不加 SuppressLint(“FragmentLiveDataObserve”) 结算界面修改商品数量的代码如下 // 结算界面 class BalanceFragment:Fragment() {private val myViewModel by lazy { ViewModelProvider(requireActivity()).get(MyViewModel::class.java) }override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)// 模拟结算界面修改商品数量myViewModel.selectsListLiveData.value listOf(数量1)} }当从结算页返回购物车时“商品数量发生变化” 会打印两次如果再进一次结算页并返回购物车就会打印三次。 若换成viewLifecycleOwner就不会有这个烦恼。因为使用 replace 更换 Fragment 时Fragment.onDestroyView()会执行即 Fragment 对应 View 的生命周期状态会变为 DESTROYED。 LiveData 内部会将生命周期为 DESTROYED 的数据观察者移除详见第二节。当再次返回购物车时onViewCreated() 重新执行LiveData 会添加一个新的观察者。一删一增整个过程 LiveData 始终只有一个观察者。又因为 LiveData 是粘性的即使修改商品数量发生在观察之前最新的商品数量还是会被分发到新观察者。详见第三节 但当使用 replace 更换 Fragment 并将其压入 back stack 时Fragment.onDestroy() 不会调用因为被压栈了并未被销毁。这导致 Fragment 的生命周期状态不会变为 DESTROYED所以 LiveData 的观察者不会被自动移除。当重新返回购物车时又添加了新的观察者。如果不停地在购物车和结算页间横跳则观察者数据会不停地增加。 在写 demo 的时候遇到一个坑 // 购物车界面 class TrolleyFragment : Fragment() {override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)// 故意使用 object 语法myViewModel.selectsListLiveData.observe(this, object : ObserverListString {override fun onChanged(t: ListString?) {Log.v(ttaylor, 商品数量发生变化)}})} }在构建 Observer 实例的时候我特意使用了 Kotlin 的 object 语法其实明明可以使用 lambda 将其写得更简洁 class TrolleyFragment : Fragment() {override fun onViewCreated(view: View, savedInstanceState: Bundle?) {super.onViewCreated(view, savedInstanceState)myViewModel.selectsListLiveData.observe(this) {Log.v(ttaylor, 商品数量发生变化)}} }如果这样写那 bug 就无法复现了。。。。 因为 java 编译器会擅作主张地将同样的 lambda 优化成静态的可以提升性能不用每次都重新构建内部类。但不巧的是 LiveData 在添加观察者时会校验是否已存在若存在则直接返回 // androidx.lifecycle.LiveData public void observe( LifecycleOwner owner, Observer? super T observer) {...LifecycleBoundObserver wrapper new LifecycleBoundObserver(owner, observer);// 调用 map 结构的写操作若 key 已存在则返回对应 valueObserverWrapper existing mObservers.putIfAbsent(observer, wrapper);...// 已存在则直接返回if (existing ! null) {return;}owner.getLifecycle().addObserver(wrapper); }这样的话Fragment 界面之间反复横跳也不会新增观察者。 7. 如何变换 LiveData 数据及注意事项 先总结再分析 androidx.lifecycle.Transformations类提供了三个变换 LiveData 数据的方法最常用的是 Transformations.map()它使用MediatorLiveData作为数据的中间消费者并将变换后的数据传递给最终消费者。需要注意的是数据变化操作都发生在主线程主线程有可能被耗时操作阻塞。解决方案是将 LiveData 数据变换操作异步化比如通过CoroutineLiveData。 还是购物-结算的场景购物车和结算页都是两个 Fragment将商品列表存在 LiveData 中购物车及结算页都观察它。结算界面对打折商品有一个特殊的 UI 展示。 此时就可以将商品列表 LiveData 进行一次变换过滤得到一个新的打折商品列表 class MyViewModel : ViewModel() {// 商品列表val selectsListLiveData MutableLiveDataListString()// 打折商品列表val foodListLiveData Transformations.map(selectsListLiveData) { list -list.filter { it.startsWith(discount) }} }每当商品列表发生变化打折商品列表都会收到通知并过滤出新的打折商品。打折商品列表是一个新的 LiveData可以单独被观察。 其中的过滤列表操作发生在主线程如果业务略复杂数据变换操作耗时的话可能阻塞主线程。 如何将 LiveData 变换数据异步化 LiveData 的 Kotlin 扩展包里提供了一个将 LiveData 和协程结合的产物 class MyViewModel : ViewModel() {// 商品列表val selectsListLiveData MutableLiveDataListString()// 用异步方式获取打折商品列表val asyncLiveData selectsListLiveData.switchMap { list -// 将源 LiveData 中的值转换成一个 CoroutineLiveDataliveData(Dispatchers.Default) {emit( list.filter { it.startsWith(discount) } )}} }其中的switchMap()是 LiveData 的扩展方法它是对Transformations.switchMap()的封装用于方便链式调用 public inline fun X, Y LiveDataX.switchMap(crossinline transform: (X) - LiveDataY ): LiveDataY Transformations.switchMap(this) { transform(it) }switchMap() 内部将源 LiveData 的每个值都转换成一个新的 LiveData 并订阅。 liveData是一个顶层方法用于构建CoroutineLiveData public fun T liveData(context: CoroutineContext EmptyCoroutineContext,timeoutInMs: Long DEFAULT_TIMEOUT,block: suspend LiveDataScopeT.() - Unit ): LiveDataT CoroutineLiveData(context, timeoutInMs, block)CoroutineLiveData 将更新 LiveData 值的操作封装到一个挂起方法中可以通过协程上下文指定执行的线程。 使用 CoroutineLiveData 需要添加如下依赖 implementation androidx.lifecycle:lifecycle-livedata-ktx:2.3.1Android 学习笔录 Android 性能优化篇https://qr18.cn/FVlo89 Android 车载篇https://qr18.cn/F05ZCM Android 逆向安全学习笔记https://qr18.cn/CQ5TcL Android Framework底层原理篇https://qr18.cn/AQpN4J Android 音视频篇https://qr18.cn/Ei3VPD Jetpack全家桶篇内含Composehttps://qr18.cn/A0gajp Kotlin 篇https://qr18.cn/CdjtAF Gradle 篇https://qr18.cn/DzrmMB OkHttp 源码解析笔记https://qr18.cn/Cw0pBD Flutter 篇https://qr18.cn/DIvKma Android 八大知识体https://qr18.cn/CyxarU Android 核心笔记https://qr21.cn/CaZQLo Android 往年面试题锦https://qr18.cn/CKV8OZ 2023年最新Android 面试题集https://qr18.cn/CgxrRy Android 车载开发岗位面试习题https://qr18.cn/FTlyCJ 音视频面试题锦https://qr18.cn/AcV6Ap
http://wiki.neutronadmin.com/news/272789/

相关文章:

  • 摄影网站建设目的app网站开发书籍下载
  • 培训机构网站php源码不屏蔽网站的浏览器
  • 绛帐做企业网站cn.wordpress
  • 无锡网站建设网络推广图形网站建设软件
  • 中国商城网站建设网站建设的主要观点
  • 可以申请微信号的网站查询网站备案进度查询
  • 佛山网站建设及优化公司网站模板 免费下载
  • 建设学校网站的需求分析百度大数据平台
  • 哪个网站可以做奖状集思吧网站怎么做问卷
  • 网站后台 灰色wordpress对比
  • 商城网站租服务器安全不福州php做网站
  • 做学校网站的济南公司曲靖企业网站建设
  • 江苏省城乡与建设厅网站桂林优化公司
  • 网站建设 千助单位网站建设情况汇报
  • 海口市网站开发内容网站管理系统
  • 廉江市住房和城乡建设局网站黄岩路桥网站设计
  • 自己如何在网上做网站八亿wap建站
  • 网站运行费用预算公司网站出现空白页
  • 做私房蛋糕在哪些网站写东西建立大数据平台
  • 重庆做公司网站高端酒店网站模板
  • 广州 网站制高唐网站开发
  • 实战网站开发北京出名的室内设计公司
  • 手机端网站seo做手表网站用什么源码比较好
  • 电子网站设计wordpress 微信支付插件
  • 智能行业网站模板门户网站开发技术服务合同
  • 汉口江岸区城市建设局网站wordpress和di
  • MIUI官方网站开发版如何让网站免费
  • 个人网站建设开题报告暴雪游戏
  • 荣耀商城官网网站wordpress转换小程序
  • 网站建设古典风格河南省示范校建设专题网站