Android 8.0 App内切换语言不生效的问题记录

本文介绍了一款应用如何实现多语言切换功能,并解决了在Android 8.0系统上的兼容性问题。包括读取手机语言设置、允许用户自定义语言选择以及技术实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

国内大部分简单应该都只做了中文简体版,但是有部分项目需要面向国际化,甚至可能就是主打国外市场。因此我们有时候会遇到需要APP内做多语言切换的功能需求。
如何做多语言切换,网上资料还是很多的,本文也不是记录如何做这个功能,就不详细描述了。简单说一下我公司的项目的做法。

1.首次安装进入APP,读取手机的语言设置。如果该语言APP内做了适配则APP语言也使用该种,否则默认使用英文。系统会记住这个值,后续就算手机语言更换,APP也还是会使用第一次打开时记住的语言。
2.用户可以在APP内的设置内切换可选的语言,后续系统就记住这个值,每次都用该语言,除非用户再次在APP内更换语言。
3.技术角度分析,点击更换其他语言的时候,保存语言设置值在本地,发送广播通知语言更换。刚开始是这样处理的,每个Activity均继承自BaseActivity,BaseActivity中进行了广播动态注册(关于广播的静态注册与动态注册也不啰嗦了)。更该语言之后调用方法更改APP的更改语言配置的方法,该方法是全局变化,然后再发送广告,每个Activity都动态注册了广告,监听到语言更换的广播之后,调用recreate方法,则每个存活的Activity都会重启一下更换成了设置的语言。

下面的代码片段是之前更新语言设置的部分代码

Resources resources = mContext.getApplicationContext().getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
Configuration config = resources.getConfiguration();

config.locale = getSetLocale(); // getSetLocale方法是获取新设置的语言
resources.updateConfiguration(config, dm);

提个题外话,其实有些APP比如微信,更换语言的时候就是会重新回到主界面去,而我这里采用的停留在原界面把所有界面都recreate的方式也是看个人喜好和需求的,当前的设置的原界面会闪一下来切换语言,如果接受不了的不妨按照微信APP的方式。然后关于如何处理更换语言也是方式很多种,一般都会写个工具类啥的,然后要不要使用广播来通知整个APP语言切换也都是大家自己决定。

一、8.0安卓系统广播
那么问题是什么呢,之前一直使用这种方式没出什么问题。但是就在最近有香港的同事在测试APP的时候发现他的手机无法语言切换,而我们都OK。询问得知他的手机系统是8.0的系统。使用8.0模拟器测试后也发现了这个问题,调试发现广播接收者根本没收到广告,很奇怪,然后就来搜索安卓8.0系统广播问题,然后知道8.0系统针对广播做了一些处理,其实在7.0就开始了,只不过8.0系统彻底大改动导致之前的广播方法行不通。
网上给了两种解决方案,使用动态广播注册或者针对静态注册添加指定广播出去的包名、接受者的全路径类名,但是不知道是哪里出了问题,我本来就是动态注册来着… 有考虑过是否是因为我每个都是继承基类,然后每个基类都动态注册了的原因。但是因为我也认识到这种动态注册方式的不可取,所以干脆尝试了下静态注册,然后成功监听到广播了。

所以我就把每个界面都动态注册语言更换广播改成了静态注册广播,我们的工程内有记录Activity列表,当初是用于更好的处理退出整个APP,在这里的话就可以用来静态注册的广播收到广播后,调用整个Activity列表每个类都执行recreate方法,虽然差不多,但是更好统一方便管理了,而且更换之后也能在8.0系统上收到广播了。

之前发送广播的代码,很简单,在8.0之前的系统中运行没毛病。

Intent intent = new Intent();
intent.setAction("XXX");
mContext.sendBroaast(intent);

静态注册广播,解决8.0系统广播问题的代码,指定了广播发给那个包名以及哪个广播接收者类的全名。

Intent intent = new Intent(mContext, XXXBroadcastReceiver.class);
intent.setPackage("com.XXX.app");
intent.setComponent(new ComponentName("com.XXX.app", "com.XXX.XXXBroadcastReceiver"));
intent.setAction("XXX");
mContext.sendBroadcast(intent);

如果不是使用广播形式通知更该语言的,这些东西就没多大关系了,但是如果你的APP内也用到了广播,还没做8.0适配的话,还是赶紧拿个8.0手机测试下,有问题的话赶紧做适配处理吧~

二、8.0安卓系统APP内语言更改
广播能监听到了,也调用了recreate方法了,但是,语言确实还是没有变化。这就很奇怪了,赶紧继续搜索安卓8.0系统语言切换问题,居然针对语言切换的改动也那么大!

1、 首先,之前的语言更改代码需要做特殊处理了。针对高版本系统,调用了setLocale的方法。另外此处的Context建议使用ApplicationContext。但是网上有些8.0语言切换的方法的代码就是需要这里的Context使用当前界面的上下文,这种方式很麻烦,每个Activity都需要这样处理,但是他们的那种方法又必须Context使用该界面的上下文才生效,所以这种方法是不可取的。至于这里使用了ApplicationContext后续如何具体生效到每个界面,后面会说。

