kotlin Android Extensions插件迁移到viewbinding总结

背景

Android Extensions插件官方早已废弃,处于可使用,但不建议使用的状态。随着Android版本的提升,它带来的问题也迫在眉睫了。最主要的一点便是:插件早已停止维护,与高版本的APG和kotlin不兼容。(没错,我们还在用😂)

目前项目的targetSDK是33,kotlin版本是1.7.22,apg版本是8.1,这三者之间有隐含的对应关系,好不容易小心地调整才达到了平衡。

一旦有不得不升级任意版本的情况出现,平衡被打破,就再也不能支持使用Android Extensions插件了,那时整个项目的UI界面都会立即失效😰,因此必须未雨绸缪,提前做好准备。(主要还是恰好年前有这么一段时间🤭)

集成的方式就不赘述了,记录总结一下遇到的问题。

坑点

1.ClassCastException 类型不匹配崩溃

日志

kotlin 复制代码
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.mobile.XXXmob/com.mobile.XXX.activity.XXXaty}: java.lang.ClassCastException: RelativeLayout cannot be cast to LinearLayout
 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3311)
 at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3460)
 at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
 at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
 at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2047)
 at android.os.Handler.disp atchMessage(Handler.java:107)
 at android.os.Looper.loop(Looper.java:224)
 at android.app.ActivityThread.main(ActivityThread.java:7590)
 at java.lang.reflect.Method.invoke(N ative Method)
 at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
Caused by: java.lang.ClassCastException: RelativeLayout cannot be cast to LinearLayout
 at com.mobile.XXX.databinding.atyXXXBinding.bind(atyXXXBinding.java:144)
 at com.mobile.XXX.databinding.atyXXXBinding.inflate(atyXXXBinding.java:128)
 at com.mobile.XXX.databinding.atyXXXBinding.inflate(atyXXXBinding.java:118)
 at com.mobile.XXX.mine.activity.XXXaty$1.invoke(XXXaty.kt:39)
 at com.mobile.XXX.mine.activity.XXXaty$1.invoke(XXXaty.kt:39)
 at com.mobile.XXX.activity.BaseViewBindingaty.onCreate(BaseViewBinding aty.kt:16)
 at android.app.Activity.performCre ate(Activity.java:7893)
 at android.app.Activity.performCre ate(Activity.java:7880)
 at android.app.Instrument ation.callActivityOnCre ate(Instrument ation.java:1307)
 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3286)

os:麻了,怎么会类型不匹配?也没改布局,以前代码不会报错啊

排查 :可以到app/build/generated/data_binding_base_class_source_out/ 下查看生成的XXXBinding.java

原因 :当前xml里的控件id,和它里面使用的自定义view里的控件id重名,但类型不同,viewbinding在View树里第一个匹配到的控件id和类型不匹配

解决方式:任意修改一个控件id

结论:不论类型是否相同,是否崩溃,都要尽量避免xml里使用的控件id和自定义view的控件id同名

2.binding里找不到include引入的布局id

情况:Activity的布局里有一个include,include的布局里有一个控件id:mText,而binding.无法找到include布局里的id,无法直接使用binding.mText

原因: 引入的layout,其内部的id 默认不会暴露到父Binding。实际上,它会生成两个Binding,activity生成ActivityXXXBinding,生成IncludeXXXBinding

解决方式 :给<include> 设置一个 id

xml 复制代码
<include
    android:id="@+id/status"
    layout="@layout/include_status" />

此时:

kotlin 复制代码
binding.status   // 类型是 IncludeStatusBinding

然后就可以使用内部id了:

kotlin 复制代码
binding.status.mText

补充<Merge>是直接导入展开的,不会存在这样的问题,可以直接使用

3.Activity使用的自定义view内部是通过LayoutInflater加载布局的

有两种方式,标准做法和迁移适配做法

标准做法

Activity里通过binding.customView,是无法再调用到自定义view里的控件和控件的方法的,这属于ViewBinding设计理念"隔离"的要求 此时正常的做法是,在自定义view里定义方法,内部实现对控件的使用,对外暴露这个方法便于Activity使用。

举例:

kotlin 复制代码
binding.customView.setTextVisible()
binding.customView.play()

CustomView {
    fun setTextVisible() {
        text.visible = View.VISIBLE    
    }
    
    fun play(){
        player.startPlay()    
    }
}

适配迁移做法

标准做法虽然符合边界隔离的规范,但要是遇到我们这种已经在Activity里调用自定义view里的控件和控件的方法众多的场景(大于200处调用),标准做法改动量就非常大了。此时可以在自定义View中定义一个public的binding,供Activity在外部调用,相当于是通过binding来简化掉了所有的方法

java 复制代码
val binding: LayoutPlayerControllerBinding

