Android _ ViewBinding 与 Kotlin 委托双剑合璧

fun bind(view : View) : T

fun inflate(inflater : LayoutInflater) : T

fun inflate(inflater : LayoutInflater, parent : ViewGroup?, attachToParent : Boolean) : T

  • 1、在 Activity 中使用

MainActivity.kt

class TestActivity: AppCompatActivity(R.layout.activity_test) {

private lateinit var binding: ActivityTestBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = ActivityTestBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tvDisplay.text = “Hello World.”
}
}

  • 2、在 Fragment 中使用

TestFragment.kt

class TestFragment : Fragment(R.layout.fragment_test) {

private var _binding: FragmentTestBinding? = null
private val binding get() = _binding!!

override fun onViewCreated(root: View, savedInstanceState: Bundle?) {
_binding = FragmentTestBinding.bind(root)

binding.tvDisplay.text = “Hello World.”
}

override fun onDestroyView() {
super.onDestroyView()

_binding = null
}
}

3.3 避免内存泄露

这里有一个隐藏的内存泄露问题,你需要理解清楚(严格来说这并不是 ViewBinding 的问题,即使你采用其它视图绑定方案也要考虑这个问题)。

问题:为什么 Fragment#onDestroyView() 里需要置空绑定类对象,而 Activity 里不需要? 答:Activity 实例和 Activity 视图的生命周期是同步的,而 Fragment 实例和 Fragment 视图的生命周期并不是完全同步的,因此需要在 Fragment 视图销毁时,手动回收绑定类对象,否则造成内存泄露。例如:detach Fragment,或者 remove Fragment 并且事务进入返回栈,此时 Fragment 视图销毁但 Fragment 实例存在。关于 Fragment 生命周期和事务在我之前的一篇文章里讨论过:Android | Fragment 核心原理 & 面试题 (AndroidX 版本)

总之,在视图销毁但是控制类对象实例还存活的时机,你就需要手动回收绑定类对象,否则造成内存泄露。

3.4 ViewBinding 绑定类源码

反编译如下:

public final class ActivityTestBinding implements ViewBinding {
private final ConstraintLayout rootView;
public final TextView tvDisplay;

private ActivityTestBinding (ConstraintLayout paramConstraintLayout1, TextView paramTextView)
this.rootView = paramConstraintLayout1;
this.tvDisplay = paramTextView;
}

public static ActivityTestBinding bind(View paramView) {
TextView localTextView = (TextView)paramView.findViewById(2131165363);
if (localTextView != null) {
return new ActivityMainBinding((ConstraintLayout)paramView, localTextView);
}else {
paramView = “tvDisplay”;
}
throw new NullPointerException("Missing required view with ID: ".concat(paramView));
}

public static ActivityMainBinding inflate(LayoutInflater paramLayoutInflater) {
return inflate(paramLayoutInflater, null, false);
}

public static ActivityMainBinding inflate(LayoutInflater paramLayoutInflater, ViewGroup paramViewGroup, boolean paramBoolean) {
paramLayoutInflater = paramLayoutInflater.inflate(2131361821, paramViewGroup, false);
if (paramBoolean) {
paramViewGroup.addView(paramLayoutInflater);
}
return bind(paramLayoutInflater);
}

public ConstraintLayout getRoot() {
return this.rootView;
}
}


4. ViewBinding 与 Kotlin 委托双剑合璧

到这里,ViewBinding 的使用教程已经说完了。但是回过头看,有没有发现一些局限性呢?

  • 1、创建和回收 ViewBinding 对象需要重复编写样板代码,特别是在 Fragment 中使用的案例;
  • 2、binding 属性是可空的,也是可变的,使用起来不方便。

那么,有没有可优化的方案呢?我们想起了 Kotlin 属性委托,关于 Kotlin 委托机制在我之前的一篇文章里讨论过:Kotlin | 委托机制 & 原理。如果你还不太了解 Kotlin 委托,下面的内容对你会有些难度。

下面,我将带你一步步封装 ViewBinding 属性委托工具:

4.1 ViewBinding + Kotlin 委托 1.0

首先,我们梳理一下我们要委托的内容与需求,以及相应的解决办法:

需求解决办法
需要委托 ViewBinding#bind() 的调用反射
需要委托 binding = null 的调用监听 Fragment 视图生命周期
期望 binding 属性声明为非空不可变变量ReadOnlyProperty<F, V>

FragmentViewBindingPropertyV1.kt

private const val TAG = “ViewBindingProperty”

public inline fun viewBindingV1() = viewBindingV1(V::class.java)

