JetPack之LiveData粘性原因分析及hook解决

目录

  • 前言
  • 一、LiveData粘性原因分析
    • 1.1 发送消息流程
    • 1.2 监听消息流程
    • 1.3 根因分析
  • 二、hook解决


前言

在 Android 中,LiveData 的默认行为是粘性的,即 LiveData 在设置数据后,即使观察者订阅时已经有数据存在,观察者仍会立即收到这个数据。

在上一篇文章中JetPack之LiveData最后的案例我们看到了livedata的粘性事件,一般情况下,我们观察者先订阅,等到消息发生改变时,接收到消息,使用observe做一些更新UI等操作,但是粘性事件会导致我们会接收到订阅之前的数据,这在某些场景下并不是我们想要的。

一、LiveData粘性原因分析

1.1 发送消息流程

MutableLiveData的两个发送消息流程setValue、postValue。
setValue 只能在主线程使用,postValue可以在任何线程使用,它被调用时,其实也是通过handler切换到了主线程,再调用 的setValue

    protected void postValue(T value) {boolean postTask;synchronized (mDataLock) {postTask = mPendingData == NOT_SET;mPendingData = value;}if (!postTask) {return;}ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);}private final Runnable mPostValueRunnable = new Runnable() {@SuppressWarnings("unchecked")@Overridepublic void run() {Object newValue;synchronized (mDataLock) {newValue = mPendingData;mPendingData = NOT_SET;}setValue((T) newValue);}};

setValue 首先声明自己要在主线程中运行,然后 mVersion++;,最后调用dispatchingValue分发消息

    protected void setValue(T value) {assertMainThread("setValue");mVersion++;mData = value;dispatchingValue(null);}

dispatchingValue方法主要是对参数中的观察者进行了判空以及遍历,最后对每个遍历的对象调用了considerNotify方法

    void dispatchingValue(@Nullable ObserverWrapper initiator) {if (mDispatchingValue) {mDispatchInvalidated = true;return;}mDispatchingValue = true;do {mDispatchInvalidated = false;if (initiator != null) {considerNotify(initiator);initiator = null;} else {for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {considerNotify(iterator.next().getValue());if (mDispatchInvalidated) {break;}}}} while (mDispatchInvalidated);mDispatchingValue = false;}

considerNotify首先判断观察者是否存活,如果观察者不处于活动状态,则直接返回,不通知观察者。

  • 检查观察者是否应该处于活动状态。如果观察者不应该处于活动状态,则调用 activeStateChanged(false) 方法通知观察者状态已更改,并返回,不通知观察者。
  • 检查观察者上一次接收到的版本号是否大于或等于 LiveData 的当前版本号。如果是,则表示观察者已经接收过最新的数据,无需再次通知观察者。
  • 如果不是最新版本号,将 LiveData 的当前版本号赋值给观察者的上一次版本号,表示观察者已经接收到最新的数据。
  • 调用观察者的 onChanged 方法,将 LiveData 中存储的数据 mData 传递给观察者进行处理。
    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.if (!observer.shouldBeActive()) {observer.activeStateChanged(false);return;}if (observer.mLastVersion >= mVersion) {return;}observer.mLastVersion = mVersion;observer.mObserver.onChanged((T) mData);}

发送消息流程基本解读完毕,读到这里,有几个核心点:

1.mLastVersion:上一个版本号
2.mVersion 当前版本号
3.如果当前版本号不是最新版本号,那么版本号会被覆盖,然后回调观察者的onChanged方法,即我们更新UI等操作的地方

接下来看监听流程

1.2 监听消息流程

