欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android Language

程序员文章站 2023-01-25 08:20:06
至最新的Android P, Google已经提供了世界绝大多数语言的支持。但是对许多发往海外的项目,可能还是会有一些国家的语言google默认没有支持。下面将介绍下对于某一特定语言如何判断Google是否支持这种语言、如何添加一种语言(如果google默认没有支持)。 还有,自Android N以 ......

       至最新的Android P, Google已经提供了世界绝大多数语言的支持。但是对许多发往海外的项目,可能还是会有一些国家的语言google默认没有支持。下面将介绍下对于某一特定语言如何判断Google是否支持这种语言、如何添加一种语言(如果google默认没有支持)。 还有,自Android N以来至Android P,首次开机时系统语言不再随SIM卡自适应,即SIM卡是法国的,首次开机时系统语言却不是法国。对此,可以参考下面提供的Feature去设计。

 

一、如何判断Google是否支持某种语言

      整个过程可以分为下面几步:

      1. 先要确定这种语言对应的country code和language code(如中文简体, country code是cn, language code是zh; 美国英语,country code是us, language code是en), 确定好country code和language code之后再进行2以下步骤;

      language code可以从这个网址去查询:

      country code可以从这个网址去查询:

      2. 是否有这种语言对应语言的icu资源(前面确定好了language code和country code, 如中文简体是zh_CN)。可以看到external\icu\icu4c\source\data下coll、curr、lang、locales、region,zone这些子文件夹中都有zh.txt和zh_CN.txt.  这些目录下记载的对应的都是这种语言对应显示的region, zone, locale, language, dateformat...这些信息。

      3. 是否有这种语言对应的字库。Google已经提供了约大多数语言的字库,很多语言是共用同一字库。所以基本不需要再添加字库。如需添加,得找对应字库公司购买。

      4. 在frameworks/base/core/res/res/下是否有对应的values-xx-rYY的文件夹(xx是language code, YY是country code,下同). 即要有这种语言对应的resource, 不然语言显示的信息从何而来呢。

      5. 每个app对应的res目录下面是否有values-xx文件夹或values-xx-rYY文件夹.   app下没有这个语言对应的resource, 所带来的影响会是这个app不能显示这种语言。如果需要显示,得手动去添加对应的resource.

      6. 如果是复杂语言,还需看看是否有对应的字体引擎。 复杂语言一般有这些特性: 阅读顺序从右到左、某几个字符相邻时会发生变形或替换等。对于复杂语言,要去external\harfbuzz_ng\src\hb-old\harfbuzz-shaper.cpp中添加对应的引擎。

      如果以上几点都是YES, 那就说明这种语言已经支持。

 

二、Google不支持的语言,如何添加这种语言呢

      上面介绍了如何判断是否支持某一种语言,对于某一种不支持的语言,是不是做到满足上面几点就能支持了呢? YES!  当然,答案是肯定的。

      可以参考下面步骤:

      1. 修改编译配置文件。 一般是在alps\device\公司名字\项目名字\full_项目名字.mk  中的  PRODUCT_LOCALES宏 下添加需要添加的语言代码。在此添加的目的是为系统打开这种语言。原因是虽然Android系统支持这么多的语言,但对于绝大多数的用户来说,并不需要这么多的语言,所以一般情况下系统只需打开对应的几种语言即可。如添加中文简体,那就将zh_CN添加到PRODUCT_LOCALES下面。 如果想要系统的默认语言(即首次开机时的语言)就是zh_CN, 那可以将zh_CN添加到PRODUCT_LOCALES下的第一个位置。

      2. 添加ICU资源。 ICU资源是开源组织提供的, 可以网络搜索对应的ICU资源添加(上面判断是否支持一种语言中有介绍需要哪些ICU资源).

      3. 如缺少这种语言对应的字库,则还需要添加字库

      4. 在frameworks/base/core/res/res/下添加这种语言对应的resource. 如添加values-zh-rCN.

      5. 复杂语言需要添加对应的引擎

 

三、 首次开机随Sim卡自适应语言的feature Design

      如下所示:

 /frameworks/opt/telephony/src/java/com/android/internal/telephony/MccTable.java