public inline fun viewBindingV1(clazz: Class): FragmentViewBindingPropertyV1<Fragment, T> {
val bindMethod = clazz.getMethod(“bind”, View::class.java)
return FragmentViewBindingPropertyV1 {
bindMethod(null, it.requireView()) as T
}
}

/**

  • @param viewBinder 创建绑定类对象
    */
    class FragmentViewBindingPropertyV1<in F : Fragment, out V : ViewBinding>(
    private val viewBinder: (F) -> V
    ) : ReadOnlyProperty<F, V> {

private var viewBinding: V? = null

@MainThread
override fun getValue(thisRef: F, property: KProperty<*>): V {
// 已经绑定,直接返回
viewBinding?.let { return it }

// Use viewLifecycleOwner.lifecycle other than lifecycle
val lifecycle = thisRef.viewLifecycleOwner.lifecycle
val viewBinding = viewBinder(thisRef)
if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
Log.w(
TAG, "Access to viewBinding after Lifecycle is destroyed or hasn’t created yet. " +
“The instance of viewBinding will be not cached.”
)
// We can access to ViewBinding after Fragment.onDestroyView(), but don’t save it to prevent memory leak
} else {
lifecycle.addObserver(ClearOnDestroyLifecycleObserver())
this.viewBinding = viewBinding
}
return viewBinding
}

@MainThread
fun clear() {
viewBinding = null
}

private inner class ClearOnDestroyLifecycleObserver : LifecycleObserver {

private val mainHandler = Handler(Looper.getMainLooper())

@MainThread
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy(owner: LifecycleOwner) {
owner.lifecycle.removeObserver(this)
mainHandler.post { clear() }
}
}
}

使用方法:

class TestFragment : Fragment(R.layout.fragment_test) {

private val binding : FragmentTestBinding by viewBindingV1()

override fun onViewCreated(root: View, savedInstanceState: Bundle?) {
binding.tvDisplay.text = “Hello World.”
}
}

干净清爽!前面提出的三个需求也都实现了,现在我为你解答细节:

  • 问题 1、为什么可以使用 V::class.java,不是泛型擦除了吗? 利用了 Kotlin 内敛函数 + 实化类型参数,编译后函数体整体被复制到调用处,V::class.java 其实是 FragmentTestBinding::class.java。具体分析见:Java | 关于泛型能问的都在这里了(含Kotlin)

  • 问题 2、ReadOnlyProperty<F, V> 是什么? ReadOnlyProperty 是不可变属性代理,通过 getValue(…) 方法实现委托行为。第一个类型参数 F 是属性所有者,第二个参数 V 是属性类型,因为我们在 Fragment 中定义属性,属性类型为 ViewBinding,所谓定义类型参数为 <in F : Fragment, out V : ViewBinding>;

  • 问题 3、解释下 getValue(…) 方法? 直接看注释:

@MainThread
override fun getValue(thisRef: F, property: KProperty<*>): V {
1、viewBinding 不为空说明已经绑定,直接返回
viewBinding?.let { return it }

2、Fragment 视图的生命周期
val lifecycle = thisRef.viewLifecycleOwner.lifecycle

3、实例化绑定类对象
val viewBinding = viewBinder(thisRef)

if (lifecycle.currentState == Lifecycle.State.DESTROYED) {
4.1 如果视图生命周期为 DESTROYED,说明视图被销毁,此时不缓存绑定类对象(避免内存泄漏)
} else {
4.2 定义视图生命周期监听者
lifecycle.addObserver(ClearOnDestroyLifecycleObserver())
4.3 缓存绑定类对象
this.viewBinding = viewBinding
}
return viewBinding
}

  • 为什么 onDestroy() 要采用 Handler#post(Message) 完成? 因为 Fragment#viewLifecycleOwner 通知生命周期事件 ON_DESTROY 的时机在 Fragment#onDestryoView 之前。

4.2 ViewBinding + Kotlin 委托 2.0

1.0 版本使用了反射,真的一定要反射吗?反射调用 bind 函数的目的就是获得一个 ViewBinding 绑定类对象,或许我们可以试试把创建对象的行为交给外部去定义,类似这样:

inline fun <F : Fragment, V : ViewBinding> viewBindingV2(
crossinline viewBinder: (View) -> V,
crossinline viewProvider: (F) -> View = Fragment::requireView
) = FragmentViewBindingPropertyV2 { fragment: F ->
viewBinder(viewProvider(fragment))
}

class FragmentViewBindingPropertyV2<in F : Fragment, out V : ViewBinding>(
private val viewBinder: (F) -> V
) : ReadOnlyProperty<F, V> {
相同 …
}

使用方法:

class TestFragment : Fragment(R.layout.fragment_test) {

private val binding by viewBindingV2(FragmentTestBinding::bind)

override fun onViewCreated(root: View, savedInstanceState: Bundle?) {
binding.tvDisplay.text = “Hello World.”
}
}