从监听的observe方法入手

  • assertMainThread(“observe”);: 检查当前线程是否为主线程,如果不是主线程则抛出异常。这是为了确保 observe 方法在主线程中调用,因为 LiveData 的观察者通常在主线程中更新 UI。
  • if (owner.getLifecycle().getCurrentState() == DESTROYED) { return; }: 检查生命周期所有者的当前状态是否为 DESTROYED(已销毁),如果是,则直接返回,不执行后续操作。
  • LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);: 创建一个 LifecycleBoundObserver 对象,将生命周期所有者和观察者传入。
  • ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);: 将观察者和对应的 LifecycleBoundObserver 对象放入 mObservers Map 中,如果之前已经存在相同的观察者则返回已存在的 ObserverWrapper 对象。
  • if (existing != null && !existing.isAttachedTo(owner)) { throw new IllegalArgumentException(“Cannot add the same observer” + " with different lifecycles"); }: 如果之前已经存在相同的观察者,但是观察者与不同的生命周期所有者绑定,则抛出异常,因为相同的观察者不能绑定到不同的生命周期所有者。
  • if (existing != null) { return; }: 如果之前已经存在相同的观察者且与相同的生命周期所有者绑定,则直接返回,不执行后续操作。
  • owner.getLifecycle().addObserver(wrapper);: 将 LifecycleBoundObserver 对象添加到生命周期所有者的 Lifecycle 中,这样当生命周期所有者的状态发生变化时,会通知绑定的观察者。
    public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {assertMainThread("observe");if (owner.getLifecycle().getCurrentState() == DESTROYED) {// ignorereturn;}LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);if (existing != null && !existing.isAttachedTo(owner)) {throw new IllegalArgumentException("Cannot add the same observer"+ " with different lifecycles");}if (existing != null) {return;}owner.getLifecycle().addObserver(wrapper);}

从 LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);入手,看一下内部做了什么
LifecycleBoundObserver 继承了 ObserverWrapper ,实现了 LifecycleEventObserver 接口。

 class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {@NonNullfinal LifecycleOwner mOwner;LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {super(observer);mOwner = owner;}.......

LifecycleEventObserver 当 lifecycle 状态改变的时候会感应到,并进行回调onStateChanged方法

public fun interface LifecycleEventObserver : LifecycleObserver {/*** Called when a state transition event happens.** @param source The source of the event* @param event The event*/public fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event)
}

当onStateChanged的状态改变传递到LifecycleBoundObserver时,会调用LifecycleBoundObserver的onStateChanged方法

  class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {@NonNullfinal LifecycleOwner mOwner;LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {super(observer);mOwner = owner;}@Overrideboolean shouldBeActive() {return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);}@Overridepublic void onStateChanged(@NonNull LifecycleOwner source,@NonNull Lifecycle.Event event) {Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();if (currentState == DESTROYED) {removeObserver(mObserver);return;}Lifecycle.State prevState = null;while (prevState != currentState) {prevState = currentState;activeStateChanged(shouldBeActive());currentState = mOwner.getLifecycle().getCurrentState();}}

最终回调到ObserverWrapper 的activeStateChanged方法,观察一下ObserverWrapper类结构,原来之前的mLastVersion是在这里定义的,默认值为-1,回到activeStateChanged方法,可以看到最终也会回到 dispatchingValue方法,只是负责分发当前观察者(this),不像发送消息流程分发到全部的观察者。

public abstract class LiveData<T> {@SuppressWarnings("WeakerAccess") /* synthetic access */final Object mDataLock = new Object();static final int START_VERSION = -1;@SuppressWarnings("WeakerAccess") /* synthetic access */
 private abstract class ObserverWrapper {final Observer<? super T> mObserver;boolean mActive;int mLastVersion = START_VERSION;ObserverWrapper(Observer<? super T> observer) {mObserver = observer;}abstract boolean shouldBeActive();boolean isAttachedTo(LifecycleOwner owner) {return false;}void detachObserver() {}void activeStateChanged(boolean newActive) {if (newActive == mActive) {return;}// immediately set active state, so we'd never dispatch anything to inactive// ownermActive = newActive;changeActiveCounter(mActive ? 1 : -1);if (mActive) {dispatchingValue(this);}}}

1.3 根因分析

mLastVersion 的默认初始值是-1,mVersion 的默认初始值也是-1,当我们先执行发送的时候,进行了自增,mVersion 就变成了0,当我们执行observe 进行监听的时候,observer.mLastVersion >= mVersion 这个条件就不成立了,因为此时mLastVersion 是-1,小于 mVersion 了。
发送和监听都会调用dispatchingValue方法,但mVersion只要发送就会在setValue方法中++,而mLastVersion永远只能在setValue方法后的considerNotify方法中被置为mVersion的值。

    public LiveData() {mData = NOT_SET;mVersion = START_VERSION;}public abstract class LiveData<T> {@SuppressWarnings("WeakerAccess") /* synthetic access */final Object mDataLock = new Object();static final int START_VERSION = -1;

二、hook解决

在安卓开发中,“Hook” 是指通过修改系统或应用程序的行为,来实现某种特定的功能或者改变程序的默认行为。常见的 Hook 技术包括方法 Hook、类 Hook、系统 Hook 等。以下是对安卓 Hook 的详细解释:

方法 Hook
方法 Hook 是指在程序运行时替换或者修改某个方法的实现逻辑,以达到特定的目的。
通过方法 Hook,可以拦截系统或第三方库的方法调用,修改方法的参数或返回值,实现功能增强或者数据篡改等操作。
常见的方法 Hook 框架有 Xposed、Dexposed、Frida 等。
类 Hook
类 Hook 是指在程序运行时替换或者修改某个类的实现逻辑,以达到特定的目的。
通过类 Hook,可以修改类的属性、方法行为,实现功能增强或者数据篡改等操作。
类 Hook 通常需要使用字节码操作技术,如 ASM、Javassist 等。
系统 Hook
系统 Hook 是指修改系统层的行为,如修改系统服务、系统调用等,以实现对系统行为的控制。
通过系统 Hook,可以实现系统级别的功能增强、权限管理、安全加固等操作。
系统 Hook 需要对系统底层进行深入了解,通常需要 root 权限才能实现。

Hook 的应用场景:

功能增强:通过 Hook 修改系统或应用程序的行为,实现功能增强或定制化功能。
数据篡改:通过 Hook 修改数据传递或处理逻辑,实现数据篡改或数据劫持。
安全加固:通过 Hook 检测恶意行为、加固系统安全,防止恶意软件的攻击。
调试分析:通过 Hook 获取程序运行时的信息,进行调试分析或性能优化。

我们使用hook反射的方式,在每次Observe方法中,将mLastVersion赋值为mVersion,这样下次就不会调用到onChanged方法中,去除了粘性!
核心代码

        private void hook(Observer<? super T> observer) {try {Field mObserversField = LiveData.class.getDeclaredField("mObservers");mObserversField.setAccessible(true);Object mObserversObject = mObserversField.get(this);Class<?> mObserversClass = mObserversObject.getClass();Method get = mObserversClass.getDeclaredMethod("get", Object.class);get.setAccessible(true);Object invokeEntry = get.invoke(mObserversObject, observer);Object observerWrapper = null;if (invokeEntry != null && invokeEntry instanceof Map.Entry) {observerWrapper = ((Map.Entry) invokeEntry).getValue();}if (observerWrapper == null) {throw new NullPointerException("observerWrapper is null");}Log.d("Henry","属性是什么"+ observerWrapper.getClass());Class<?> superClass = observerWrapper.getClass().getSuperclass();Field mLastVersion = superClass.getDeclaredField("mLastVersion");mLastVersion.setAccessible(true);Field mVersion = LiveData.class.getDeclaredField("mVersion");mVersion.setAccessible(true);Object mVersionValue = mVersion.get(this);mLastVersion.set(observerWrapper, mVersionValue);} catch (Exception e) {e.printStackTrace();}}

示例:采取反射,先发消息后订阅,不会接受到旧消息。
OkLiveDataBusJava.java

public class OkLiveDataBusJava {//存放订阅者private static final Map<String, BusMutableLiveData<?>> bus = new HashMap<>();public synchronized static <T> BusMutableLiveData<T> with(String key, Class<T> type, boolean ishook) {if (!bus.containsKey(key)) {bus.put(key, new BusMutableLiveData<>(ishook));}return (BusMutableLiveData<T>) bus.get(key);}public static class BusMutableLiveData<T> extends MutableLiveData<T> {private boolean ishook;private BusMutableLiveData(boolean ishook) {this.ishook = ishook;}@Overridepublic void observe(LifecycleOwner owner, Observer<? super T> observer) {super.observe(owner, observer);if (ishook) {hook(observer);Log.d("Henry", " 启用hook");} else {Log.d("Henry", " 不启用hook");}}private void hook(Observer<? super T> observer) {try {Field mObserversField = LiveData.class.getDeclaredField("mObservers");mObserversField.setAccessible(true);Object mObserversObject = mObserversField.get(this);Class<?> mObserversClass = mObserversObject.getClass();Method get = mObserversClass.getDeclaredMethod("get", Object.class);get.setAccessible(true);Object invokeEntry = get.invoke(mObserversObject, observer);Object observerWrapper = null;if (invokeEntry != null && invokeEntry instanceof Map.Entry) {observerWrapper = ((Map.Entry) invokeEntry).getValue();}if (observerWrapper == null) {throw new NullPointerException("observerWrapper is null");}Log.d("Henry","属性是什么"+ observerWrapper.getClass());Class<?> superClass = observerWrapper.getClass().getSuperclass();Field mLastVersion = superClass.getDeclaredField("mLastVersion");mLastVersion.setAccessible(true);Field mVersion = LiveData.class.getDeclaredField("mVersion");mVersion.setAccessible(true);Object mVersionValue = mVersion.get(this);mLastVersion.set(observerWrapper, mVersionValue);} catch (Exception e) {e.printStackTrace();}}}
}

OkLiveDataBusActivity

public class OkLiveDataBusActivity extends AppCompatActivity {Button button;@SuppressLint("MissingInflatedId")@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_ok_live_data_bus);button = findViewById(R.id.OKlivedata_jump);button.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {startActivity(new Intent(OkLiveDataBusActivity.this,OkLiveDataBusSecondActivity.class));}});OkLiveDataBusJava.with("data", String.class, true).postValue("old 数据-----------");}
}

OkLiveDataBusSecondActivity

public class OkLiveDataBusSecondActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_ok_live_data_bus_second);OkLiveDataBusJava.with("data", String.class, true).observe(this,new Observer<String>() {@Overridepublic void onChanged(String s) {Toast.makeText(OkLiveDataBusSecondActivity.this,"获取数据" + s, Toast.LENGTH_SHORT).show();}});new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}OkLiveDataBusJava.with("data", String.class, true).postValue("new 数据-----------");}}).start();}
}

