背景
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,它们的根部局标签分别是merge和LinearLayout,这时如果使用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年。 👻