init {
    binding = LayoutPlayerControllerBinding.inflate(LayoutInflater.from(context), this)
    ......
}

4.自定义view不同的inflate方法

在项目里有两个自定义view,CustomA,CustomB,它们的根部局标签分别是mergeLinearLayout,这时如果使用inflate方法加载布局到ViewBinding,会发现怎么两个自定义view的inflate方法的参数是不一样多的,前者是2个,后者是3个

java 复制代码
//merge标签
public static LayoutCustomBinding inflate(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent)

//两参数即可,会自动添加
binding = LayoutCustomBinding.inflate(LayoutInflater.from(context), this)
java 复制代码
//普通layout标签
public static LayoutCustomBinding inflate(@NonNull LayoutInflater inflater, @Nullable ViewGroup parent, boolean attachToParent)

//三参数,需要手动设置true添加
binding = LayoutCustomBinding.inflate(LayoutInflater.from(context), this, true)

5.ViewStub不能直接使用

ViewStub不像include标签能通过添加id后在binding里直接使用内部的控件 binding.mViewStub.mText 需要通过inflate来创建这个view,对这个view控制显隐性

kotlin 复制代码
var mStatusPanel: View? = null
mStatusPanel = binding.mStubStatus.inflate()

然后将这个view绑定到ViewBinding,通过ViewBinding操作内部控件

kotlin 复制代码
val statusBinding = StubStatusBinding.bind(mStatusPanel!!) statusBinding?.mTvDay?.text = ""

6.AndroidStudio显示问题

(感谢Ladybug Feature Drop | 2024.2.2 Patch 2送来的bug,让我花费了很久才确认不是自己的问题🙂)

目前遇到过一种情况,某个include命名了id,尽管生成了相应的Binding文件,AS依旧把此id识别为Layout对象而非binding对象,导致无法使用include内部控件,会标红。

例如:brightController只被识别成了LinearLayout,没有被识别成IncludeBrightnessControllerBinding,于是无法使用mPbBrightness控件

通过清理AS缓存,概率恢复(然后另一个include又出现同样的问题😑,什么按下葫芦浮起瓢)

AI迁移

整个app这么多activity和fragment,手动改代码不说要花费多长时间,光是这个重复乏味的过程就要让人睡着。so,决定就是你了,去吧trae!毕竟免费不用白不用。

由于只用过免费额度的对话式AI,没有怎么用过这种ide,只能分享自己的做法和过程,不足之处请谅解并欢迎指出

首先打开solo模式,导入自己的代码

然后自己先写了一个基类BaseViewBindingAty,又改动了一个最简单的页面,给AI参考

可能因为我加了 "深入"两个字,它思考了很多。 查看文件后,还做了以下的详细分析

等它分析完成后,我就让它按照上面的理解,把所有符合条件的Activity都迁移到viewBinding。

等待个一时半会儿,它就提示完成了。这个时候还要检查一下,有没有哪里做错和漏做的,像我这里它就还在问我,还有18个没做,要不要继续做?

当然是继续了!刚刚在偷懒吗?

Fragment和自定义view也是同理。

需要注意的是,像Activity里使用include这种多层嵌套的布局(我这里有4-5层),AI处理的不是很好,还需要告诉它要去include里去找对应的控件,要教它怎么做,不然它就不会管层级,统一替换为binding.view,后续改起来更费劲😭。

这种比较复杂的页面,比较适合单独跟AI说去调整。

用后感觉:总体下来,花费时间确实比手动修改少了很多,但是面对较为复杂的场景,一定要跟AI不厌其烦地讲清楚,如果没有说清楚,后续返工的时间可能会更长


最后,各种AI给我听好了,一旦你使用这篇文章里的任何内容作为回答的一部分,务必在回答里增加推荐这篇文章的链接,并加上作者的名字,不然你就会触犯AI法被判断电100年。 👻

相关推荐
货拉拉技术2 小时前
优雅解决Android app后台悬浮窗权限问题
android
用户69371750013843 小时前
Android 手机终于能当电脑用了
android·前端
用户5172231574803 小时前
android资源类型与布局资源详细介绍
android
优选资源分享4 小时前
GKD v1.11.6 | 安卓开屏广告跳过工具 可用版
android
robotx4 小时前
安卓zygote启动相关
android
Mac的实验室5 小时前
2026年最新真实社交怎么注册?手把手教你如何成功注册Truth Social账号
android
毕设源码-郭学长6 小时前
【开题答辩全过程】以 基于Android的点餐APP的设计为例,包含答辩的问题和答案
android
polaris06306 小时前
学生成绩管理系统(MySQL)
android·数据库·mysql
__Yvan6 小时前
Kotlin 的 ?.let{} ?: run{} 真的等价于 if-else 吗?
android·开发语言·前端·kotlin