测试一下:
在这里插入图片描述
来张美图犒劳一下
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/761086.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【链表】Leetcode 19. 删除链表的倒数第 N 个结点【中等】

删除链表的倒数第 N 个结点 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5] 解题思路 1、使用快慢指针找到要删除节点的前一个节点。2、删…

使用Jmeter进行http接口测试的实践

前言&#xff1a; 本文主要针对http接口进行测试&#xff0c;使用Jmeter工具实现。 Jmter工具设计之初是用于做性能测试的&#xff0c;它在实现对各种接口的调用方面已经做的比较成熟&#xff0c;因此&#xff0c;本次直接使用Jmeter工具来完成对Http接口的测试。 一、开发接口…

深入理解二叉树构建和中序遍历

在计算机科学中&#xff0c;二叉树是一种重要的数据结构&#xff0c;用于模拟层次化结构的关系。本文将介绍一个简单的C语言程序&#xff0c;该程序实现了对输入字符数组的解析&#xff0c;并构建相应的二叉树&#xff0c;随后对二叉树进行中序遍历。 二叉树定义 首先&#x…

Redis监控工具

Redis 是一种 NoSQL 数据库系统&#xff0c;以其速度、性能和灵活的数据结构而闻名。Redis 在许多领域都表现出色&#xff0c;包括缓存、会话管理、游戏、排行榜、实时分析、地理空间、叫车、聊天/消息、媒体流和发布/订阅应用程序。Redis 数据集完全存储在内存中&#xff0c;这…

