【Jetpack】学穿:ViewBinding → 视图绑定

0x0、Jetpack简介

手机厂商还没卷完Android 12,Android 13 就悄然声息地来了,距离Google 2008年9月22日发布Android 1.0,已过去13个年头。

历经13年的打磨和沉淀,Android体系与社区生态已非常成熟,开发者从最初的框架少、没规范、代码都得自己写,到轮子、框架满天飞。得益于此,我们少做了很多脏活累活(基础代码),把更多的时间花在业务逻辑上,达成快速迭代的目的。

但琳琅满目的技术选型,也让开发者无从选择,以致于做出的应用良莠不齐,Android官方一直没推出开发标准。而一些技术社区出于更高效地进行协同开发,逐渐引入了MVP、MVVM等应用开发架构。使用这些架构开发出的应用,从项目质量、代码可读性与可维护性来说,都更加出色,所以这些框架和技术逐渐流行起来。

Google一直致力于Android生态环境的搭建,为了解决开发碎片化,方便广大开发者,在2018年的 Google I/O大会上推出了 全新的Android Jetpack应用开发架构。它是一套 库、工具和指南的集合,称作 Jetpack开发工具集 可能更贴切。

Android Jetpack 向后兼容,是为现代设计实践而设计的,如关注点分离、测试能力、松散耦合、观察者模式、控制翻转、Kotlin集成等生产力特性。旨在让开发者用更少的代码,更易构建出健壮、高质量的应用程序。

网上盛传的一张将Jetpack组件分为四大类的老图:

【Jetpack】学穿:ViewBinding → 视图绑定_第1张图片

图片来源:Use Android Jetpack to Accelerate Your App Development,简单介绍下~

Architecture → 架构

帮助开发者设计稳健、可测试、易维护的应用。

  • Data Binding数据绑定,可使用 声明式 将布局中的界面组件绑定到应用中的数据源;
  • Lifecycles生命周期感知,可感知和响应Activity和Fragment的生命周期状态的变化;
  • LiveData可观察的数据持有者类,与常规Observable不同,它是具有生命周期感知的;
  • Navigation应用内导航,Fragment的管理框架,或者说路由;
  • Paging列表分页,可以轻松实现分页预加载以达到无限滑动的效果;
  • Room轻量级ORM数据库,本质上是一个SQLite抽象层,注解 + 编译时自动生成功能类;
  • ViewModel数据存储组件,具备生命周期感知能力;
  • WorkManager托管延时任务,即使APP被杀、或设备重启,只要TaskRecord还存在最近访问列表中,都会执行;

Foundation → 基础

提供横向功能,如:向后兼容、测试、安全、Kotlin语言支持;

  • AppCompat → 帮助较低版本的Android系统进行兼容;
  • Android KTX → 基于Kotlin特性为Android、Jetpack提供一些简易易用的扩展;
  • Multidex → 为具有多个Dex文件应用提供支持;
  • Test → 用于单元和运行时界面测试的 Android 测试框架;
  • Benchmark(性能检测)、Security(安全)等;

UI → 界面

  • Animation & Transition → 内置动画及自定义动画效果;
  • Emoji → 即便用户没有更新Android系统也可以获取最新的表情符号;
  • Auto(车)、TV、WearOS;
  • Fragment → 组件化界面的基本单位;
  • Layout → 用XML中声明UI元素或者在代码中实例化UI元素;
  • Paletee → 从调色板中提取出有用的信息;

Behavior → 行为

  • Download Manager → 处理长时间运行的HTTP下载、超时重连的系统服务;
  • Media & Playback → 用于媒体播放和路由(包括 Google Cast)的向后兼容 API;
  • Permissions → 用于检查和请求应用权限的兼容性API;
  • Notifications → 提供向后兼容的通知API,支持Wear和Auto;
  • Sharing → 提供适合应用操作栏的共享操作;
  • Slices → 一种UI模板,创建可在营养外部显示应用数据的灵活界面元素;

咳咳,Android官网已经找不到上面这个图了,猜测官方旨在强化 Architecture架构组件,其他三个只是对已有内容的收集整理。实际开发中,也是这部分的组件用得多一些,Jetpack库可单独使用,也可以组合使用,开发者可按需选择。对此,官方还进行了更细致的分类,具体可见:《按类型探索Jetpack库》

