Android国际化多语言切换

2023-11-05

关于App国际化,之前有讲到国际化资源、字符换、布局相关,想要了解的猛戳用力抱一下APP国际化。借着本次重构多语言想跟大家聊一下多语言切换,多语言切换对于一款国际化App来讲是重中之重,并非难事,但是若要做好也是一件不容易的事情。

问题

  1. Android N版本适配问题
  2. AndroidX不同版本兼容问题
  3. 一些界面局部适配突然失效
  4. 切换系统导航,更改深色模式导致多语言无法适配
  5. 系统授权弹窗导致ApplicationContext中的Local被还原
  6. 切换语言,系统通知栏显示未翻译,重启后正常
  7. Service服务中Toast不适配
  8. 系统Local.getDefault()之伤,如何正确获取系统当前语言
  9. WebView第一次加载多语言不适配
  10. 系统广播中的获取context中的Local信息显示异常

上面我随手列出了项目中常见遇到的问题,有一些是随着Android版本升级而未做出相应兼容性调整造成的,有一些则是局部失效寻找原因所得。我们先了解下应用中一般多语言切换适配的方案,从中会提到这些问题相应的解决方案。

Andorid 13 语言偏好设置

最近Android 13发布了,讲多语言切换之前,我们先了解一下这个新平台多语言新特性。Android 13 在手机设置页面中新增了一个集中设置应用语言的选项,用于设置各个应用语言首选语言,如果你的应用存在多语言,谷歌强烈建议在设置中进行多语言切换,这样就无须在应用中去做多语言选择切换的功能,页面由设置中的应用语言界面统一管理,具体使用如下:

1. 如何让应用app显示在设置中的应用语言中

  • 创建一个名为 res/xml/locales_config.xml 的文件,并指定您的应用的语言,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
   <locale android:name="ja"/>
   <locale android:name="fr"/>
   <locale android:name="en"/>
</locale-config>
  • 在清单中,添加一行指向这个新文件的代码:
<manifest
    ...
    <application
        ...
        android:localeConfig="@xml/locales_config">
    </application>
</manifest>

2. 如何处理设置中的语言偏好

对于具有或想要使用应用内语言选择器的应用,请使用这些新 API(而非自定义应用逻辑)来处理相关设置和获取用户对应用的首选语言设置。

// 如需设置用户的首选语言,您需要让用户在语言选择器中选择语言区域,然后在系统中设置该值
LocaleListCompat appLocale = LocaleListCompat.forLanguageTags("xx-YY");
// Call this on the main thread as it may require Activity.restart()
AppCompatDelegate.setApplicationLocales(appLocale);
  • 使用 Android 框架 API 来实现
// 1. Inside an activity, in-app language picker gets an input locale "xx-YY"
// 2. App calls the API to set its locale
mContext.getSystemService(LocaleManager.class).setApplicationLocales(newLocaleList(Locale.forLanguageTag("xx-YY")));
// 3. The system updates the locale and restarts the app, including any configuration updates
// 4. The app is now displayed in "xx-YY" language
  • 获取用户当前的首选语言
// 1. App calls the API to get the preferred locale
LocaleList currentAppLocales =
    mContext.getSystemService(LocaleManager.class).getApplicationLocales();
// 2. App uses the returned LocaleList to display languages to the user

举个栗子,手机系统如果是中文,设置界面中的应用语言如果首选英文,app如果已启动,那么需要自己监听 onConfigurationChanged来切换应用内部语言,如果未启动,第一次启动时候需要先去读取设置中的语言然后设置给当前应用。亦或是如果你的应用保留了内部切换语言的方案,那么语言切换是也应该调用以上API把当前语言刷到系统设置应用语言中,以保持同步。

讲完Andorid 13 多语言新特性,想要了解更多猛戳,我们继续回到本文的重点,应用内多语言切换如何去做适配。

多语言适配整体部分

1. Application的适配