揭秘爆红AI图像增强神器:Magnific AI如何做到1亿像素放大?

最近有个很火的AI图像增强应用&#xff0c;叫Magnific AI。 你知道吗&#xff0c;它发布一个多月就有40万人注册了&#xff01; 这个应用确实非常实用&#xff0c;它不仅利用AI技术放大了图像&#xff0c;还能提升分辨率&#xff0c;从而使图片呈现得更加清晰。 值得一提的是…

扩展以太网(数据链路层)

目录 一、在物理层扩展以太网 二、在数据链路层扩展以太网 三、以太网交换机的特点 四、以太网交换机的交换方式 五、以太网交换机的自学习功能 六、小结 一、在物理层扩展以太网 使用光纤扩展&#xff1a; • 主机使用光纤&#xff08;通常是一对光纤&#xff09;和…

跨境电商商品数据集爬取方案|跨境电商商品采集API接口

跨境电商数据集介绍 我们就以电商商品的数据集作为依托&#xff0c;从而来了解什么是数据集&#xff0c;可以通过什么方式来进行数据集的获取。 什么是数据集 电商商品数据集通常是指收集自电子商务平台的商品信息的结构化数据集合。这些数据包括但不限于商品名称、价格、描…

有关Theano和PyTensor库

根据Github里面的介绍&#xff0c;PyTensor是源于Theano&#xff0c; Theano目前应该已经不再开发了&#xff0c;更新都是很多年前。 因此PyTensor在背景介绍中说 PyTensor is a fork of Aesara, which is a fork of Theano. Theano和PyTensor都是计算相关的库&#xff0c;可以…

数据结构面试常见问题之- Sort with Swap(0,*)