174 public static void updateMccMncConfiguration(Context context, String mccmnc,
175 boolean fromServiceState) {
176 Slog.d(LOG_TAG, "updateMccMncConfiguration mccmnc='" + mccmnc + "' fromServiceState=" + fromServiceState);
177
178 if (Build.IS_DEBUGGABLE) {
179 String overrideMcc = SystemProperties.get("persist.sys.override_mcc");
180 if (!TextUtils.isEmpty(overrideMcc)) {
181 mccmnc = overrideMcc;
182 Slog.d(LOG_TAG, "updateMccMncConfiguration overriding mccmnc='" + mccmnc + "'");
183 }
184 }
185
186 if (!TextUtils.isEmpty(mccmnc)) {
187 int mcc, mnc;
188
189 String defaultMccMnc = TelephonyManager.getDefault().getSimOperatorNumeric();
190 Slog.d(LOG_TAG, "updateMccMncConfiguration defaultMccMnc=" + defaultMccMnc);
191 //Update mccmnc only for default subscription in case of MultiSim.
192// if (!defaultMccMnc.equals(mccmnc)) {
193// Slog.d(LOG_TAG, "Not a Default subscription, ignoring mccmnc config update.");
194// return;
195// }
196
197 try {
198 mcc = Integer.parseInt(mccmnc.substring(0,3));
199 mnc = Integer.parseInt(mccmnc.substring(3));
200 } catch (NumberFormatException e) {
201 Slog.e(LOG_TAG, "Error parsing IMSI: " + mccmnc);
202 return;
203 }
204
205 Slog.d(LOG_TAG, "updateMccMncConfiguration: mcc=" + mcc + ", mnc=" + mnc);
Locale mccLocale = null; //添加这行
206 if (mcc != 0) {
207 setTimezoneFromMccIfNeeded(context, mcc);
mccLocale = getLocaleFromMcc(context, mcc); //添加这行
208 }
209 if (fromServiceState) {
210 setWifiCountryCodeFromMcc(context, mcc);
211 } else {
212 // from SIM
213 try {
214 Configuration config = new Configuration();
215 boolean updateConfig = false;
216 if (mcc != 0) {
217 config.mcc = mcc;
218 config.mnc = mnc == 0 ? Configuration.MNC_ZERO : mnc;
219 updateConfig = true;
220 }
221
if (mccLocale != null) { //添加这行
Configuration conLocale = new Configuration(); //添加这行
conLocale = ActivityManagerNative.getDefault().getConfiguration(); //添加这行
LocaleList userLocale = conLocale.getLocales(); //添加这行
LocaleList newUserLocale = new LocaleList(mccLocale,userLocale); //添加这行
config.setLocales(newUserLocale); //添加这行
updateConfig = true; //添加这行
} //添加这行

222 if (updateConfig) {
223 Slog.d(LOG_TAG, "updateMccMncConfiguration updateConfig config=" + config);
224 ActivityManagerNative.getDefault().updateConfiguration(config);
225 } else {
226 Slog.d(LOG_TAG, "updateMccMncConfiguration nothing to update");
227 }
228 } catch (RemoteException e) {
229 Slog.e(LOG_TAG, "Can't update configuration", e);
230 }
231 }
232 } else {
233 if (fromServiceState) {
234 // an empty mccmnc means no signal - tell wifi we don't know
235 setWifiCountryCodeFromMcc(context, 0);
236 }
237 }
238 }


//添加下面函数
245 private static boolean canUpdateLocale(Context context) {
246 return !(userHasPersistedLocale() || isDeviceProvisioned(context));
247 }
248
249 private static boolean userHasPersistedLocale() {
250 String persistSysLanguage = SystemProperties.get("persist.sys.locale", "");
251 String persistSysCountry = SystemProperties.get("persist.sys.country", "");
252 return !(persistSysLanguage.isEmpty() && persistSysCountry.isEmpty());
253 }
254
255 private static boolean isDeviceProvisioned(Context context) {
256 try {
257 return Settings.Global.getInt(
258 context.getContentResolver(), Settings.Global.DEVICE_PROVISIONED) != 0;
259 } catch (Settings.SettingNotFoundException e) {
260 return false;
261 }
262 }