我们为什么要适配Application,原因很简单,对于多语言来讲,我们其实最关心的是切换语言后,界面或者Toast等等显示是否已经翻译成所选择的语言,但是一般我们项目中都会直接或者间接用到ApplicationContext,比如Application中一些三方控件的初始化,还有一些项目中封装的工具类,为了方便全局一次行初始化,有可能甚至用到单例模式,当我们用到ApplicationContxt去getString(@StringRes int id),在切换语言后,如果不重启整个应用或者刷新ApplicaitonContext的local,那么肯定是无效的。
我们在启动APP时候,应该对Application中的context进行当前应用语言Local适配。

@Override
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(LanguageUtil.attachBaseContext(newBase));
}

当我们做了系统的配置更改,比如说切换了系统导航或者说更改了深色模式,那么我们一般的处理是也是要对Application作出处理。

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    // 系统资源配置发生更改,例如主题模式,需要重新刷新多语言
    LanguageUtil.attachBaseContext(this);
}

如果项目中有用到ApplicationContext去getString(@StringRes int id)实现加载的提示语,那么如果只是单纯的重启界面则无法让当前的提示语跟随当前切换的语言,所以我们要么重启整个应用,要么对ApplicationContext中的Local也作出相应的更新方可,这里有一点问题,虽然Android N之后updateConfiguration是过时方法,官方给出使用createConfigurationContext代替,但是更新ApplicationContext的Local发现无效使用老版本updateConfiguration正常。

Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
Locale locale = getLanguageLocale(newLanguage);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    // apply locale
    configuration.setLocales(new LocaleList(locale));
} else {
    configuration.setLocale(locale);
}
DisplayMetrics dm = resources.getDisplayMetrics();
resources.updateConfiguration(configuration, dm);

如果你发现你的应用广播通知栏适配无效,那就是context中的Local在切换语言是并未及时更新Local,这里调试一下便知,如果是Applicaiton注册的广播,那么多半情况下是没有更新ApplicationContext的Local所导致的。

2. Service适配

如果你的Service有用到Toast提示或者UI相关的东西,你必须要对Service也进行适配,这时候Service中也需要重写attachBaseContext进行语言适配,否则语言适配也是无效的。

@Override
protected void attachBaseContext(Context newBase) {
    super.attachBaseContext(LanguageUtil.getNewLocalContext(newBase));
}

3. Activity适配

Activity是我们最主要的适配的界面,正常的情况下我们直接在基类BaseActivity中去处理即可,但是值得注意的一点是如果我们使用的是Androidx而非support库,那么不同的版本适配有点区别,这也是官方组件的问题.记得一些第三方界面如果不是继承我们的BaseActivity需要单独处理即可。

@Override
protected void attachBaseContext(Context newBase) {
    if(isSupportMultiLanguage()){
        // 多语言适配
        super.attachBaseContext(LanguageUtil.getNewLocalContext(newBase));
    }else {
        super.attachBaseContext(newBase);
    }
}

多语言适配基本步骤大概就是如此了,下面看一下适配的细节问题。

适配部分细节

1. Andorid N 适配

Android N开始,由于系统的API变更,updateConfiguration已经被沦为过时的方法。但是有一点需要大家注意,网上几乎全部的判断都是有问题的,API已经明确说明是在API25过时的,不等价于Build.VERSION_CODES.N,所以你的项目用对了嘛,详情可参考下图。

还有一点Android N之后,手机系统的语言配置选项已经不是单选了,改为一个列表了,具体可以参考手机设置中的语言和输入法,所以setLocal(@Nullable Locale loc)方法建议不要再使用了,我相信很多人还在用,正确的用法应该是setLocals(@Nullable LocaleList locales),需要传递一个集合。

public static Context attachBaseContext(Context context) {
    String language = LanguageSp.getLanguage(context);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
        return createConfigurationContext(context, language);
    } else {
        return updateConfiguration(context, language);
    }
}
// 注意此处不是Build.VERSION_CODES.N
@RequiresApi(api = Build.VERSION_CODES.N_MR1)
private static Context createConfigurationContext(Context context, String language) {
    Resources resources = context.getResources();
    Configuration configuration = resources.getConfiguration();
    Locale locale = getLanguageLocale(language);
    Log.d(TAG, "current Language locale = " + locale);
    LocaleList localeList = new LocaleList(locale);
    // 注意此处setLocales
    configuration.setLocales(localeList);
    return context.createConfigurationContext(configuration);
}
private static Context updateConfiguration(Context context, String language) {
    Resources resources = context.getResources();
    Configuration configuration = resources.getConfiguration();
    Locale locale = getLanguageLocale(language);
    Log.e(TAG, "updateLocalApiLow==== " + locale.getLanguage());
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        // apply locale 注意此处是setLocales
        configuration.setLocales(new LocaleList(locale));
    } else {
        // updateConfiguration
        configuration.locale = locale;
        DisplayMetrics dm = resources.getDisplayMetrics();
        resources.updateConfiguration(configuration, dm);
    }
    return context;
}

