阅读 354

LiveData粘性清除与ViewBing基础

LiveData概要

  • 粘性数据更新:子线程更新UI的原理(调用到了主线程的setValue并执行事件分发)

    • 将粘性数据放到了mData 中了;

    • 并且粘性数据的版本号会++(从-1增加为0)

      image-20211204135301143

    • postValue:子线程更新UI

      image-20211204134918601

    • 调用setValue

      image-20211204135136861

    • 进入主线程setValue:

  • 粘性数据如何触发

    • 在Activity中进行注册

      image-20211204135528722

    • 封装宿主与观察者

      image-20211204135714986

    • 在宿主的六个生命周期中都有去调用onStateChanged,此函数的最后一句(376行,当宿主存活时进入下一阶段)

      image-20211204135938454

    • 此时宿主存活(因为activeStateChanged函数的参数为bool值,且实参是shouldBeActive返回值,当宿主存活时,返回值为true);在423行,当宿主真正存活,进行事件分发,并且传入当前的this做为dispatchingValue参数

      image-20211204140209044

    • dispatchingValue参数:@Nullable ObserverWrapper initiator(ObserverWrapper是LifecycleBoundObserver的父类),

      this不空,进入这里

      image-20211204140346536

    • 比较数据版本号,判断是否分发粘性数据;最开始的版本号都是-1,但是之前++了(宿主此时的粘性数据的版本号为0,与此时的观察者中的数据版本-1不相等,此时不会进入下面的if退出;会执行

       observer.mLastVersion = mVersion;
       //noinspection unchecked
       observer.mObserver.onChanged((T) mData);//分发粘性数据复制代码
    • 比较版本号,决定是否分发事件:分发粘性数据

      image-20211204140807368

    • 继续调用:

      image-20211204141048418

LiveDataBus

  • 概述:

    • 使用反射技术,使得在比较版本号的时候,执行 return;从而不进行事件分发

       if (observer.mLastVersion >= mVersion) {
           return;
       }复制代码
    • 解决粘性事件:实现可以通过开关决定是否启用粘性(依托Kotlin默认参数)

    • 解决问题:

  • 设计思路:

    • 通过反射拿到宿主(mLasetVersion),观察者(mVersion),然后实现状态对齐(mLastVersion.set(observerWraper, mVersionValue))

    • 代码思路:

      image-20211217114516331

具体实现步骤

  • 设计总线

    •  //  封装总线类:存放订阅者,为了程序扩展性(方便用户指定操作对象,用户希望对于某个MyLiveData操作)所以搞成Map类型:
           Map类型:
               key:这条数据的名字
               Any:这条数据的类型复制代码
    • key:数据的名字作为标识,就是下面那个红色框里的

      image-20211216145606417

    • 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类:

    图片.png

  • 设计hook函数:使用反射

    • 目标:拿到mLastVersion(宿主),拿到mVersion(观察者的),然后对齐(直接赋值过去)就不会进行粘性的分发了

      image-20211217113511686

    • 代码: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函数,粘性开关就是第三个参数,默认是开启粘性的

    image-20211217115008709

  • 使用场景:

    • 在做登录功能的时候,只想处理本次的数据,不需要处理之前的粘性数据(粘性数据会带来Bug)那么就需要去掉粘性了

  • 实现细节:

    • 粘性数据:先触发后订阅,还能收到上次的信息(触发之前的),这个数据就是粘性数据了

ViewBinding

  • 使用场景:

    • 实现简单功能,属于轻量级DataBinding;

    • 性能很高,ViewBinding背后用的是Gradle技术(Gradle插件直接扫描布局,还是需要编译一下的)不是APT注解处理器(在编译期构建注解扫描)

  • DataBinding的优缺点:

    • 因为DataBinding中的双向绑定是比较消耗性能(在MVVM换成MVP,就是DataBinding作为持有者损耗性能)

    • 但是DataBinding功能强大,使用APT注解处理器技术全盘扫描布局,直接就干上去了

  • ViewBinding使用:

    • 系统会为MainActivity2创建一个ActivityMain2Binding

    • 点击它会直接跳转到activity_main2.xml

    • 打开就行了,打开之后,整个项目的ViewBinding就打开了

      image-20211217121313801

    • 不用再写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>复制代码
  • 实现效果:

    image-20211217124116803

    • 未注释掉MainActivity2中的setText语句:

      image-20211217124332613

    • 注释掉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控件的

      image-20211217131423373

    • 程序截图:使用ViewBinding是无法访问到当前布局文件(activity_main.xml)之外的控件(test.xml中的Button)的

      image-20211217131648437

    • MainActivity:这里可以看出来,是没有报错的

      image-20211217131031342

    • 真机测试结果:

      image-20211217131135289

    • activity_main.xml:就只有三个TextView控件

      image-20211217130717797

    • text.xml:这个里面放了一个Button

      image-20211217130827714

    • 工程结构展示:

      image-20211217130510444

    • 代码测试:验证插件弊端(在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原理才能弄懂其工作本质

  • 源码验证:

    • 打开路径

      image-20211217133703700

    • 编译一下项目:

      image-20211217133609781

    • 该路径下,生成了布局文件对应的对象,其中就有我们需要的TestBinding类

      image-20211217133918918

    • 在生成的TestBinding类中就有布局文件的控件

      image-20211217134032998

    • 底层还是findViewById,不过是封装的比较友好罢了

      image-20211217134220541

    • 打开ViewBinding开关

    • 编辑一个布局文件:test.xml

      image-20211217133453303


作者:WAsbry
链接:https://juejin.cn/post/7042561708266782727

玩站网免费分享SEO网站优化 技术及文章 伪原创工具 https://www.237it.com/ 


文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