284 private static Locale getLocaleForLanguageCountry(Context context, String language,
285 String country) {
286 if (language == null) {
287 Slog.d(LOG_TAG, "getLocaleForLanguageCountry: skipping no language");
288 return null; // no match possible
289 }
290 if (country == null) {
291 country = ""; // The Locale constructor throws if passed null.
292 }

if(!canUpdateLocale()){ //添加这行
return null; //添加这行
} //添加这行
293
294 final Locale target = new Locale(language, country);
295 try {
296 String[] localeArray = context.getAssets().getLocales();
297 List<String> locales = new ArrayList<>(Arrays.asList(localeArray));
298
299 // Even in developer mode, you don't want the pseudolocales.
300 locales.remove("ar-XB");
301 locales.remove("en-XA");
302
303 List<Locale> languageMatches = new ArrayList<>();
304 for (String locale : locales) {
305 final Locale l = Locale.forLanguageTag(locale.replace('_', '-'));
306
307 // Only consider locales with both language and country.
308 if (l == null || "und".equals(l.getLanguage()) ||
309 l.getLanguage().isEmpty() || l.getCountry().isEmpty()) {
310 continue;
311 }
312 if (l.getLanguage().equals(target.getLanguage())) {
313 // If we got a perfect match, we're done.
314 if (l.getCountry().equals(target.getCountry())) {
315 Slog.d(LOG_TAG, "getLocaleForLanguageCountry: got perfect match: " +
316 l.toLanguageTag());
317 return l;
318 }
319
320 // We've only matched the language, not the country.
321 languageMatches.add(l);
322 }
323 }

325 Locale bestMatch = chooseBestFallback(target, languageMatches);
326 if (bestMatch != null) {
327 Slog.d(LOG_TAG, "getLocaleForLanguageCountry: got a language-only match: " +
328 bestMatch.toLanguageTag());
329 return bestMatch;
330 } else {
331 Slog.d(LOG_TAG, "getLocaleForLanguageCountry: no locales for language " +
332 language);
333 }
334 } catch (Exception e) {
335 Slog.d(LOG_TAG, "getLocaleForLanguageCountry: exception", e);
336 }
337
338 return null;
339 }
340

/frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

19627 private boolean updateConfigurationLocked(Configuration values,
19628 ActivityRecord starting, boolean initLocale, boolean persistent, int userId) {
...
19643 if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) { //删除这行
if (!initLocale && !values.getLocales().isEmpty()) { //添加这行
19644 final LocaleList locales = values.getLocales();
19645 int bestLocaleIndex = 0;
19646 if (locales.size() > 1) {
19647 if (mSupportedSystemLocales == null) {
19648 mSupportedSystemLocales =
19649 Resources.getSystem().getAssets().getLocales();
19650 }
19651 bestLocaleIndex = Math.max(0,
19652 locales.getFirstMatchIndex(mSupportedSystemLocales));
19653 }
if(values.userSetLocale){
SystemProperties.set("persist.sys.locale", //添加这行
locales.get(bestLocaleIndex).toLanguageTag()); //添加这行
}else{ //添加这行
SystemProperties.set("persist.sys.simLocale", //添加这行
locales.get(bestLocaleIndex).toLanguageTag()); //添加这行
} //添加这行
19654 SystemProperties.set("persist.sys.locale", //删除这行
19655 locales.get(bestLocaleIndex).toLanguageTag()); //删除这行
19656 LocaleList.setDefault(locales, bestLocaleIndex);
19657 mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG,
19658 locales.get(bestLocaleIndex)));
19659 }
...
}


/frameworks/base/core/jni/AndroidRuntime.cpp 
414const std::string readLocale()
415{
416 const std::string locale = getProperty("persist.sys.locale", "");
417 if (!locale.empty()) {
418 return locale;
419 }
420
421 const std::string language = getProperty("persist.sys.language", "");
422 if (!language.empty()) {
423 const std::string country = getProperty("persist.sys.country", "");
424 const std::string variant = getProperty("persist.sys.localevar", "");
425
426 std::string out = language;
427 if (!country.empty()) {
428 out = out + "-" + country;
429 }
430
431 if (!variant.empty()) {
432 out = out + "-" + variant;
433 }
434
435 return out;
436 }
437
const std::string simLocale = getProperty("persist.sys.simLocale", ""); //添加这行
if (!simLocale.empty()) { //添加这行
return simLocale; //添加这行
} //添加这行

438 const std::string productLocale = getProperty("ro.product.locale", "");
439 if (!productLocale.empty()) {
440 return productLocale;
441 }
442
443 // If persist.sys.locale and ro.product.locale are missing,
444 // construct a locale value from the individual locale components.
445 const std::string productLanguage = getProperty("ro.product.locale.language", "en");
446 const std::string productRegion = getProperty("ro.product.locale.region", "US");
447
448 return productLanguage + "-" + productRegion;
449}