Resources resources = mContext.getApplicationContext().getResources();
DisplayMetrics dm = resources.getDisplayMetrics();
Configuration config = resources.getConfiguration();

Locale locale = getSetLocale();// getSetLocale方法是获取新设置的语言
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
    config.setLocale(locale);
} else {
    config.locale = locale;
}
resources.updateConfiguration(config, dm);

2、 其实做了多语言切换的APP都会在Application类的onCreate方法执行读取该APP设置的语言进行设置更换的方法,但是我们现在还需要在Application类加这段代码。

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    XXXX(); // 调用读取语言设置且更换APP语言的方法
}

3、语言切换的代码,在高版本中废弃了updateConfiguration方法,替代方法为createConfigurationContext。该方法是返回一个Context,也就是语言需要植入到Context中,每个Context都植入一遍。当然我们没那么傻,我们有基类,所以在BaseActivity中加入了如下代码。

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(XXXUtils.attachBaseContext(base));
}

XXXUtils代码中的attachBaseContext方法做了处理。

public Context attachBaseContext(Context context) {
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // 8.0需要使用createConfigurationContext处理
       return updateResources(context);
   } else {
       return context;
   }
}

@TargetApi(Build.VERSION_CODES.N)
private Context updateResources(Context context) {
    Resources resources = context.getResources();
    Locale locale = getSetLocale();// getSetLocale方法是获取新设置的语言

    Configuration configuration = resources.getConfiguration();
    configuration.setLocale(locale);
    configuration.setLocales(new LocaleList(locale));
    return context.createConfigurationContext(configuration);
}

至此,再去8.0模拟器中测试,可以正常更换语言了。
该文章作为自己的一次适配简单记录,也提醒还没做适配的安卓开发者需要做下适配了。下面给出我参考的文章和Github的Demo,有需要的可以去查看,Demo中有写好工具类等大家可以拿来使用或者和我一样根据自己的情况参考代码自己做处理。
有错误之处大家可以指出或者交流,有疑问的也可以留言。

参考文章: https://yanlu.me/android-7-0-app-language-switch/
参考Demo:https://github.com/captain-miao/MultiLanguagesSwitch

20190219更新
如果在app中需要知道系统的语言设置,在某些地方根据系统语言做某些处理或者语言设置列表需要有一个跟随系统的选项的话,实践可行的一个解决方案是:

之前的设置语言代码,都是保存设置的语言在手机本地,在application启动时读取保存的语言设置进行设置,一旦手动更改设置了,后续再去获取语言都是获取到设置了之后的,所以需要在启动程序application初始化中,先获取系统语言保存后再把app设置为显示其他语言。后续便可以语言设置更改会跟随系统。
在application的onConfigurationChanged中,还能监听到app使用过程中系统语言设置的更改,这样更能保证保存的是实时的正确的系统语言。(微信的跟随系统,在微信使用过程中更改了系统语言后跟随系统语言设置还能读取设置的之前的系统语言,可能就是没做这个监听重置,当然,这些都是小问题而且一般用户也不会这样操作)
经测试发现系统语言更改了,application不是重走onCreate方法但是onConfigurationChanged会触发,界面也会重新onCreate。

onConfigurationChanged在横竖屏切换时也会触发,另一种方式是监听广播,系统语言更改时会触发广播ACTION_LOCALE_CHANGED,未测试,但是应该是比onConfigurationChanged方法更合适的。