关于Jetpack的简介就到这里,在选型时弄清楚组件的 存在缘由、责任边界,就能有的放矢。本节开始折腾,先带来一个超简单的 → ViewBinding(视图绑定)


0x2、从 手写findViewById 到 ViewBinding

从早期对照XML手写findViewById,到在线工具自动生成:

【Jetpack】学穿:ViewBinding → 视图绑定_第2张图片

到AS插件自动生成:

【Jetpack】学穿:ViewBinding → 视图绑定_第3张图片

再到View注入框架 → ButterKnife(黄油刀)

【Jetpack】学穿:ViewBinding → 视图绑定_第4张图片

后面Kotlin普及,带来了扩展创建 kotlin-android-extensions(KAE)直接拿id当控件用,原理:

类中定义一个存储控件引用的HashMap,id为key,控件实例为value,当用到控件时,先查HashMap中该id对应的实例是否缓存,是返回,否findViewById获取实例存到HashMap中,同时把找到的实例返回。

粗暴的 空间换时间,方便是挺方便的,但也存在下述问题:

【Jetpack】学穿:ViewBinding → 视图绑定_第5张图片

好景不长,Kotlin 1.4.20-M2中,JetBrains废弃了KAE,转而建议我们使用 ViewBinding


0x3、ViewBinding基本用法

ViewBinding 的作用:代替findViewById,还可以保证空安全和类型安全,支持Java。

:使用ViewBinding,AGP版本需 >= 3.6

接着介绍下基本用法,部分内容搬运自官方文档:《视图绑定》

① 启用ViewBinding

需要启用视图绑定的 Module,在其 build.gradle 添加下述配置:

android {
    ...
    viewBinding {
        enabled = true
    }
} 

不需要生成绑定类的布局XML文件,可在根节点中添加下述属性:


    ...
 

编译后,AGP会为Module中包含的XML布局文件生成一个绑定类,类名规则:

XML文件名转换为Pascal大小写,并加上Binding,比如:result_profile.xml → ResultProfileBinding。

② 三个类绑定API

// View已存在
fun  bind(view : View) : T

// View未存在
fun  inflate(inflater : LayoutInflater) : T
fun  inflate(inflater : LayoutInflater, parent : ViewGroup?, attachToParent : Boolean) : T 

接下来演示一波各种场景下的ViewBinding用法,其实都是围绕上述三个API进行的~

③ Activity

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 1、实例化绑定实例
        binding = ActivityMainBinding.inflate(layoutInflater)
        // 2、获得对根视图的引用
        val view = binding.root
        // 3、让根视图称为屏幕上的活动视图
        setContentView(view)
        // 4、引用视图控件
        binding.tvContent.text = "修改TextView文本"
    }
} 

④ Fragment

class ContentFragment: Fragment() {
    private var _binding: FragmentContentBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentContentBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding.ivLogo.visibility = View.GONE
    }

    override fun onDestroyView() {
        super.onDestroyView()
        // Fragment的存活时间比View长,务必在此方法中清除对绑定类实例的所有引用
        // 否则会引发内存泄露
        _binding = null
    }
} 

【Jetpack】学穿:ViewBinding → 视图绑定_第6张图片

如果布局已inflated,还可以采用另一种写法(调bind):

class TestFragment: Fragment(R.layout.fragment_content) {
    private var _binding: FragmentContentBinding? = null

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val binding = FragmentContentBinding.bind(view)
        _binding = binding
        binding.ivLogo.visibility = View.VISIBLE
    }

    override fun onDestroyView() {
        super.onDestroyView()
        // 同样需要置空
        _binding = null
    }
} 

⑤ Dialog

如果是继承DialogFragment写法同Fragment,如果是继承Dialog写法示例如下(PopupWindow类似)~

class TestDialog(context: Context) : Dialog(context) {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DialogTestBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.tvTitle.text = "对话框标题"
    }
} 

⑥ RecyclerView

