如何让Data Binding与RRO/SRRO协同工作?
RRO的核心是运行时替换资源内容而非资源ID,且无法修改代码,所以和Data Binding的配合要围绕「利用系统资源加载优先级」和「触发Data Binding动态刷新」两个核心点展开,以下是具体实现方案:
1. 基础方案:直接在Data Binding布局中引用标准资源ID
这是最直接的方式,因为RRO的资源替换完全依赖系统资源加载机制——当RRO包安装后,系统会优先加载RRO中与主APK同名ID的资源。
在Data Binding布局中像普通布局一样引用资源即可,无需额外修改:
<layout> <data> <!-- 无需额外定义资源相关变量,直接引用系统资源ID --> </data> <TextView android:text="@string/app_title" android:background="@drawable/background_primary" /> </layout>
只要RRO包中包含与主APK完全相同ID的app_title字符串、background_primary drawable,运行时Data Binding会自动加载RRO替换后的资源。
关键注意事项:主APK需通过res/values/public.xml固定需要被RRO替换的资源ID,确保RRO包的资源ID与主APK完全一致,否则系统无法完成替换。例如:
<public type="string" name="app_title" id="0x7f0a0001" /> <public type="drawable" name="background_primary" id="0x7f0d0002" />
2. 动态绑定:将资源ID绑定到可观察对象
如果需要根据业务逻辑动态切换资源(同时兼容RRO替换),可将资源ID存入Data Binding的可观察对象,通过自定义BindingAdapter加载资源:
步骤1:定义ViewModel中的可观察资源ID
class MainViewModel : ViewModel() { // 可观察的标题资源ID,初始为主APK默认资源 val titleResId = ObservableField(R.string.app_title) }
步骤2:自定义BindingAdapter加载资源
@BindingAdapter("app:dynamicText") fun TextView.setDynamicText(resId: Int) { // 从当前Context的Resources中加载资源,自动兼容RRO替换 text = context.getString(resId) }
步骤3:在Data Binding布局中绑定
<layout> <data> <variable name="viewModel" type="com.example.MainViewModel" /> </data> <TextView app:dynamicText="@{viewModel.titleResId}" /> </layout>
当RRO替换资源后,只要资源ID不变,调用binding.executePendingBindings()即可让View加载新的资源内容。
3. 监听RRO切换,触发Data Binding刷新
RRO的切换(如通过系统设置或命令行安装/卸载RRO包)属于系统级资源变化,主APK需要监听资源变化事件,主动通知Data Binding刷新:
在Activity/Fragment中注册资源变化监听:
class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityMainBinding private var resourcesListener: Resources.OnResourcesChangedListener? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) // 注册资源变化监听 resourcesListener = Resources.OnResourcesChangedListener { // 触发Data Binding重新绑定,加载RRO新资源 binding.executePendingBindings() } resources.registerResourcesChangedListener(resourcesListener!!, packageName) } override fun onDestroy() { super.onDestroy() // 注销监听,避免内存泄漏 resourcesListener?.let { resources.unregisterResourcesChangedListener(it) } } }
该监听会在系统资源(包括RRO替换)发生改变时触发,确保Data Binding能及时加载最新的资源内容。
核心限制与注意事项
- RRO仅能替换资源内容或资源文件,无法修改Data Binding的表达式逻辑、ViewModel代码或BindingAdapter逻辑,所有业务逻辑必须在主APK中实现。
- 必须保证RRO包的资源ID与主APK完全一致,否则系统不会执行替换逻辑。
- 对于已缓存的资源(如Bitmap),可能需要手动调用
View.invalidate()或Drawable.invalidate()强制刷新。
内容的提问来源于stack exchange,提问作者Brown