<think>好的,我需要帮助用户在Android 13上实现字体样式动态切换功能。首先,我得回想一下Android系统关于字体管理的知识。记得从Android 8.0开始,系统支持通过资源文件配置字体,但动态切换可能需要一些额外的步骤。 用户提到的是Android 13,所以应该基于最新的API来考虑。首先,可能需要检查系统是否允许动态更换字体。根据引用[5],Android允许自定义字体样式,但默认可能需要静态设置。动态切换的话,可能需要通过覆盖Typeface的方式,或者使用反射等方法,不过这些可能存在兼容性问题。 接下来,用户可能需要在应用中内置字体文件,比如TTF或WOFF格式。引用[3]提到了使用LVGL实现多语言切换时下载字体文件,这可能类似,需要将字体放在assets目录下。然后通过Typeface.createFromAsset加载字体。 另外,引用[4]列出的框架修改文件,如fonts.mk和Android.bp,可能涉及系统级的字体添加,但用户可能需要应用层级的解决方案,而不是修改系统文件。不过如果用户有系统权限,可能需要将字体添加到系统字体目录,但这对于普通应用来说不可行,所以应该集中在应用内实现。 然后,动态切换可能涉及到监听设置的变化,比如用户切换字体时,应用如何响应。引用[2]提到切换系统字体样式的问题,但用户的问题是在应用内动态切换,所以可能需要一个机制,比如按钮点击时重新设置字体。 可能的步骤是:1. 准备字体文件并放入assets/fonts目录。2. 在代码中创建Typeface对象。3. 遍历View层级,找到TextView并设置Typeface。不过这种方法效率可能不高,特别是对于复杂布局。或者,使用LayoutInflater设置全局字体,但这需要Hook或自定义主题。 另外,考虑使用Android的下载字体功能,通过FontRequest和FontsContract,但需要网络权限,并且可能不支持所有字体。或者使用AppCompat的字体资源,在res/font中定义,然后通过XML设置,但动态切换可能需要重新创建Activity,或者使用回调更新UI。 还有,引用[5]提到自定义字体样式和系统默认样式的设置,可能涉及Typeface类的方法。例如,通过反射修改系统默认字体,但这在Android高版本中可能被限制,特别是非系统应用。 所以,综合考虑,应用内动态切换字体的可行方案是:每次切换时,遍历当前Activity的视图树,将所有TextView的字体替换为自定义字体。或者,使用自定义的TextView子类,全局替换。但这样需要修改所有布局文件,不太灵活。 或者,使用第三方库如Calligraphy,但需要评估是否支持最新Android版本。不过用户可能需要原生实现。 总结步骤:1. 加载字体文件;2. 设置点击事件触发字体切换;3. 更新所有TextView的字体。同时,要注意性能问题,以及如何保存字体状态,可能在SharedPreferences中存储当前字体选择,应用启动时应用设置。 另外,需要考虑字体作用域,是否全局生效还是单个Activity。每次进入Activity时检查设置并更新字体。或者使用基类Activity,在onResume中更新字体。 最后,测试不同Android版本的兼容性,特别是Android 13的权限和API限制。可能需要使用Activity.recreate()来重新加载界面,但这样体验不佳。如果使用覆盖,可能更平滑,但需要正确的实现。</think>在 Android 13 中实现字体样式动态切换功能,可通过以下步骤实现: --- ### 一、准备工作 1. **添加字体文件** - 将自定义字体文件(如 `.ttf` 或 `.otf`)放入 `app/src/main/assets/fonts/` 目录。 - 示例:`myfont_regular.ttf`、`myfont_bold.ttf`。 2. **配置字体资源(可选)** 在 `res/font/` 目录下定义字体族(适用于静态绑定): ```xml <!-- res/font/my_font_family.xml --> <font-family> <font android:font="@font/myfont_regular" android:weight="400"/> <font android:font="@font/myfont_bold" android:weight="700"/> </font-family> ``` --- ### 二、动态切换字体实现方案 #### 方案 1:通过代码动态加载字体 1. **加载字体文件** ```kotlin val typeface = ResourcesCompat.getFont(context, R.font.myfont_regular) // 或从 assets 加载: val typeface = Typeface.createFromAsset(assets, "fonts/myfont_regular.ttf") ``` 2. **全局替换 TextView 字体** ```kotlin fun setGlobalFont(view: View, typeface: Typeface?) { if (view is ViewGroup) { for (i in 0 until view.childCount) { setGlobalFont(view.getChildAt(i), typeface) } } else if (view is TextView) { view.typeface = typeface } } // 在 Activity 中调用: setGlobalFont(window.decorView.rootView, typeface) ``` #### 方案 2:使用主题和样式(需重启 Activity) 1. **定义带字体引用的主题** ```xml <style name="AppTheme.CustomFont" parent="Theme.Material3.DayNight"> <item name="android:fontFamily">@font/my_font_family</item> </style> ``` 2. **动态切换主题后重启 Activity** ```kotlin class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { setTheme(R.style.AppTheme_CustomFont) // 根据设置选择主题 super.onCreate(savedInstanceState) // ... } fun switchFont() { // 保存字体选择到 SharedPreferences recreate() // 重启 Activity 应用新主题 } } ``` --- ### 三、高级优化 1. **性能优化** - 使用 `ViewModel` 或静态变量缓存 `Typeface` 对象,避免重复加载[^5]。 - 对复杂界面采用异步加载字体。 2. **系统级字体切换(需 Root 权限)** - 修改系统字体配置文件(如 `fonts.xml`),需系统签名或 Root 权限[^4]: ```xml <!-- 示例:替换系统默认字体 --> <family name="sans-serif"> <font weight="400" style="normal">MyFont-Regular.ttf</font> </family> ``` 3. **多语言支持** - 结合多语言资源目录(如 `values-zh/fonts.xml`),实现按语言自动切换字体[^3]。 --- ### 四、注意事项 - **兼容性**:动态加载字体在 Android 8.0+ 上更稳定,低版本需测试。 - **作用域**:代码动态替换仅影响当前 Activity,需结合 `ActivityLifecycleCallbacks` 实现全局生效。 - **系统限制**:非系统应用无法直接修改系统全局字体[^2][^4]。 ---
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值