&#x1f600;前言 在数据结构面试中&#xff0c;排序算法是考察重点之一。传统的排序算法&#xff0c;例如冒泡排序、快速排序等&#xff0c;都依赖于元素之间的比较和交换操作。然而&#xff0c;在某些情况下&#xff0c;我们可能只允许使用特定的交换操作&#xff0c;例如只…

【网络原理】详解HTTPS协议加密过程

文章目录 &#x1f334;HTTPS协议是什么&#xff1f;&#x1f384;运营商劫持事件&#x1f332;HTTPS的工作过程&#x1f338;对称加密&#x1f338;非对称加密&#x1f338;引入证书&#x1f338;完整流程 &#x1f333;HTTPS加密总结⭕总结 &#x1f334;HTTPS协议是什么&…

Spring Cloud Alibaba微服务从入门到进阶(七)(服务容错-Sentinel)

雪崩效应 我们把基础服务故障&#xff0c;导致上层服务故障&#xff0c;并且这个故障不断放大的过程&#xff0c;成为雪崩效应。 雪崩效应&#xff0c;往往是因为服务没有做好容错造成的。 微服务常见容错方案 仓壁模式 比如让controller有自己独立的线程池&#xff0c;线程池满…

Windows 设置多显示器显示

Windows 设置多显示器显示 1. Windows 7 设置 HDMI 输出2. Windows 11 设置多显示器显示References 1. Windows 7 设置 HDMI 输出 2. Windows 11 设置多显示器显示 ​​​ References [1] Yongqiang Cheng, https://yongqiang.blog.csdn.net/

畅谈AIGC,ISIG-AIGC技术与应用发展峰会成功举办

3月16日&#xff0c;第四届ISIG中国产业智能大会在上海中庚聚龙酒店如期开幕&#xff0c;此次大会由苏州市金融科技协会指导、企智未来科技&#xff08;RPA中国、LowCode低码时代、AIGC开放社区&#xff09;主办。大会聚集了来自不同领域的专家学者、行业领军人物及技术研发者&…

Linux系统——nload命令

目录 引言 一、nload安装 二、nload命令详解 1.命令使用 2.命令详解 3.命令选项 3.1-u选项 nload -u h 自动变更单位&#xff0c;Bit/s nload -u H 自动变更单位&#xff0c;Byte/s 3.2-m选项 nload -m 不显示流量图 nload -m -H ens33 不显示流量图&#xff0c;以By…

深度学习训练前标准的LMDB文件(data.mdb和lock.mdb)制作(附代码)

目录 一、LMDB文件二、准备训练集三、安装basicsr包四、LMDB文件制作4.1 参数修改4.2 其它格式图片修改4.3 代码4.4 转换结果4.4.1 data.mdb文件4.4.2 lock.mdb文件4.4.3 meta_info.txt文件 五、总结 一、LMDB文件 在训练的时候使用LMDB 存储形式可以加快IO 和CPU 解压缩的速度…

点餐小程序开发:如何通过抽奖与消费者互动

随着科技的发展&#xff0c;越来越多的商家开始使用点餐小程序来提升自己的服务质量和效率。然而&#xff0c;仅仅提供点餐服务并不能满足消费者的需求&#xff0c;他们还需要一种方式来增加与商家的互动&#xff0c;提高消费体验。抽奖活动就是一种非常有效的互动方式&#xf…

FreeRTOS 消息队列

1. 队列简介 1.1 队列的概念 队列是任务到任务、任务到中断、中断到任务数据交流的一种机制&#xff08;消息传递&#xff09; 类似全局变量&#xff1f;假设有一个全局变量a 0&#xff0c;现有两个任务都在写这个变量 a&#xff1a; 大家想象一下如果任务 1 运行一次&#…

[NOIP1998 提高组] 拼数

[NOIP1998 提高组] 拼数 题目描述 设有 n n n 个正整数 a 1 … a n a_1 \dots a_n a1​…an​&#xff0c;将它们联接成一排&#xff0c;相邻数字首尾相接&#xff0c;组成一个最大的整数。 输入格式 第一行有一个整数&#xff0c;表示数字个数 n n n。 第二行有 n n …

一些刷题需要用的大数据

无符号版本和有符号版本的区别就是有符号类型需要使用一个bit来表示数字的正负。 如果需声明无符号类型的话就需要在类型前加上unsigned。 整型的每一种都分为&#xff1a;无符号&#xff08;unsigned&#xff09;和有符号&#xff08;signed&#xff09;两种类型&#xff08;f…