Android多语言切换实现进化版--Kotlin实现
前言
前面发了一篇“Android多语言切换实现——Java实现”,但是那个方案有缺陷,于是今天想写个更完美的方案。
原理
原理是一样的,这里不多说,看前面那篇文章的原理部分
新的尝试
我又去搜了一波,看了下Activity的生命周期、Application方法的执行顺序等等…
主要思路
- App本地保存所设置的语言(通过数据库 or SharePreferences);
- 每个页面及App类的生命周期中判断当时语言是否是所设置语言,如果不是,则更新
Configuration信息;- 在
Application的attachBaseContext设置当前设置的语言Locale - 在
Application的onConfigurationChanged(改变系统语言以及横竖屏切换时会调用到)设置当前的语言Locale - 在
Activity的attachBaseContext设置当前设置的语言Locale,所以一般这里是创建BaseActivity来方便统一改变 - 在
Service的attachBaseContext设置当前设置的语言Locale(如果有 Service 的话) 在经过测试的确是跟着Activity改变的Fragment的….暂时没找到,Fragment不知道是不是跟随父Activity改变语言的
- 在
实现
App本地保存所设置的语言
本例使用SharePreferences保存设置的语言,所以这里先创建一个SharePreferences的工具类(SharePrefUtils.kt):
|
|
这里使用建造者模式写这个工具类,使用方法:
-
先创建实例
1 2 3 4 5 6private var sharePrefUtils: SharePrefUtils? = null sharePrefUtils = SharePrefUtils .Builder() .setContext(context) // 传上下文Context .setPref("SharePrefName") // SharePref的名称 .create() -
根据Key获取Value
1var value = sharePrefUtils!!.getInt("Key") -
把数据存到SharePref中
1 2 3sharePrefUtils!! .dataPrepare("Key", value) .putData()
创建管理语言的工具类(LocaleManagementUtil.kt)
-
首先,获取用户设置过的语言
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42// 静态属性,修复识别系统语言为英语的问题 private var systemCurrentLocal = Locale.ENGLISH // 默认英语 fun getSelectLanguage(): Int { return sharePrefUtils!!.getInt(Constant.TAG_LANGUAGE) } /** * 获取选择的语言设置 * * @param context * @return Locale对象 */ fun getSetLanguageLocale(context: Context): Locale { return when (getSelectLanguage()) { 0 -> getSystemLocale(context) // 获取系统设置的语言 1 -> Locale.CHINA 2 -> Locale.ENGLISH else -> Locale.ENGLISH } } /** * 获取系统的locale * * @return Locale对象 */ fun getSystemLocale(context: Context): Locale { return systemCurrentLocal } /** * 获取系统的locale并保存到静态变量systemCurrentLocal中 */ fun saveSystemCurrentLanguage(context: Context) { val locale: Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { LocaleList.getDefault().get(0) } else { Locale.getDefault() } systemCurrentLocal = locale } -
改变语言
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19fun setLocal(context: Context): Context { return updateResources(context, getSetLanguageLocale(context)) } private fun updateResources(context: Context, locale: Locale): Context { var context = context Locale.setDefault(locale) val res = context.resources val config = Configuration(res.configuration) if (Build.VERSION.SDK_INT >= 19) { config.setLocale(locale) context = context.createConfigurationContext(config) } else { config.locale = locale res.updateConfiguration(config, res.displayMetrics) } return context }
调用方法设置语言
-
Application
1 2 3 4 5 6 7 8 9 10 11 12 13override fun attachBaseContext(base: Context) { LocaleManageUtil.saveSystemCurrentLanguage(base) super.attachBaseContext(base) } override fun onCreate() { super.onCreate() instance = this LocaleManageUtil.setSharePref(this) } companion object { lateinit var instance: App private set } -
BaseActivity
1 2 3override fun attachBaseContext(newBase: Context) { super.attachBaseContext(LocaleManageUtil.setLocal(newBase)) } -
Service
1 2 3override fun attachBaseContext(newBase: Context) { super.attachBaseContext(LocaleManageUtil.setLocal(newBase)) } -
设置语言并重启Activity到启动页
-
LocaleManageUtil.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18fun saveLanguage(select: Int) { sharePrefUtils!! .dataPrepare(Constant.TAG_LANGUAGE, select) .putData() } fun saveSelectLanguage(context: Context, select: Int) { saveLanguage(select) } /** * 跳转主页 * * @param context */ fun toRestartLauncherActivity(context: Context) { val intent = Intent(context, MainActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK context.startActivity(intent) } -
SettingActivity.kt
1 2 3 4private fun selectLanguage(select: Int) { LocaleManageUtil.saveSelectLanguage(this, select) LocaleManageUtil.toRestartLauncherActivity(this) }
-
这里稍微解释一下:在Application和Activity中,attachBaseContext都是在onCreate方法之前执行的,所以我们先在Application中的attachBaseContext获取当前系统的语言并存下来(必须在所有获取语言的方法执行前存下当前系统语言,否则在后面获取可能会出现获取到的不是系统语言而是设置的语言的情况)。
到这里理论上语言就可以成功改变啦~
遇到的坑
context为applicationContext时切换语言后设置的string没有改变的问题
我们都会在代码中调用context.getResource().getString()这句代码看起来没什么问题,但是你这个context要是用的是applicationContext那么问题就来了。你会发现当你切换语言后用这样方式设置的string没有改变,所以我们需要改动我们的代码。
解决方法就是,在切换语言后把application的updateConfiguration也要更新了,方法如下:
-
LocaleManageUtil.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22/** * 设置语言类型 */ fun setApplicationLanguage(context: Context) { val resources = context.applicationContext.resources val dm = resources.displayMetrics val config = resources.configuration val locale = getSetLanguageLocale(context) config.locale = locale if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { val localeList = LocaleList(locale) LocaleList.setDefault(localeList) config.locales = localeList context.applicationContext.createConfigurationContext(config) Locale.setDefault(locale) } resources.updateConfiguration(config, dm) } fun saveSelectLanguage(context: Context, select: Int) { saveLanguage(select) setApplicationLanguage(context) } -
App.kt
1 2 3 4 5 6override fun onCreate() { super.onCreate() instance = this LocaleManageUtil.setSharePref(this) LocaleManageUtil.setApplicationLanguage(this) }
解决横竖屏切换后语言变成系统语言的问题
-
LocaleManageUtil.kt
1 2 3 4 5fun onConfigurationChanged(context: Context) { saveSystemCurrentLanguage(context) setLocal(context) setApplicationLanguage(context) } -
App.kt
1 2 3 4override fun onConfigurationChanged(newConfig: Configuration?) { super.onConfigurationChanged(newConfig) LocaleManageUtil.onConfigurationChanged(this) }
源码地址
具体的源码在github上,大家可以自行查看
参考文章
- Android App 多语言切换
- Android面试系列之应用内多语言切换
- android程序内多语言切换不需要重新启动的解决方案 - 这第三篇好像讲的有点复杂,但是看完代码觉得效果应该不错,不过还没尝试
- 安卓App内多语言切换(Kotlin实现),不需要强杀重启app
- 不只是切换多语言Android(二) - 这个讲的不错,原理跟第三篇一样,直接在View实现,源码在这里。 - BTW:他前一篇换肤的文章——不只是切换多语言Android(一)也写的不错,有兴趣的朋友可以去看看
- Android国际化(多语言)实现,支持8.0 - 最终我参考的最多的是这个方案 XD
- Android Context完全解析,你所不知道的Context的各种细节 - 郭神的文章,主要是为了看Application方法的执行顺序,看看什么方法是在
onCreate()之前执行的——attachBaseContext()- Android Developer官方文档——处理配置变更
- onConfigurationChanged方法介绍及问题解决 - 可解决横竖屏切换后语言变成系统语言的问题