2. 关于AndroidX版本兼容问题

当你的应用使用的是androidx.appcompat:appcompat:1.1.0时,BaseActivity中需要实现下面方法。

@Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
    // 兼容androidX在部分手机切换语言失败问题
    if (overrideConfiguration != null) {
        int uiMode = overrideConfiguration.uiMode;
        overrideConfiguration.setTo(getBaseContext().getResources().getConfiguration());
        overrideConfiguration.uiMode = uiMode;
    }
    super.applyOverrideConfiguration(overrideConfiguration);
}

当你的应用使用的是androidx.appcompat:appcompat:1.2.0及以上时,BaseActivity中需要实现下面方法。

@Override
protected void attachBaseContext(Context newBase) {
    if (isSupportMultiLanguage()) {
        String language = LanguageSp.getLanguage(newBase);
        Context context = LanguageUtil.attachBaseContext(newBase, language);
        final Configuration configuration = context.getResources().getConfiguration();
        final ContextThemeWrapper wrappedContext = new ContextThemeWrapper(context,
            R.style.Theme_AppCompat_Empty) {
            @Override
            public void applyOverrideConfiguration(Configuration overrideConfiguration) {
                if (overrideConfiguration != null) {
                    overrideConfiguration.setTo(configuration);
                }
                super.applyOverrideConfiguration(overrideConfiguration);
            }
        };
        super.attachBaseContext(wrappedContext);
    } else {
        super.attachBaseContext(newBase);
    }
}

3. 系统授权弹框导致Local失效

我们惊奇的发现,当我们首次进入APP选择语言后,当首页检查系统权限弹框的时候,Local被莫名其妙的重置了,我在想,可能因为google授权弹框他有自己的多语言翻译,所以不会采取我们的,所以把ApplicationContext中的Local给重置了,所以当我们点击允许或者仅在使用此应用时允许后需要再次把Application中的Local修改掉。

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults)
    // 更新Application中的local
    LanguageUtil.updateApplicationLocale(AppApplication.getAppContext(),LanguageSp.getLanguage(mContext))
}

4. 如何真正的获取系统语言

我们有可能会存在这个场景,当我们的APP不跟随系统语言的时候,使用的APP内部语言,我们去检测系统语言的时候如何去判断,是不是很多人在此跌倒了,无论是Local.getDefault()还是LocalList.get(0)始终获取的语言是错误的,应该通过以下渠道获取当前的系统语言。

// 第一种方式
if (Build.VERSION.SDK_INT  >= Build.VERSION_CODES.N) {
return Resources.getSystem().getConfiguration().getLocales().get(0).getLanguage();//解决了获取系统默认错误的问题
} else {
return Locale.getDefault().getLanguage();
}
// 第二种方式(推荐)
return ConfigurationCompat.getLocales(Resources.getSystem().getConfiguration()).get(0).getLanguage();

5. 关于WebView适配

在原来的低版本切换语言中,我们会发现WebView第一次加载时,适配是无效的,再次加载则正常适配,所以网上也有了一道方案如下:

@Override public void onCreate(Bundle savedInstanceState) { 
// TODO 解决含有webView控件导致切换语言失效 
~~new WebView(this).destroy(); ~~
super.onCreate(savedInstanceState); 
}

这套方案目前不在推荐,直接去替换attatchBaseContext()中的context则可,经过测试是完全正常的。

工具类

以下则是多语言操作的工具类,现在提供出来,需要的朋友可以自行进行改造。

/**
 * @author : le.hu
 * e-mail : 暂无
 * time   : 2021/11/26/16:08
 * desc   : 多语言适配方案,适配各种版本,核心未替换上下文Context中的Local
 */
