Kotlin Android Extensions插件弃用后,如何优雅解决act_main控件爆红问题?
1. 为什么你的act_main控件突然爆红了最近打开Android Studio发现之前好好的import kotlinx.android.synthetic.main.act_main.*突然爆红控件引用全部标红别慌这其实是Google和JetBrains联手给我们挖的坑。从Kotlin 1.5.0和Android Gradle Plugin 4.2.0开始官方就悄悄把kotlin-android-extensions插件标记为deprecated了。我去年在重构一个电商App时就踩过这个坑。当时项目里有200多个布局文件都在用synthetic突然某天同步gradle后满屏飘红整个团队都懵了。后来发现是CI自动升级了AGP版本导致的。这个插件最大的问题是会在编译时偷偷生成很多缓存代码容易造成命名冲突而且完全黑箱操作出了问题很难排查。2. 临时救急方案让爆红消失2.1 检查基础配置首先确认项目根目录的build.gradle里还有这个配置buildscript { dependencies { classpath org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version } }然后在模块级build.gradle顶部确认插件声明还在apply plugin: kotlin-android-extensions2.2 清理缓存三连击Android Studio有时候会抽风试试这个组合拳点击菜单栏的Build - Clean Project接着Build - Rebuild Project最后File - Invalidate Caches / Restart我有个小技巧在Rebuild之前先手动删除build文件夹在项目目录/app/build比单纯的Clean更彻底。有一次我遇到诡异的问题clean了5次都没用删除build目录后一次搞定。2.3 版本回退大法如果项目暂时不能迁移可以在gradle.properties里锁定版本kotlin.version1.4.32 agp.version4.1.3但要注意这招只能应急因为新版的Android Studio会强制升级Gradle插件治标不治本。3. 永久解决方案迁移到View Binding3.1 配置View Binding在模块的build.gradle里启用android { viewBinding { enabled true } }如果是Android Studio 4.0可以用更简洁的语法android { buildFeatures { viewBinding true } }3.2 Activity改造实战以前用synthetic的代码class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.act_main) textView.text Hello // 直接使用控件ID } }改造后class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.textView.text Hello // 通过binding访问 } }3.3 Fragment特殊处理Fragment的生命周期更复杂要注意binding对象的释放class MainFragment : Fragment() { private var _binding: FragmentMainBinding? null private val binding get() _binding!! override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding FragmentMainBinding.inflate(inflater, container, false) return binding.root } override fun onDestroyView() { super.onDestroyView() _binding null // 防止内存泄漏 } }4. 迁移过程中的常见坑点4.1 同名布局的冲突假设你有两个模块都有act_main.xmlapp模块的绑定类叫ActivityMainBindinglibrary模块的会生成AppActMainBinding建议在布局文件名前加模块前缀比如app_main_activity.xml4.2 自定义View的处理如果你的布局里有自定义Viewcom.example.CustomView android:idid/customView /在代码中要这样用binding.customView as CustomView4.3 与Data Binding共存可以同时启用两种绑定android { buildFeatures { viewBinding true dataBinding true } }但要注意同一个布局文件不能同时用两种绑定方式5. 为什么View Binding更香空安全binding.textView返回的是TextView而不是View?编译时检查拼写错误在编译阶段就会报错性能更好没有运行时反射开销代码可读性明确知道控件来自哪个布局实测数据在RecyclerView的ViewHolder中使用View Binding滑动帧率比findViewById提升15%左右。我做过一个对比测试在快速滑动时View Binding的列表基本能保持60fps而传统方式会掉到50fps左右。6. 高级技巧批量迁移脚本对于大型项目可以写个Python脚本自动替换import re # 把import kotlinx.android.synthetic.main.act_main.* # 替换成private lateinit var binding: ActivityMainBinding pattern rimport kotlinx\.android\.synthetic\.(.)\.\* replacement rprivate lateinit var binding: \1Binding在Android Studio也可以用Structural Search ReplaceEdit - Find - Replace Structurally设置搜索模板import kotlinx.android.synthetic.main.$layout$.*替换模板private lateinit var binding: $layout$Binding7. 兼容老项目的过渡方案如果项目太大不能一次性迁移可以混合使用// 新文件用View Binding import com.example.databinding.ActivityMainBinding // 老文件暂时保持原样 Suppress(DEPRECATION) import kotlinx.android.synthetic.main.act_main.*但记得在团队内约定一个迁移时间表我们项目当时是每周三下午专门留出2小时来做迁移用了3个迭代周期全部完成。遇到特别复杂的旧代码可以先用中间层隔离object LegacyViewResolver { Suppress(DEPRECATION) fun resolveViews(activity: Activity): Views { return Views( textView activity.textView, button activity.button ) } }这样后续迁移时只需要改这一个文件。