1.思路???
怎样实现多语言切换的功能呢?
首先基本的操作流是:用户选择语言,多语言输入法根据语言来进行适配,同时界面根据选择的语言也相应的进行适配。那么问题来了,输入法apk如何根据当前的语言来选择对应的输入法?如何实现选择语言更改整个界面?最后一个问题是导入资源字符串时,布局界面如何适配?
带着这些问题,我们来一个一个的解决,最后实现多语言这个功能。
2.首先找到键盘-> 解决输入法问题
鉴于此次项目不能联网,只能在现有的平台,于是针对很多国内的输入法,如百度输入法、搜狗输入法等比较长见的,无法使用。于是想着爬墙在Google商城中去寻找输入法,好在十分幸运找到了Gborad。此输入法不用联网就可以切换多语言,十分方便,但有一点不好的就是此输入法内存较大。
总结:找资源需要用到很多途径,一般国外资源会相对更多。
3.多语言切换功能要怎么实现呢?
问:输入法apk如何根据当前的语言来选择对应的输入法???
回答
:要安装输入法,安装输入法要考虑输入的名称怎么得到?怎么安装?安装思路是什么?首先要明确,要先判断
当前是否有输入法,如果没有,再进行安装。安装输入法,是用命令行去安装,文件要从assets目录复制到sd卡的可安装路径下
去安装。安装完成后,还要根据当前系统的语言来适配不同的输入法,如中文界面用谷歌拼音输入法,其他语言用Gboard输入
法。
/**
* 安装中文输入法
*/
public static void installChineseInputSoft() {
ArrayList<String> inputMethodList = new ArrayList<>();
InputMethodManager imm = (InputMethodManager) Utils.getApp().getSystemService(Context.INPUT_METHOD_SERVICE);
List<InputMethodInfo> mInputMethodProperties = imm.getInputMethodList();
for (int i = 0; i < mInputMethodProperties.size(); i++) {
inputMethodList.add(mInputMethodProperties.get(i).getId());
}
LogUtils.dTag(TAG, inputMethodList);//找到当前系统安装的输入法的名称,后面要用到
//首先判断当前是否有中文输入法,
if (inputMethodList.contains("com.google.android.inputmethod.pinyin/.PinyinIME")) {
return;
}
// 如果没有,则再判断config目录中是否有googelpingyin.apk
String pingYinApkName = "googlepinyin.apk";
String configApkName = PathConstants.APK_INSTALL_SDCARD_PATH + File.separator + pingYinApkName;
if (FileUtils.isFileExists(configApkName)) {
runInstallApk(configApkName);
return;
}
// 如无该APK,则进行复制粘贴
ThreadUtils.executeByIo(new ThreadUtils.SimpleTask<Void>() {
@Override
public Void doInBackground() throws Throwable {
boolean isCopy = ResourceUtils.copyFileFromAssets(pingYinApkName, configApkName);
LogUtils.dTag(TAG, "APK复制结果:" + isCopy);
//复制完成后进行脚本安装操作
if (isCopy) {
runInstallApk(configApkName);
}
return null;
}
});
}
/**
* 安装Gboard输入法
*/
public static void installGboradInputSoft() {
ArrayList<String> inputMethodList = new ArrayList<>();
InputMethodManager imm = (InputMethodManager) Utils.getApp().getSystemService(Context.INPUT_METHOD_SERVICE);
List<InputMethodInfo> mInputMethodProperties = imm.getInputMethodList();
for (int i = 0; i < mInputMethodProperties.size(); i++) {
inputMethodList.add(mInputMethodProperties.get(i).getId());
}
//首先判断当前是否有Gboard输入法,
if (inputMethodList.contains("com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME")) {
return;
}
// 如果没有,则再判断config目录中是否有googelpingyin.apk
String pingYinApkName = "Gboard.apk";
String configApkName = PathConstants.APK_INSTALL_SDCARD_PATH + File.separator + pingYinApkName;
if (FileUtils.isFileExists(configApkName)) {
runInstallApk(configApkName);
return;
}
// 如无该APK,则进行复制粘贴
ThreadUtils.executeByIo(new ThreadUtils.SimpleTask<Void>() {
@Override
public Void doInBackground() throws Throwable {
boolean isCopy = ResourceUtils.copyFileFromAssets(pingYinApkName, configApkName);
LogUtils.dTag(TAG, "APK复制结果:" + isCopy);
//复制完成后进行脚本安装操作
if (isCopy) {
runInstallApk(configApkName);
}
return null;
}
@Override
public void onSuccess(Void aVoid) {
}
});
}
private static void runInstallApk(String configApkName) {
long beforeTime = System.currentTimeMillis();
//如有该APK,则直接进行静默安装
String installCommand = "pm install -r " + configApkName;
ShellUtils.execCmdAsync(installCommand, false, new Utils.Consumer<ShellUtils.CommandResult>() {
@Override
public void accept(ShellUtils.CommandResult commandResult) {
long useTime = System.currentTimeMillis() - beforeTime;
LogUtils.dTag(TAG, "安装结果:"
+ commandResult
+ ",errorMsg:" + commandResult.errorMsg
+ ",time=" + useTime);
//有可能出现安装不成功的情况,这时候要将APK给删除避免以后再也无法安装成功
if (commandResult.result != 0 || !"Success".equals(commandResult.successMsg)) {
boolean deleteApk = FileUtils.delete(configApkName);
LogUtils.dTag(TAG, "安装不成功:删除apk" + deleteApk);
}
//安装完成后,切换一下语言
switchInputSoftKeyboard();
}
});
}
问:接着怎么选择输入法???
答:
安装输入法就实现了。但是目的还没有达到,安装输入法后,要根据当前的系统配置去切换输入法,目前只有3个输入法:系统自带、中文、其他国家,如何去适配其他国家呢,这个时候就要用到自定义的一些配置变量了。
先要定义一个系统相关的配置常量的接口,此接口中包含语言配置的一些常量,用于切换多语言时使用。
public interface ConfigConstants {
/**
* 语言默认为英文,下标为0,对应arrays中的system_language
*/
int LANGUAGE_DEFAULT = 0;
/**
* 简体中文,下标为1
*/
int LANGUAGE_SIMPLIFIED_CHINESE = 1;
/**
* 巴西葡萄牙语
*/
int LANGUAGE_BRAZILIAN_PORTUGUESE = 2;
/**
* 法语
*/
int LANGUAGE_FRENCH = 3;
/**
* 西班牙语
*/
int LANGUAGE_SPANISH = 4;
/**
* 俄语
*/
int LANGUAGE_RUSSIAN = 5;
/**
* sp的名称
*/
String SP_NAME = "system_config";
/**
* 当前设置的语言
*/
String SP_KEY_LANGUAGE = "language";
}
接着根据系统相关的配置常量,来切换软键盘。
/**
* 获取当前系统配置的语言,下标与arrays中的system_language相关联
*
* @return 当前系统设置的语言,默认值0,表示英文
*/
public static int getCurrentLanguageConfig() {
return SPUtils.getInstance(ConfigConstants.SP_NAME).getInt(ConfigConstants.SP_KEY_LANGUAGE, 0);
}
/**
* 切换软键盘
*/
public static void switchInputSoftKeyboard() {
try {
ArrayList<String> Array_mInputMethodId = new ArrayList<>();
InputMethodManager imm = (InputMethodManager) Utils.getApp().getSystemService(Context.INPUT_METHOD_SERVICE);
List<InputMethodInfo> mInputMethodProperties = imm.getInputMethodList();
for (int i = 0; i < mInputMethodProperties.size(); i++) {
Array_mInputMethodId.add(mInputMethodProperties.get(i).getId());
}
LogUtils.dTag(TAG, Array_mInputMethodId);//找到当前系统安装的输入法名称
switch (getCurrentLanguageConfig()) {//根据当前系统配置去切换输入法
case ConfigConstants.LANGUAGE_SIMPLIFIED_CHINESE:
// if (Array_mInputMethodId.contains("com.iflytek.inputmethod/.FlyIME")) {
if (Array_mInputMethodId.contains("com.google.android.inputmethod.pinyin/.PinyinIME")) {
Settings.Secure.putString(Utils.getApp().getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD
, "com.google.android.inputmethod.pinyin/.PinyinIME");
// , "com.iflytek.inputmethod/.FlyIME");
} else if (Array_mInputMethodId.contains("com.android.inputmethod.latin/.LatinIME")) {
Settings.Secure.putString(Utils.getApp().getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD
, "com.android.inputmethod.latin/.LatinIME");
}
break;
case ConfigConstants.LANGUAGE_BRAZILIAN_PORTUGUESE:
case ConfigConstants.LANGUAGE_FRENCH:
case ConfigConstants.LANGUAGE_SPANISH:
case ConfigConstants.LANGUAGE_RUSSIAN:
if (Array_mInputMethodId.contains("com.android.inputmethod.latin/.LatinIME")) {
Settings.Secure.putString(Utils.getApp().getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD
, "com.android.inputmethod.latin/.LatinIME");
}
break;
case ConfigConstants.LANGUAGE_DEFAULT:
default:
if (Array_mInputMethodId.contains("com.android.inputmethod.latin/.LatinIME")) {
Settings.Secure.putString(Utils.getApp().getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD
, "com.android.inputmethod.latin/.LatinIME");
}
break;
}
} catch (Exception e) {
LogUtils.eTag(TAG, e.toString());
}
}
软件盘问题算是解决了,接下来就是界面的问题了。
问:如何实现选择语言更改整个界面?
回答:
先了解Android多语言国际化适配(兼容7.0)这篇博客,写的非常详细,了解整个多语言切换的过程。接着回到我们的这个问题:需要使用Locale这个类设置系统字符串资源了(Locale是JavaSE中一个类,用以表示本地语言的类型,可用于日历、数字和字符串的本地化。 )
,根据当前设置的语言加载字符串资源(获取),同时设置配置语言,最后保存语言。
/**
* 获取当前系统配置的语言,下标与arrays中的system_language相关联
*
* @return 当前系统设置的语言,默认值0,表示英文
*/
public static int getCurrentLanguageConfig() {
return SPUtils.getInstance(ConfigConstants.SP_NAME).getInt(ConfigConstants.SP_KEY_LANGUAGE, 0);
}
/**
* 获取当前的语言配置
*
* @return 配置属性对应的locale, 默认英文
*/
public static Locale getCurrentLocale() {
switch (getCurrentLanguageConfig()) {
case ConfigConstants.LANGUAGE_DEFAULT:
return Locale.US;
case ConfigConstants.LANGUAGE_SIMPLIFIED_CHINESE:
return Locale.SIMPLIFIED_CHINESE;
case ConfigConstants.LANGUAGE_BRAZILIAN_PORTUGUESE:
return new Locale("pt", "BR");
case ConfigConstants.LANGUAGE_FRENCH:
return new Locale("fr", "FR");
case ConfigConstants.LANGUAGE_SPANISH:
return new Locale("es", "ES");
case ConfigConstants.LANGUAGE_RUSSIAN:
return new Locale("ru", "RU");
}
return Locale.US;
}
/**
* 设置系统当前保存的语言
*
* @param context 上下文
*/
public static void setSystemLanguage(Context context) {
updateLanguage(context, getLocale(getCurrentLanguageConfig()));
}
/**
* 更新并设置语言
*
* @param context 上下文
* @param locale 配置对象
*/
private static void updateLanguage(Context context, Locale locale) {
Resources resources = context.getResources();
Configuration config = resources.getConfiguration();
Locale contextLocale = config.locale;
// 切换系统语言
changeSystemLanguage(locale);
LogUtils.dTag("language", "更新语言:" + contextLocale.getDisplayName());
if (!isSameLocale(contextLocale, locale)) {
DisplayMetrics dm = resources.getDisplayMetrics();
config.setLocale(locale);
if (context instanceof Application) {
Context newContext = context.createConfigurationContext(config);
try {
Field mBaseField = ContextWrapper.class.getDeclaredField("mBase");
mBaseField.setAccessible(true);
mBaseField.set(context, newContext);
} catch (Exception var8) {
var8.printStackTrace();
}
}
resources.updateConfiguration(config, dm);
LogUtils.dTag("language", "更新后语言:" + config.locale);
}
}
private static boolean isSameLocale(Locale l0, Locale l1) {
return StringUtils.equals(l1.getLanguage(), l0.getLanguage()) && StringUtils.equals(l1.getCountry(), l0.getCountry());
}
/**
* @param locale 通过外部设置的语言从而设置系统语言,为了满足切换不同的语言时适配不同国家的键盘
* 后来发现切换了系统语言之后,可只使用Android系统自带的输入法,系统会自适配不同国家从而选择对于国家的输入法
*/
private static void changeSystemLanguage(Locale locale) {
if (locale != null) {
try {
Class classActivityManagerNative = Class.forName("android.app.ActivityManagerNative");
Method getDefault = classActivityManagerNative.getDeclaredMethod("getDefault");
Object objIActivityManager = getDefault.invoke(classActivityManagerNative);
Class classIActivityManager = Class.forName("android.app.IActivityManager");
Method getConfiguration = classIActivityManager.getDeclaredMethod("getConfiguration");
Configuration config = (Configuration) getConfiguration.invoke(objIActivityManager);
config.setLocale(locale);
//config.userSetLocale = true;
Class clzConfig = Class.forName("android.content.res.Configuration");
java.lang.reflect.Field userSetLocale = clzConfig.getField("userSetLocale");
userSetLocale.set(config, true);
Class[] clzParams = {Configuration.class};
Method updateConfiguration = classIActivityManager.getDeclaredMethod("updateConfiguration", clzParams);
updateConfiguration.invoke(objIActivityManager, config);
BackupManager.dataChanged("com.android.providers.settings");
} catch (Exception e) {
LogUtils.dTag(TAG, "changeSystemLanguage: " + e.getLocalizedMessage());
}
}
}
/**
* 保存当前选择设置的语言 /**
* 保存当前选择设置的语言
*
* @param languageConfig 需要设置的语言,0英文,1中文
*/
public static void saveSystemLanguage(int languageConfig) {
SPUtils.getInstance(ConfigConstants.SP_NAME).put(ConfigConstants.SP_KEY_LANGUAGE, languageConfig, true);
}
*
* @param languageConfig 需要设置的语言,0英文,1中文
*/
public static void saveSystemLanguage(int languageConfig) {
SPUtils.getInstance(ConfigConstants.SP_NAME).put(ConfigConstants.SP_KEY_LANGUAGE, languageConfig, true);
}
//在基类Activity中实现如下方法
@Override
public void onConfigurationChanged(Configuration newConfig) {
newConfig.setLocale(ResUtils.getCurrentLocale());
super.onConfigurationChanged(newConfig);
ResUtils.setSystemLanguage(this);
}
@Override
protected void attachBaseContext(Context newBase) {
ResUtils.setSystemLanguage(newBase);
}
针对很多细节性的东西还是不太了解,后续再更新此文。
4.导入多语言资源字符串
在此过程中遇到一个问题,对于部分欧洲国家来说。数学里面的小数点在他们国家是用“,”来表示的,切换到法语界面之后,系统会自动将小数点转化为“,”。查询原因之后发现是调用了NumberUtil.format
中没有考虑到国家区域切换从而导致的问题,最后的解决方案是统一使用Locale.ENGLISH,全部采用小数点的格式来显示数据。
我这边的解决办法是在调用NumberUtil时固定区域:Locale.ENGLISH
private static final ThreadLocal<DecimalFormat> DF_THREAD_LOCAL = new ThreadLocal<DecimalFormat>() {
protected DecimalFormat initialValue() {
return (DecimalFormat)NumberFormat.getInstance(Locale.ENGLISH);
}
};
}
5.反思???!!!
回头来想,多语言切换时,涉及到系统方面的部分,比如多语言输入法的适配,可以先从两方面考虑,尝试先利用系统改变区域看能否改变系统的输入法,如果此方法不通,可利用外部资源,如导入其他的多语言来解决因切换语言输入法不匹配的问题。
由于我对Android了解很少,所以先走了一条比较缓慢的道路才到达了终点,在此过程中,意外发现还有一条快捷的小路,走完了这一小段的快捷的道路时,在即将结束时差点摔倒进入一个小坑。
即使结束了一个小小的功能的开发,但是针对于此我还是有很多不太了解模糊的概念,如果从头开始依然不知道如何下手去写。可见我自己编程能力十分有限,掌握的十分匮乏,知道的更多,不知道的更多。
开发的过程就是这样的,会遇到各种未曾料到的问题,关键是否能够始终如一并且耐心的坚持下去和相信自己有解决问题的思维和能力。
程序人生,这条道路上会遇到各种的问题与阻碍,有些可以解决有些暂时无法解决,有时思路明确,有时一团乱麻,正如代码是人写出来一样,人生也是自己走出来的,既然可以优化代码,解决bug,那么人生,不也如此?
希望每个人,都能在自己热爱的领域中生根发芽。