class TestAdapter(list: List) : RecyclerView.Adapter() {
    private var mList: List = list

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        // 需在此初始化以获得父类容器
        val binding = ItemTestBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.tvItem.text = "Adapter"
    }

    override fun getItemCount() = mList.size

    // 传递Binding对象
    class ViewHolder(binding: ItemTestBinding) : RecyclerView.ViewHolder(binding.root) {
        var tvItem: TextView = binding.tvItem
    }
} 

⑦ 自定义ViewGroup

ViewGroup子类才能使用视图绑定,View子类不可使用,示例如下:

class TestLayout: LinearLayout {
    constructor(context: Context): super(context)
    constructor(context: Context, attrs: AttributeSet): super(context, attrs) {
        val inflater = LayoutInflater.from(this.context)
        val binding = ItemLayoutBinding.inflate(inflater, this, true)
        binding.tvLayout.text = "自定义ViewGroup"
    }

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
    }
} 

⑧ include

根据include的布局xml是否带**标签**,分为两种,先是不带的情况: include的xml文件名为sub_include_test.xml,id为include_layout:

【Jetpack】学穿:ViewBinding → 视图绑定_第7张图片

然后是带的情况,布局文件改下:

【Jetpack】学穿:ViewBinding → 视图绑定_第8张图片

使用部分的代码不变,运行奔溃报错信息如下:

【Jetpack】学穿:ViewBinding → 视图绑定_第9张图片

原因是merge并不会加载到布局里,解法:把include标签的id去掉,然后bind传入父布局~

【Jetpack】学穿:ViewBinding → 视图绑定_第10张图片

⑨ ViewStub

【Jetpack】学穿:ViewBinding → 视图绑定_第11张图片

基础用法很简单,也很好上手,但存在下述问题:

需重复编写:创建和回收ViewBinding实例的样板代码,特别是Fragment,还要手动置空。

所以有必要封装优化一波~

0x4、封装优化思路

① 泛型 + 父类实现模板代码

最容易想到的常规写法,配合泛型,把模板代码都在父类中写好,非常简单:

abstract class BaseFragment(layoutId: Int) : Fragment(layoutId) {
    private var _binding: T? = null
    val binding get() = _binding!!

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        _binding = initBinding(view)
        init()
    }

    abstract fun initBinding(view: View): T

    abstract fun init()

    override fun onDestroyView() {
        _binding = null
        super.onDestroyView()
    }
}

// 子类实现
class TestFragment : BaseFragment(R.layout.fragment_content) {
    override fun initBinding(view: View) = FragmentContentBinding.bind(view)

    override fun init() {
        binding.ivLogo.visibility = View.VISIBLE
    }
    
} 

② Kotlin委托 + lifecycle组件

有些朋友可能觉得写在父类中侵入性太强,接着试下用其他方式进行封装,先看原始Activity:

【Jetpack】学穿:ViewBinding → 视图绑定_第12张图片

要把圈住的代码干掉,先是 泛型传递问题,泛型在进JVM前会被擦除,可在运行时通过反射获得,还可以通过实例化类类型代替类引用,如:

fun  FragmentActivity.startActivity(context: Context, clazz: Class) {
    startActivity(Intent(context, clazz))
}

// 调用处
startActivity(context, MainActivity::class.java) 

而在Kotlin中还可以用 inline 定义一个 内联函数 (编译时自动替换到调用位置),配合 reified 具体化(类型不擦除),得到泛型类型的Class,如:

inline fun  Activity.startActivity(context: Context) {
    startActivity(Intent(context, T::class.java))
}

// 调用
startActivity(context) 

可以,配合反射invoke()调用,随手写上一个扩展方法:

调用下:

【Jetpack】学穿:ViewBinding → 视图绑定_第13张图片

看似十拿九稳,结果一跑就崩:

不过也在意料之内,Activity还没onCreate()就初始化了,不空才怪,可以利用 标准委托-lazy 延迟初始化,修改后的代码:

调用下:

【Jetpack】学穿:ViewBinding → 视图绑定_第14张图片

运行通过,你还可以把还可以把setContentView()也塞到扩展中:

配合lifecycle组件,顺手把Fragment的也写出来:

【Jetpack】学穿:ViewBinding → 视图绑定_第15张图片

调用下:

【Jetpack】学穿:ViewBinding → 视图绑定_第16张图片

