LiveData粘性清除与ViewBing基础
LiveData概要
粘性数据更新:子线程更新UI的原理(调用到了主线程的setValue并执行事件分发)
将粘性数据放到了mData 中了;
并且粘性数据的版本号会++(从-1增加为0)
postValue:子线程更新UI
调用setValue
进入主线程setValue:
粘性数据如何触发
在Activity中进行注册
封装宿主与观察者
在宿主的六个生命周期中都有去调用onStateChanged,此函数的最后一句(376行,当宿主存活时进入下一阶段)
此时宿主存活(因为activeStateChanged函数的参数为bool值,且实参是shouldBeActive返回值,当宿主存活时,返回值为true);在423行,当宿主真正存活,进行事件分发,并且传入当前的this做为dispatchingValue参数
dispatchingValue参数:@Nullable ObserverWrapper initiator(ObserverWrapper是LifecycleBoundObserver的父类),
this不空,进入这里
比较数据版本号,判断是否分发粘性数据;最开始的版本号都是-1,但是之前++了(宿主此时的粘性数据的版本号为0,与此时的观察者中的数据版本-1不相等,此时不会进入下面的if退出;会执行
observer.mLastVersion = mVersion; //noinspection unchecked observer.mObserver.onChanged((T) mData);//分发粘性数据复制代码
比较版本号,决定是否分发事件:分发粘性数据
继续调用:
LiveDataBus
概述:
使用反射技术,使得在比较版本号的时候,执行 return;从而不进行事件分发
if (observer.mLastVersion >= mVersion) { return; }复制代码
解决粘性事件:实现可以通过开关决定是否启用粘性(依托Kotlin默认参数)
解决问题:
设计思路:
通过反射拿到宿主(mLasetVersion),观察者(mVersion),然后实现状态对齐(mLastVersion.set(observerWraper, mVersionValue))
代码思路:
具体实现步骤
设计总线
// 封装总线类:存放订阅者,为了程序扩展性(方便用户指定操作对象,用户希望对于某个MyLiveData操作)所以搞成Map类型: Map类型: key:这条数据的名字 Any:这条数据的类型复制代码
key:数据的名字作为标识,就是下面那个红色框里的
value:BusMutableLiveData< Any >:
使用懒加载:kotlin的好特性
HashMap:数据结构
就不像之前的哪像直接写死,这样的扩展性更好
用户使用参数进行指定,决定对于何种类型数据进行操作
代码
private val bus : MutableMap<String, BusMutableLiveData<Any>> by lazy { HashMap() }复制代码
参数解释:为什么要使用Map
设计with函数:暴露函数,给外界注册订阅者关系
概述:
/* // 暴露一个函数,给外界注册 订阅者关系 //扩展性:使用泛型,with函数的实参泛型T类型一旦确定,那么其返回值的泛型T类型随机确定并且下面的BusMutableLiveData中的两个泛型T类型也随之确定 //安全性:搞个同步锁,并发环境 //第二个参数:控制变化,直接传相应的字节码文件就行了 //第三个参数:开关,是否启用数据粘性(配合kotlin中的默认参数,默认是开启数据粘性的)*/复制代码
代码:
@Synchronized fun <T> with(key: String, type: Class<T>, isStick: Boolean = true) : BusMutableLiveData<T> { if (!bus.containsKey(key)) {//判断是否有重复的KEY bus[key] = BusMutableLiveData(isStick)//运算符重载 } return bus[key] as BusMutableLiveData<T>//强制类型转换 }复制代码
设计BusMutableLiveData类:
设计hook函数:使用反射
目标:拿到mLastVersion(宿主),拿到mVersion(观察者的),然后对齐(直接赋值过去)就不会进行粘性的分发了
代码:Kotlin
private fun hook(observer: Observer<in T>) { // TODO 1.得到mLastVersion // 获取到LivData的类中的mObservers对象,::class这个是kotlin的,需要转换成java的 val liveDataClass = LiveData::class.java //拿到字段 val mObserversField: Field = liveDataClass.getDeclaredField("mObservers") mObserversField.isAccessible = true // 私有修饰也可以访问 // 获取到这个成员变量的对象 Any == Object val mObserversObject: Any = mObserversField.get(this) // 得到map对象的class对象 private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers = val mObserversClass: Class<*> = mObserversObject.javaClass // 获取到mObservers对象的get方法 protected Entry<K, V> get(K k) { val get: Method = mObserversClass.getDeclaredMethod("get", Any::class.java) get.isAccessible = true // 私有修饰也可以访问 // 执行get方法 val invokeEntry: Any = get.invoke(mObserversObject, observer) // 取到entry中的value is "AAA" is String is是判断类型 as是转换类型 var observerWraper: Any? = null if (invokeEntry != null && invokeEntry is Map.Entry<*, *>) { observerWraper = invokeEntry.value } if (observerWraper == null) { throw NullPointerException("observerWraper is null") } // 得到observerWraperr的类对象 val supperClass: Class<*> = observerWraper.javaClass.superclass val mLastVersion: Field = supperClass.getDeclaredField("mLastVersion") mLastVersion.isAccessible = true // TODO 2.得到mVersion val mVersion: Field = liveDataClass.getDeclaredField("mVersion") mVersion.isAccessible = true // TODO 3.mLastVersion=mVersion,状态对齐 val mVersionValue: Any = mVersion.get(this) mLastVersion.set(observerWraper, mVersionValue) }复制代码
代码:java版本:
private void hook(Observer<? super T> observer) { try { // TODO 1.得到mLastVersion // 获取到LivData的类中的mObservers对象 Class<LiveData> liveDataClass = LiveData.class; Field mObserversField = liveDataClass.getDeclaredField("mObservers"); mObserversField.setAccessible(true); // 获取到这个成员变量的对象 Object mObserversObject = mObserversField.get(this); // 得到map对象的class对象 Class<?> mObserversClass = mObserversObject.getClass(); // 获取到mObservers对象的get方法 Method get = mObserversClass.getDeclaredMethod("get", Object.class); get.setAccessible(true); // 执行get方法 Object invokeEntry = get.invoke(mObserversObject, observer); // 取到entry中的value Object observerWraper = null; if (invokeEntry != null && invokeEntry instanceof Map.Entry) { observerWraper = ((Map.Entry) invokeEntry).getValue(); } if (observerWraper == null) { throw new NullPointerException("observerWraper is null"); } // 得到observerWraperr的类对象 Class<?> supperClass = observerWraper.getClass().getSuperclass(); Field mLastVersion = supperClass.getDeclaredField("mLastVersion"); mLastVersion.setAccessible(true); // TODO 2.得到mVersion Field mVersion = liveDataClass.getDeclaredField("mVersion"); mVersion.setAccessible(true); // TODO 3.mLastVersion=mVersion Object mVersionValue = mVersion.get(this); mLastVersion.set(observerWraper, mVersionValue); } catch (Exception e) { e.printStackTrace(); } }复制代码
带有粘性开关的完整代码:
package com.derry.livedatabusandviewbinding.simple1 import android.util.Log import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import java.lang.reflect.Field import java.lang.reflect.Method /** * 单例模式 设置了一个开关,通过函数参数决定是否启用黏性事件(有关闭黏性的开关) KT的版本 */ object OKLiveDataBusKT { /* // 封装总线类:存放订阅者,为了程序扩展性(方便用户指定操作对象,用户希望对于某个MyLiveData操作)所以搞成Map类型: Map类型: key:这条数据的名字 Any:这条数据的类型 //尽量使用懒加载 */ private val bus : MutableMap<String, BusMutableLiveData<Any>> by lazy { HashMap() } /* // 暴露一个函数,给外界注册 订阅者关系 //扩展性:使用泛型,with函数的实参泛型T类型一旦确定,那么其返回值的泛型T类型随机确定并且下面的BusMutableLiveData中的两个泛型T类型也随之确定 //安全性:搞个同步锁,并发环境 //第二个参数:控制变化,直接传相应的字节码文件就行了 //第三个参数:开关,是否启用数据粘性(配合kotlin中的默认参数,默认是开启数据粘性的)*/ @Synchronized fun <T> with(key: String, type: Class<T>, isStick: Boolean = true) : BusMutableLiveData<T> { //判断是否有重复的KEY if (!bus.containsKey(key)) { bus[key] = BusMutableLiveData(isStick)//运算符重载 } return bus[key] as BusMutableLiveData<T>//强制类型转换 } // 私有主构造,不让外界new,那么就要写次构造函数调用主构造进行激活 //前面那个T会影响后面那个T class BusMutableLiveData<T> private constructor() : MutableLiveData<T>() { //粘性开关 var isStick: Boolean = false // 次构造调用主构造进行激活 constructor(isStick: Boolean) : this() { this.isStick = isStick } // 对系统代码进行重载,目的就是实现带开关的粘性清除(可以开,可以关) override fun observe(owner: LifecycleOwner, observer: Observer<in T>) { /*这句话是不能删掉的,hook操作不能破坏原有系统,只是多开了一条路而已; 并且执行下面这句代码的时候,只是起到了一个注册而已; 真正执行代码是由宿主的生命周期变化从而触发的*/ super.observe(owner, observer) // 这句会执行父类的 // 启用系统的功能 不写就破坏了 //使用参数,决定是否启用粘性 if (!isStick) { hook(observer = observer) Log.d("derry", "Kotlin版本的 不启用黏性") } else { Log.d("derry", "Kotlin版本的 启用黏性") } } //使用反射,去掉数据粘性 private fun hook(observer: Observer<in T>) { // TODO 1.得到mLastVersion // 获取到LivData的类中的mObservers对象,后面的.java将其转为java的 val liveDataClass = LiveData::class.java //拿到字段 val mObserversField: Field = liveDataClass.getDeclaredField("mObservers") mObserversField.isAccessible = true // 私有修饰也可以访问 // 获取到这个成员变量的对象 Any == Object val mObserversObject: Any = mObserversField.get(this) // 得到map对象的class对象 private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers = val mObserversClass: Class<*> = mObserversObject.javaClass // 获取到mObservers对象的get方法 protected Entry<K, V> get(K k) { val get: Method = mObserversClass.getDeclaredMethod("get", Any::class.java) get.isAccessible = true // 私有修饰也可以访问 // 执行get方法 val invokeEntry: Any = get.invoke(mObserversObject, observer) // 获取entry中的value is "AAA" is String is是判断类型 as是转换类型 var observerWraper: Any? = null if (invokeEntry != null && invokeEntry is Map.Entry<*, *>) { observerWraper = invokeEntry.value } if (observerWraper == null) { throw NullPointerException("observerWraper is null") } // 得到observerWraperr的类对象 val supperClass: Class<*> = observerWraper.javaClass.superclass val mLastVersion: Field = supperClass.getDeclaredField("mLastVersion") mLastVersion.isAccessible = true // TODO 2.得到mVersion val mVersion: Field = liveDataClass.getDeclaredField("mVersion") mVersion.isAccessible = true//私有的也可以访问 // TODO 3.mLastVersion=mVersion val mVersionValue: Any = mVersion.get(this) mLastVersion.set(observerWraper, mVersionValue)//状态对齐 } } }复制代码
使用:调用with函数,粘性开关就是第三个参数,默认是开启粘性的
使用场景:
在做登录功能的时候,只想处理本次的数据,不需要处理之前的粘性数据(粘性数据会带来Bug)那么就需要去掉粘性了
实现细节:
粘性数据:先触发后订阅,还能收到上次的信息(触发之前的),这个数据就是粘性数据了
ViewBinding
使用场景:
实现简单功能,属于轻量级DataBinding;
性能很高,ViewBinding背后用的是Gradle技术(Gradle插件直接扫描布局,还是需要编译一下的)不是APT注解处理器(在编译期构建注解扫描)
DataBinding的优缺点:
因为DataBinding中的双向绑定是比较消耗性能(在MVVM换成MVP,就是DataBinding作为持有者损耗性能)
但是DataBinding功能强大,使用APT注解处理器技术全盘扫描布局,直接就干上去了
ViewBinding使用:
系统会为MainActivity2创建一个ActivityMain2Binding
点击它会直接跳转到activity_main2.xml
打开就行了,打开之后,整个项目的ViewBinding就打开了
不用再写findViewById了
同时还会出现一个非常神奇的现象:
MainActivity2
package com.derry.view_binding; import android.os.Bundle; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import com.derry.view_binding.databinding.ActivityMain2Binding; import com.derry.view_binding.databinding.TestBinding; // ViewBinding背后不是用 APT 注解处理器 使用什么? 答:gradle插件 public class MainActivity2 extends AppCompatActivity { ActivityMain2Binding main2Binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); main2Binding = ActivityMain2Binding.inflate(getLayoutInflater()); setContentView(main2Binding.getRoot()); /* main2Binding.tv1.setText("AAA"); main2Binding.tv2.setText("BNB"); main2Binding.tv3.setText("DDDD");*/ } }复制代码
activity_main2.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <TextView android:id="@+id/tv1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:textSize="30sp" android:textColor="@color/teal_200" /> <TextView android:id="@+id/tv2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:textSize="30sp" android:textColor="@color/purple_500" /> <TextView android:id="@+id/tv3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" android:textSize="30sp" android:textColor="@color/black" /> </LinearLayout>复制代码
实现效果:
未注释掉MainActivity2中的setText语句:
注释掉MainActivity2中的setText 语句
Kotlin-android-extensions插件与ViewBinding技术
ViewBinding:
始终是面向布局的(始终与布局挂钩),Gradle插件扫描布局文件在项目编译后生成对应的类;
面向对象的设计思想,将布局文件看做一个对象;在项目编译后生成对应的类;
比Kotlin-android-extensions出现晚
Kotlin-android-extensions
在MainActivity2中使用这个插件访问到了test.xml的控件了,就错了;
ViewBinding不会出现这个问题:连提示都没有
工作原理:依托于Kotlin的绑定机制
这个还是Gradle插件
在ViewBinding之前出现并流行
使用时有风险:人为失误造成在当前Activity中访问另外一个xml文件中的控件:空指针异常
代码展示:
程序截图:可以看到是可以正常访问activity_main.xml中的三个TextView控件的
程序截图:使用ViewBinding是无法访问到当前布局文件(activity_main.xml)之外的控件(test.xml中的Button)的
MainActivity:这里可以看出来,是没有报错的
真机测试结果:
activity_main.xml:就只有三个TextView控件
text.xml:这个里面放了一个Button
工程结构展示:
代码测试:验证插件弊端(在MainActivity中依托插件去访问text.xml中的控件)
代码测试:验证ViewBinding优点
导入插件:
plugins { id 'com.android.application' id 'kotlin-android' // 有绑定机制后,就不需要 findv gradle插件 id 'kotlin-android-extensions' // 启用 绑定机制 }复制代码
启用ViewBinding
// 启用ViewBinding viewBinding { enabled true }复制代码
环境准备:编辑build.gradle
ViewBinding原理解析
概述:使用Gradle插件进行布局文件扫描+动态生成类(这个类里面对findViewById做了封装而已)
需要熟悉Gradle原理才能弄懂其工作本质
源码验证:
打开路径
编译一下项目:
该路径下,生成了布局文件对应的对象,其中就有我们需要的TestBinding类
在生成的TestBinding类中就有布局文件的控件
底层还是findViewById,不过是封装的比较友好罢了
打开ViewBinding开关
编辑一个布局文件:test.xml
作者:WAsbry
链接:https://juejin.cn/post/7042561708266782727
玩站网免费分享SEO网站优化 技术及文章 伪原创工具 https://www.237it.com/