目录

Android多语言切换实现进化版--Kotlin实现

前言

前面发了一篇“Android多语言切换实现——Java实现”,但是那个方案有缺陷,于是今天想写个更完美的方案。

原理

原理是一样的,这里不多说,看前面那篇文章的原理部分

新的尝试

我又去搜了一波,看了下Activity的生命周期、Application方法的执行顺序等等…

主要思路

  1. App本地保存所设置的语言(通过数据库 or SharePreferences);
  2. 每个页面App类的生命周期中判断当时语言是否是所设置语言,如果不是,则更新Configuration信息;
    • ApplicationattachBaseContext设置当前设置的语言Locale
    • ApplicationonConfigurationChanged(改变系统语言以及横竖屏切换时会调用到)设置当前的语言Locale
    • ActivityattachBaseContext设置当前设置的语言Locale,所以一般这里是创建BaseActivity来方便统一改变
    • ServiceattachBaseContext设置当前设置的语言Locale(如果有 Service 的话)
    • Fragment的….暂时没找到,Fragment不知道是不是跟随父Activity改变语言的经过测试的确是跟着Activity改变的

实现

App本地保存所设置的语言

本例使用SharePreferences保存设置的语言,所以这里先创建一个SharePreferences的工具类(SharePrefUtils.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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
import android.content.Context
import android.content.SharedPreferences
import java.util.HashMap

class SharePrefUtils {

    private var pref: SharedPreferences? = null

    private val map = HashMap<String, Any>()

    val all: Map<String, *>
        get() = pref!!.all

    private fun setPref(pref: SharedPreferences) {
        map.clear()
        this.pref = pref
    }

    fun getString(key: String): String? {
        return pref!!.getString(key, "")
    }

    fun getBoolean(key: String): Boolean {
        return pref!!.getBoolean(key, false)
    }

    fun getInt(key: String): Int {
        return pref!!.getInt(key, 0)
    }

    fun clear() {
        pref!!.edit().clear().apply()
    }

    fun dataPrepare(key: String, value: Any): SharePrefUtils {
        // map.clear();
        map[key] = value
        return this
    }

    fun putData(): SharePrefUtils {
        // 获取所有键值对对象的集合
        for ((key, value) in map) {
            // 根据键值对对象获取键和值
            putObject(key, value)
        }
        return this
    }

    private fun putObject(key: String, value: Any) {
        when (value) {
            is Boolean -> pref!!.edit().putBoolean(key, value).apply()
            is Float -> pref!!.edit().putFloat(key, value).apply()
            is Int -> pref!!.edit().putInt(key, value).apply()
            is Long -> pref!!.edit().putLong(key, value).apply()
            is String -> pref!!.edit().putString(key, value).apply()
        }
    }

    class Builder {

        private var context: Context? = null

        private var pref: SharedPreferences? = null

        fun setContext(context: Context): Builder {
            this.context = context
            return this
        }

        fun setPref(prefName: String): Builder {
            if (pref == null && context != null) {
                pref = context!!.getSharedPreferences(prefName, Context.MODE_PRIVATE)
            }
            return this
        }

        fun create(): SharePrefUtils {

            val sharePrefUtils = SharePrefUtils()

            if (pref != null) {
                sharePrefUtils.setPref(pref!!)
            }
            return sharePrefUtils
        }
    }

}

这里使用建造者模式写这个工具类,使用方法:

  1. 先创建实例

    1
    2
    3
    4
    5
    6
    
    private var sharePrefUtils: SharePrefUtils? = null
    sharePrefUtils = SharePrefUtils
        .Builder()
        .setContext(context)    // 传上下文Context
        .setPref("SharePrefName")   // SharePref的名称
        .create()
    
  2. 根据Key获取Value

    1
    
    var value = sharePrefUtils!!.getInt("Key")
    
  3. 把数据存到SharePref中

    1
    2
    3
    
    sharePrefUtils!!
        .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
    19
    
    fun 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
    13
    
    override 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
    3
    
    override fun attachBaseContext(newBase: Context) {
        super.attachBaseContext(LocaleManageUtil.setLocal(newBase))
    }
    
  • Service

    1
    2
    3
    
    override 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
      18
      
      fun 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
      4
      
      private fun selectLanguage(select: Int) {
          LocaleManageUtil.saveSelectLanguage(this, select)
          LocaleManageUtil.toRestartLauncherActivity(this)
      }
      

这里稍微解释一下:在ApplicationActivity中,attachBaseContext都是在onCreate方法之前执行的,所以我们先在Application中的attachBaseContext获取当前系统的语言并存下来(必须在所有获取语言的方法执行前存下当前系统语言,否则在后面获取可能会出现获取到的不是系统语言而是设置的语言的情况)。

到这里理论上语言就可以成功改变啦~

遇到的坑

context为applicationContext时切换语言后设置的string没有改变的问题

我们都会在代码中调用context.getResource().getString()这句代码看起来没什么问题,但是你这个context要是用的是applicationContext那么问题就来了。你会发现当你切换语言后用这样方式设置的string没有改变,所以我们需要改动我们的代码。

解决方法就是,在切换语言后把applicationupdateConfiguration也要更新了,方法如下:

  • 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
    6
    
    override fun onCreate() {
        super.onCreate()
        instance = this
        LocaleManageUtil.setSharePref(this)
        LocaleManageUtil.setApplicationLanguage(this)
    }
    

解决横竖屏切换后语言变成系统语言的问题

  • LocaleManageUtil.kt

    1
    2
    3
    4
    5
    
    fun onConfigurationChanged(context: Context) {
        saveSystemCurrentLanguage(context)
        setLocal(context)
        setApplicationLanguage(context)
    }
    
  • App.kt

    1
    2
    3
    4
    
    override fun onConfigurationChanged(newConfig: Configuration?) {
        super.onConfigurationChanged(newConfig)
        LocaleManageUtil.onConfigurationChanged(this)
    }
    

源码地址

具体的源码在github上,大家可以自行查看

参考文章