对了,如果还不想使用反射,可以利用Kotlin高阶函数,示例如下:

调用下:

啧啧啧,你还可以不用lazy,自己重写ReadOnlyProperty,这里只是试试水封装,并没用到生产上,更完善的封装方案可自行参考下述开源库:

  • Binding
  • VBHelper

0x5、原理

AGP会为模块中每个XML生成一个绑定类,该类的实例会直接引用布局中声明了资源id的View

① 自动生成的绑定类

打开:module模块名/build/generated/intermediates/javac/渠道/包名/databinding

可以看到 (基于AGP 7.1.1,不同AGP版本可能不一样):

【Jetpack】学穿:ViewBinding → 视图绑定_第17张图片

自动生成的class文件,随手打开一个:

【Jetpack】学穿:ViewBinding → 视图绑定_第18张图片

所以本质上还是findViewById,只是自动生成了控件实例,并一一对应,接着简单了解下大概的生成流程。

② 生成Java类

执行gradlew assembleDebug,在Task构建列表没找到ViewBinding,却找到了DataBinding:

【Jetpack】学穿:ViewBinding → 视图绑定_第19张图片

打开AGP源码,全局搜 dataBindingMergeGenClassesDataBindingMergeBaseClassLogTask.kt

【Jetpack】学穿:ViewBinding → 视图绑定_第20张图片

跟到:TaskManager.ktcreateDataBindingTasksIfNecessary

【Jetpack】学穿:ViewBinding → 视图绑定_第21张图片

2333,跟DataBinding混一起了,所以 ViewBinding其实只是DataBinding功能的一小部分~

看回:DataBindingMergeBaseClassLogTask,增量和全量执行动作:

【Jetpack】学穿:ViewBinding → 视图绑定_第22张图片

跟下:DataBindingMergeBaseClassLogDelegate

【Jetpack】学穿:ViewBinding → 视图绑定_第23张图片

跟下:DataBindingMergeBaseClassLogRunnable

【Jetpack】学穿:ViewBinding → 视图绑定_第24张图片

判断文件是否新建、修改、或移除,跟下是哪个文件:

全局搜下这个结尾的文件,在下述目录找到了它:

【Jetpack】学穿:ViewBinding → 视图绑定_第25张图片

【Jetpack】学穿:ViewBinding → 视图绑定_第26张图片

不难看出是:XML名称和ViewBinding类的映射,往下看 DataBindingMergeDependencyArtifactsTask,BR相关的,目前不知道是干嘛的。再往下走:DataBindingGenBaseClassesTask → CreationAction

【Jetpack】学穿:ViewBinding → 视图绑定_第27张图片

跟下:DataBindingGenBaseClassesTask → @TaskAction

【Jetpack】学穿:ViewBinding → 视图绑定_第28张图片

先看 buildInputArgs(),构建输入参数,同样对增量和全量编译进行了不同的处理,然后返回配置实例

【Jetpack】学穿:ViewBinding → 视图绑定_第29张图片

接着看 CodeGenerator 类,见名知意,代码生成器

【Jetpack】学穿:ViewBinding → 视图绑定_第30张图片

这里直接索引不到 BaseDataBinder,需要另外依赖:databinding-compiler-common

implementation 'androidx.databinding:databinding-compiler-common:7.1.0' 

async后就可以了,打开 MVNResponsitory - Gradle » 7.1.0 可以看到它依赖了47个运行时库~

【Jetpack】学穿:ViewBinding → 视图绑定_第31张图片

跟下:BaseDataBinder → generateAll()

【Jetpack】学穿:ViewBinding → 视图绑定_第32张图片

跟下:ViewBinderGenerateJava.kt → toJavaFile() → JavaFileGenerator

【Jetpack】学穿:ViewBinding → 视图绑定_第33张图片

Java文件就是从这里构造出来的,具体构造过程,感兴趣的可以自己翻阅下此文件。

另外,如果你想了解布局采集和写Layout部分的逻辑,可以参考 [《ViewBinding 的本质》],笔者卷不动了…

【Jetpack】学穿:ViewBinding → 视图绑定_第34张图片

0x6、一些补充

① 与DataBinding的区别

