Android: 在 attachBaseContext 中使用 DataStore

Android: use DataStore in attachBaseContext

当我在我的应用程序中使用Datastore制作可以动态切换语言的功能时,我发现了一个错误:

java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.pm.ApplicationInfo android.content.Context.getApplicationInfo()' on a null object reference

我的代码很简单:

open class BaseActivity : AppCompatActivity() {

 override fun attachBaseContext(newBase: Context) {
    lifecycleScope.launch(Dispatchers.Main) {
        var context = newBase
        val language = withContext(Dispatchers.IO) {
            DataStore.read(context, DataStore.KEY_LANG)
        }
        context = Language.changeLanguage(context, language ?: Language.ZH)
        super.attachBaseContext(context)
    }
}

}

Language.changeLanguage 将 return 更新本地的上下文:

    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
    fun changeLanguage(context: Context, lang: String): Context {
    val configuration = context.resources.configuration
    configuration.setLocale(getLocal(lang))

    return context.createConfigurationContext(configuration)

}

我调试了我的应用程序,发现代码无法到达 super.attachBaseContext(context) 并完成。 我不知道原因,但我猜是因为协程。

问题是 Application 是一个 ContextWrapper 并且在 attachBaseContext returns 之后假定基础上下文已经正确设置。

由于您将调用 super.attachBaseContext 推迟到协同程序的末尾,因此在协同程序有机会 运行 之前不会调用此方法(因为发生在 Dispatchers.Main 这可能会在正常初始化完成之后,因为这是同步发生的),打破了方法的契约。

实际上你有三个选择:

  1. 接受您必须阻塞才能同步获取更新的上下文。虽然不理想,但无需更改其他代码即可工作。
  2. 创建和管理您自己的用于在下游创建资源的上下文。可能是最不方便的,因为很多库都使用 context.getApplicationContext().
  3. 使用其他人创建的库来解决问题,因为它可能不是唯一的。 Google 迅速将我指向 YarikSOffice / lingver