public class LanguageUtil {

    private static final String TAG = "LanguageUtil";

    /**
     * 默认支持的语言,英语、法语、阿拉伯语
     */
    private static HashMap<String, Locale> supportLanguage = new HashMap<String, Locale>(4) {{
        put(Language.ENGLISH, Locale.ENGLISH);
        put(Language.FRANCE, Locale.FRANCE);
        put(Language.ARABIC, new Locale("ar", "", ""));
    }};

    /**
     * 应用多语言切换,重写BaseActivity中的attachBaseContext即可
     * 采用本地SP存储的语言
     *
     * @param context 上下文
     * @return context
     */
    public static Context attachBaseContext(Context context) {
        String language = LanguageSp.getLanguage(context);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
            return createConfigurationContext(context, language);
        } else {
            return updateConfiguration(context, language);
        }
    }

    /**
     * 应用多语言切换,重写BaseActivity中的attachBaseContext即可
     *
     * @param context  上下文
     * @param language 语言
     * @return context
     */
    public static Context attachBaseContext(Context context, String language) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
            return createConfigurationContext(context, language);
        } else {
            return updateConfiguration(context, language);
        }
    }

    /**
     * 获取Local,根据language
     *
     * @param language 语言
     * @return Locale
     */
    private static Locale getLanguageLocale(String language) {
        if (supportLanguage.containsKey(language)) {
            return supportLanguage.get(language);
        } else {
            Locale systemLocal = getSystemLocal();
            for (String languageKey : supportLanguage.keySet()) {
                if (TextUtils.equals(supportLanguage.get(languageKey).getLanguage(), systemLocal.getLanguage())) {
                    return systemLocal;
                }
            }
        }
        return Locale.ENGLISH;
    }

    /**
     * 获取当前的Local,默认英语
     *
     * @param context context
     * @return Locale
     */
    public static Locale getCurrentLocale(Context context) {
        String language = LanguageSp.getLanguage(context);
        if (supportLanguage.containsKey(language)) {
            return supportLanguage.get(language);
        } else {
            Locale systemLocal = getSystemLocal();
            for (String languageKey : supportLanguage.keySet()) {
                if (TextUtils.equals(supportLanguage.get(languageKey).getLanguage(), systemLocal.getLanguage())) {
                    return systemLocal;
                }
            }
        }
        return Locale.ENGLISH;
    }

    /**
     * 获取系统的Local
     *
     * @return Locale
     */
    private static Locale getSystemLocal() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            return Resources.getSystem().getConfiguration().getLocales().get(0);
        } else {
            return Locale.getDefault();
        }
    }

    /**
     * Android 7.1 以下通过 updateConfiguration
     *
     * @param context  context
     * @param language 语言
     * @return Context
     */
    private static Context updateConfiguration(Context context, String language) {
        Resources resources = context.getResources();
        Configuration configuration = resources.getConfiguration();
        Locale locale = getLanguageLocale(language);
        Log.e(TAG, "updateLocalApiLow==== " + locale.getLanguage());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            // apply locale
            configuration.setLocales(new LocaleList(locale));
        } else {
            // updateConfiguration
            configuration.locale = locale;
            DisplayMetrics dm = resources.getDisplayMetrics();
            resources.updateConfiguration(configuration, dm);
        }
        return context;
    }

    /**
     * Android 7.1以上通过createConfigurationContext
     * N增加了通过config.setLocales去修改多语言
     *
     * @param context  上下文
     * @param language 语言
     * @return context
     */
    @RequiresApi(api = Build.VERSION_CODES.N_MR1)
    private static Context createConfigurationContext(Context context, String language) {
        Resources resources = context.getResources();
        Configuration configuration = resources.getConfiguration();
        Locale locale = getLanguageLocale(language);
        Log.d(TAG, "current Language locale = " + locale);
        LocaleList localeList = new LocaleList(locale);
        configuration.setLocales(localeList);
        return context.createConfigurationContext(configuration);
    }

    /**
     * 切换语言
     *
     * @param language 语言
     * @param activity 当前界面
     * @param cls      跳转的界面
     */
    public static void switchLanguage(String language, Activity activity, Class<?> cls) {
        LanguageSp.setLanguage(activity, language);
        Intent intent = new Intent(activity, cls);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        activity.startActivity(intent);
        activity.finish();
    }

    /**
     * 切换语言,携带传递数据
     *
     * @param language 语言
     * @param activity 当前界面
     * @param cls      跳转的界面
     */
    public static void switchLanguage(String language, Activity activity, Class<?> cls, Bundle bundle) {
        LanguageSp.setLanguage(activity, language);
        Intent intent = new Intent(activity, cls);
        if (bundle != null) {
            intent.putExtras(bundle);
        }
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        activity.startActivity(intent);
        activity.finish();
    }

    /**
     * 获取新语言的 Context,修复了androidx.appCompact 1.2.0的问题
     *
     * @param newBase newBase
     * @return Context
     */
    public static Context getNewLocalContext(Context newBase) {
        try {
            // 多语言适配
            Context context = LanguageUtil.attachBaseContext(newBase);
            // 兼容appcompat 1.2.0后切换语言失效问题
            final Configuration configuration = context.getResources().getConfiguration();
            return new ContextThemeWrapper(context, R.style.Theme_AppCompat_Empty) {
                @Override
                public void applyOverrideConfiguration(Configuration overrideConfiguration) {
                    if (overrideConfiguration != null) {
                        overrideConfiguration.setTo(configuration);
                    }
                    super.applyOverrideConfiguration(overrideConfiguration);
                }
            };
        } catch (Exception e) {
            e.printStackTrace();
        }
        return newBase;
    }

    /**
     *  更新Application的Resource local,应用不重启的情况才调用,因为部分会用到application中的context
     *  切记不能走新api createConfigurationContext,亲测
     * @param context context
     * @param newLanguage newLanguage
     */
    public static void updateApplicationLocale(Context context, String newLanguage) {
        Resources resources = context.getResources();
        Configuration configuration = resources.getConfiguration();
        Locale locale = getLanguageLocale(newLanguage);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            // apply locale
            configuration.setLocales(new LocaleList(locale));
        } else {
            configuration.setLocale(locale);
        }
        DisplayMetrics dm = resources.getDisplayMetrics();
        resources.updateConfiguration(configuration, dm);
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android国际化多语言切换 的相关文章