干净清爽!不使用反射也可以实现,现在我为你解答细节:

  • 问题 4、(View) -> V 是什么? Kotlin 高阶函数,可以把 lambda 表达式直接作为参数传递,其中 View 是函数参数,而 T 是函数返回值。lambda 表达式本质上是 「可以作为值传递的代码块」。在老版本 Java 中,传递代码块需要使用匿名内部类实现,而使用 甚至连函数声明都不需要,可以直接传递代码块作为函数值;

  • 问题 5、Fragment::requireView 是什么? 把函数 requireView() 作为参数传递。Fragment#requireView() 返回 Fragment 根视图,在 onCreateView() 之前调用 requireView() 会抛出异常;

  • 问题 6、FragmentTestBinding::bind 是什么? 把函数 bind() 作为参数传递,bind 函数的参数为 View,返回值为 ViewBinding,与函数声明 (View) -> V 匹配。

4.3 ViewBinding + Kotlin 委托 最终版

2.0 版本已经完成了针对 Fragment 的属性代理,但是实际场景中只会在 Fragment 中使用 ViewBinding 吗?显然并不是,我们还有其他一些场景:

  • Activity
  • Fragment
  • DialogFragment
  • ViewGroup
  • RecyclerView.ViewHolder

所以,我们有必要将委托工具适当封装得更通用些,完整代码和演示工程你可以直接下载查看:下载路径,这里只展示部分核心代码如下:

@JvmName(“viewBindingActivity”)
inline fun <A : ComponentActivity, V : ViewBinding> viewBinding(
crossinline viewBinder: (View) -> V,
crossinline viewProvider: (A) -> View = ::findRootView
): ViewBindingProperty<A, V> = ActivityViewBindingProperty { activity: A ->
viewBinder(viewProvider(activity))
}

@JvmName(“viewBindingActivity”)
inline fun <A : ComponentActivity, V : ViewBinding> viewBinding(
crossinline viewBinder: (View) -> V,
@IdRes viewBindingRootId: Int
): ViewBindingProperty<A, V> = ActivityViewBindingProperty { activity: A ->
viewBinder(activity.requireViewByIdCompat(viewBindingRootId))
}

// -------------------------------------------------------------------------------------
// ViewBindingProperty for Fragment
// -------------------------------------------------------------------------------------

@Suppress(“UNCHECKED_CAST”)
@JvmName(“viewBindingFragment”)
inline fun <F : Fragment, V : ViewBinding> Fragment.viewBinding(
crossinline viewBinder: (View) -> V,
crossinline viewProvider: (F) -> View = Fragment::requireView
): ViewBindingProperty<F, V> = when (this) {
is DialogFragment -> DialogFragmentViewBindingProperty { fragment: F ->
viewBinder(viewProvider(fragment))
} as ViewBindingProperty<F, V>
else -> FragmentViewBindingProperty { fragment: F ->
viewBinder(viewProvider(fragment))
}
}

@Suppress(“UNCHECKED_CAST”)
@JvmName(“viewBindingFragment”)
inline fun <F : Fragment, V : ViewBinding> Fragment.viewBinding(
crossinline viewBinder: (View) -> V,
@IdRes viewBindingRootId: Int
): ViewBindingProperty<F, V> = when (this) {
is DialogFragment -> viewBinding(viewBinder) { fragment: DialogFragment ->
fragment.getRootView(viewBindingRootId)
} as ViewBindingProperty<F, V>
else -> viewBinding(viewBinder) { fragment: F ->
fragment.requireView().requireViewByIdCompat(viewBindingRootId)
}
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

分享一份工作1到5年以上的Android程序员架构进阶学习路线体系,希望能对那些还在从事Android开发却还不知道如何去提升自己的,还处于迷茫的朋友!

  • 阿里P7级Android架构师技术脑图;查漏补缺,体系化深入学习提升

  • **全套体系化高级架构视频;**七大主流技术模块,视频+源码+笔记

有任何问题,欢迎广大网友一起来交流

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!

最后

分享一份工作1到5年以上的Android程序员架构进阶学习路线体系,希望能对那些还在从事Android开发却还不知道如何去提升自己的,还处于迷茫的朋友!

  • 阿里P7级Android架构师技术脑图;查漏补缺,体系化深入学习提升

    [外链图片转存中…(img-2h6cxS64-1711765158557)]

  • **全套体系化高级架构视频;**七大主流技术模块,视频+源码+笔记

[外链图片转存中…(img-qrXWmQSe-1711765158557)]

有任何问题,欢迎广大网友一起来交流

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门即可获取!
  • 13
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值