可以把ViewBinding看做DataBinding功能的子集,它有的功能DataBinding都有,不需要数据绑定单纯想替代findViewById 可以用ViewBinding。

② 不用build就能自动生成Java类

笔者猜测:AS起了一个进程 Filesystem events processor 用于监听文件变化,有文件变动时回调执行ViewBinding相关的Task。

③ KAE库过时,迁移Parcelable

Module 层次的 build.gradle 添加 kotlin-parcelize 插件。

以上就是本节的全部内容,有疑问或补充欢迎评论区指出,谢谢~


总结

要想成为架构师,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
在这里插入图片描述
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

一、架构师筑基必备技能

1、深入理解Java泛型
2、注解深入浅出
3、并发编程
4、数据传输与序列化
5、Java虚拟机原理
6、高效IO
……

在这里插入图片描述

二、Android百大框架源码解析

1.Retrofit 2.0源码解析
2.Okhttp3源码解析
3.ButterKnife源码解析
4.MPAndroidChart 源码解析
5.Glide源码解析
6.Leakcanary 源码解析
7.Universal-lmage-Loader源码解析
8.EventBus 3.0源码解析
9.zxing源码分析
10.Picasso源码解析
11.LottieAndroid使用详解及源码解析
12.Fresco 源码分析——图片加载流程

在这里插入图片描述

三、Android性能优化实战解析

  • 腾讯Bugly:对字符串匹配算法的一点理解
  • 爱奇艺:安卓APP崩溃捕获方案——xCrash
  • 字节跳动:深入理解Gradle框架之一:Plugin, Extension, buildSrc
  • 百度APP技术:Android H5首屏优化实践
  • 支付宝客户端架构解析:Android 客户端启动速度优化之「垃圾回收」
  • 携程:从智行 Android 项目看组件化架构实践
  • 网易新闻构建优化:如何让你的构建速度“势如闪电”?

在这里插入图片描述

四、高级kotlin强化实战

1、Kotlin入门教程
2、Kotlin 实战避坑指南
3、项目实战《Kotlin Jetpack 实战》

  • 从一个膜拜大神的 Demo 开始

  • Kotlin 写 Gradle 脚本是一种什么体验?

  • Kotlin 编程的三重境界

  • Kotlin 高阶函数

  • Kotlin 泛型

  • Kotlin 扩展

  • Kotlin 委托

  • 协程“不为人知”的调试技巧

  • 图解协程:suspend

在这里插入图片描述

五、Android高级UI开源框架进阶解密

1.SmartRefreshLayout的使用
2.Android之PullToRefresh控件源码解析
3.Android-PullToRefresh下拉刷新库基本用法
4.LoadSir-高效易用的加载反馈页管理框架
5.Android通用LoadingView加载框架详解
6.MPAndroidChart实现LineChart(折线图)
7.hellocharts-android使用指南
8.SmartTable使用指南
9.开源项目android-uitableview介绍
10.ExcelPanel 使用指南
11.Android开源项目SlidingMenu深切解析
12.MaterialDrawer使用指南
在这里插入图片描述

六、NDK模块开发

1、NDK 模块开发
2、JNI 模块
3、Native 开发工具
4、Linux 编程
5、底层图片处理
6、音视频开发
7、机器学习

在这里插入图片描述

七、Flutter技术进阶

1、Flutter跨平台开发概述
2、Windows中Flutter开发环境搭建
3、编写你的第一个Flutter APP
4、Flutter开发环境搭建和调试
5、Dart语法篇之基础语法(一)
6、Dart语法篇之集合的使用与源码解析(二)
7、Dart语法篇之集合操作符函数与源码分析(三)

在这里插入图片描述

八、微信小程序开发

1、小程序概述及入门
2、小程序UI开发
3、API操作
4、购物商场项目实战……

在这里插入图片描述

全套视频资料:

一、面试合集
在这里插入图片描述
二、源码解析合集

在这里插入图片描述
三、开源框架合集

在这里插入图片描述
欢迎大家一键三连支持,若需要文中资料,直接点击文末CSDN官方认证微信卡片免费领取【保证100%免费】↓↓↓

你可能感兴趣的:(Android开发,jetpack,android,kotlin,android,studio)