随机推荐

  • php利用循环链表找猴王

    php利用循环链表找猴王 1 前述 2 php源码 3 html源码 1 前述 1 1实现说明 与array数组的实现思路大同小异 不过链表的实现方式略显繁琐 1 2实现思路 建立一个单向循环链表不断循环查找 查找到指定删除位置 踢出猴子即
  • Java连接mysql,sql语句中含有中文就查询不到结果

    最近写一个小项目 javaee项目连接了mysql数据库 根据用户名 密码却怎么也查不到用户结果 后改用英文的用户名就可以查到了 可见是编码问题 解决方法 连接语句加入如下代码 useUnicode true characterEncodi
  • 华为eNSP--4多个路由器连接配置(静态路由)

    R1路由器的配置 Huawei int e0 0 0 Huawei Ethernet0 0 0 ip add 192 168 1 10 24 Huawei Ethernet0 0 0 int e0 0 1 Huawei Ethernet0
  • cookie和session有什么区别,请你谈谈cookie的缺点

    1 区别 cookie机制采用的是在客户端保持状态的方案 session机制采用的是在服务端保持状态的方案 2 cookie 优点 1 gt 极高的扩展性和可用性 2 gt 通过编程方式 控制保存在cookie中的session对象的大小
  • 进程间的通信方式

    文章目录 一 进程通信概念 二 进程间通信方式 2 1 管道 匿名管道 管道的实质 局限 2 2 有名管道 FIFO 2 3 信号 来源 2 4 消息队列 特点 2 5 共享内存 2 6 信号量 互斥 同步 信号量的实现 2 7 Socke
  • 基本运算电路之---反向比例运算电路(1)

    基本运算电路之 反向比例运算电路 1 我们是从大二上学期开始学习模电 丫丫的 当时一直知道这门学科很重要 励志学好它 嘿嘿 可惜后来发现然并卵 除了知道有二极管 三极管 MOS管 运放 就基本别无其他的事了 电路图的原理 数据计算更是一窍不
  • Linux屏中信息量大,一屏或几屏显示不全怎么办

    Linux屏中信息量大 一屏或几屏显示不全 两种方法 1 加个参数 more 例如 ls l more 这样敲完命令后 屏幕信息会停留在第一页上 再敲回车后 又多显示一行 但如果 信息太多敲回车太麻烦 可以采用第二种方法 2 写入一个文件中
  • linux下eclipse C++ 多线程调试

    初学linux编程 想要用linux下eclipse C 多线程调试 发现相关资料很少 所以想写一篇这样的文章 在这个页面看到 这里 If you use eclipse CDT you probably understand that e
  • 小信号先滤波还是先放大?

    1 是先滤波再放大 还是先放大再滤波 ADI 技术 电子技术论坛 广受欢迎的专业电子论坛 ADI亚洲技术支持中心的同事们给出的建议是 一般是先放大再滤波 这样经过放大器带来的噪声也可以被滤除 您怎么看 我觉得应该看是什么信号以及用什么样的运
  • Quartz 建表语句SQL文件

    Quartz 通过配置初始化数据库 https blog csdn net weixin 44371237 article details 133278217 官网找SQL SQL文件在jar里面 github下载 https github
  • SecureCRT MAC版本的单词跳转

    20210201 引言 最近更换了笔记本 第一次用mac 使用起来还是跟windows有很多不同 之前也已经安装了很多我在windows下的软件 例如securecrt 这个是我一直使用的终端软件 可能很多人在mac下都是使用iterm2
  • Linux系统意外断电无法启动解决方案

    首先看提示哪个盘有问题 如 sda2 fsck y dev sda2 修复完成后键入 reboot 重启电脑后修复OK
  • Gradle SNAPSHOT 版本更新

    转自 https www cnblogs com scoftlin p 9809623 html 在引用Maven 库上的aar 时经常会出现我们更新依赖的库时 Studio 并不能及时将最新的依赖库拉下来 这个因为gradle为了加快构建
  • [机器学习与scikit-learn-46]:特征工程-特征选择(降维)-2-常见的特征降维的方法大全

    作者主页 文火冰糖的硅基工坊 文火冰糖 王文兵 的博客 文火冰糖的硅基工坊 CSDN博客 本文网址 https blog csdn net HiWangWenBing article details 123953894 目录 前言 第1章
  • 服务器安装系统如何收费,服务器系统安装费用

    服务器系统安装费用 内容精选 换一换 根据是否支持高级的SCSI命令来划分磁盘模式 分为VBD 虚拟块存储设备 Virtual Block Device 类型和SCSI 小型计算机系统接口 Small Computer System Int
  • 不使用binlog,canal,kafka等,只用java+mybatis拦截器来实现项目中的异步双写主从数据库,代码逻辑全整理

    项目中因为要迁库 所以我要在原项目中接入我的双写逻辑 确保新旧两个库都有数据写入 假如新库写入失败 旧库数据也能写入 这就确保了重要数据不能丢失 一开始考虑的方案是使用数据同步工具 像是canal或是DTS等 但是环境这块卡的比较死 没有其
  • CSS3 opacity 属性设置 div 元素的不透明级别:

    div 本元素的不透明度是 0 5 请注意 文本和背景色都受到不透明级别的影响 div
  • for循环各部分的执行顺序

    最近在考虑for循环第三个参数是 i or i 如果清楚了for循环各部分的执行顺序的话 其实这个问题无关紧要 因为第三个参数的变化是留给第二个判断语句判断的 所以 i or i 的结果都是增一 判断结果一样 for循环的执行顺序是 初始化
  • vue项目中使用echarts做词云图

    效果图 安装依赖 npm install echarts npm i echarts wordcloud 完整代码
  • Android国际化多语言切换

    关于App国际化 之前有讲到国际化资源 字符换 布局相关 想要了解的猛戳用力抱一下APP国际化 借着本次重构多语言想跟大家聊一下多语言切换 多语言切换对于一款国际化App来讲是重中之重 并非难事 但是若要做好也是一件不容易的事情 问题 An