Settings 部分 切换流程 首先是语言和输入的设置界面src/com/android/settings/language/LanguageAndInputSettings.java
1 2 3 4 5 6 7 8 ...... @Override protected int getPreferenceScreenResId () { return R.xml.language_and_input; } ......
然后看下language_and_input.xml 这个布局文件
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 <PreferenceScreen xmlns:android ="http://schemas.android.com/apk/res/android" xmlns:settings ="http://schemas.android.com/apk/res-auto" android:title ="@string/language_settings" > <Preference android:key ="phone_language" android:title ="@string/phone_language" android:icon ="@drawable/ic_translate_24dp" android:fragment ="com.android.settings.localepicker.LocaleListEditor" /> <PreferenceCategory android:key ="keyboards_category" android:title ="@string/keyboard_and_input_methods_category" > <Preference android:key ="virtual_keyboard_pref" android:title ="@string/virtual_keyboard_category" android:fragment ="com.android.settings.inputmethod.VirtualKeyboardFragment" settings:keywords ="@string/keywords_virtual_keyboard" /> <Preference android:key ="physical_keyboard_pref" android:title ="@string/physical_keyboard_title" android:summary ="@string/summary_placeholder" android:fragment ="com.android.settings.inputmethod.PhysicalKeyboardFragment" /> </PreferenceCategory > ......
另外,Settings里的界面基本都是Preference(界面xml) 和 xxxController(数据逻辑管理) ,语言的controller 是 PhoneLanguagePreferenceController ,具体这里不再详细展开。
多语言的切换和添加页面:src/com/android/settings/localepicker/LocaleListEditor.java
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 ...... @Override public void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setHasOptionsMenu(true ); LocaleStore.fillCache(this .getContext()); final List<LocaleStore.LocaleInfo> feedsList = getUserLocaleList(); mAdapter = new LocaleDragAndDropAdapter (this .getContext(), feedsList); } @Override public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstState) { final View result = super .onCreateView(inflater, container, savedInstState); final View myLayout = inflater.inflate(R.layout.locale_order_list, (ViewGroup) result); configureDragAndDrop(myLayout); return result; } ...... private List<LocaleStore.LocaleInfo> getUserLocaleList() { final List<LocaleStore.LocaleInfo> result = new ArrayList <>(); final LocaleList localeList = LocalePicker.getLocales(); for (int i = 0 ; i < localeList.size(); i++) { Locale locale = localeList.get(i); result.add(LocaleStore.getLocaleInfo(locale)); } return result; } private void configureDragAndDrop (View view) { final RecyclerView list = view.findViewById(R.id.dragList); final LocaleLinearLayoutManager llm = new LocaleLinearLayoutManager (getContext(), mAdapter); llm.setAutoMeasureEnabled(true ); list.setLayoutManager(llm); list.setHasFixedSize(true ); mAdapter.setRecyclerView(list); list.setAdapter(mAdapter); mAddLanguage = view.findViewById(R.id.add_language); mAddLanguage.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider() .logSettingsTileClick(INDEX_KEY_ADD_LANGUAGE, getMetricsCategory()); final Intent intent = new Intent (getActivity(), LocalePickerWithRegionActivity.class); startActivityForResult(intent, REQUEST_LOCALE_PICKER); } }); ......
已添加语言的由前面分析可以知道,布局locale_order_list.xml 里面是由
自定义RecyclerView列表:src/com/android/settings/localepicker/LocaleRecyclerView.java
数据适配器:src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
来组合实现的,先看下
LocaleDragAndDropAdapter.java
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 ...... public void doTheUpdate () { int count = mFeedItemList.size(); final Locale[] newList = new Locale [count]; for (int i = 0 ; i < count; i++) { final LocaleStore.LocaleInfo li = mFeedItemList.get(i); newList[i] = li.getLocale(); } final LocaleList ll = new LocaleList (newList); updateLocalesWhenAnimationStops(ll); } private LocaleList mLocalesToSetNext = null ; private LocaleList mLocalesSetLast = null ; public void updateLocalesWhenAnimationStops (final LocaleList localeList) { if (localeList.equals(mLocalesToSetNext)) { return ; } LocaleList.setDefault(localeList); mLocalesToSetNext = localeList; final RecyclerView.ItemAnimator itemAnimator = mParentView.getItemAnimator(); itemAnimator.isRunning(new RecyclerView .ItemAnimator.ItemAnimatorFinishedListener() { @Override public void onAnimationsFinished () { if (mLocalesToSetNext == null || mLocalesToSetNext.equals(mLocalesSetLast)) { return ; } LocalePicker.updateLocales(mLocalesToSetNext); mLocalesSetLast = mLocalesToSetNext; new ShortcutsUpdateTask (mContext).execute(); mLocalesToSetNext = null ; mNumberFormatter = NumberFormat.getNumberInstance(Locale.getDefault()); } }); }
初看可能有点疑惑,这个doTheUpdate 方法 在哪里调用和触发的呢?
LocaleRecyclerView.java
1 2 3 4 5 6 7 8 9 10 11 @Override public boolean onTouchEvent (MotionEvent e) { if (e.getAction() == MotionEvent.ACTION_UP || e.getAction() == MotionEvent.ACTION_CANCEL) { LocaleDragAndDropAdapter adapter = (LocaleDragAndDropAdapter) this .getAdapter(); if (adapter != null ) { adapter.doTheUpdate(); } } return super .onTouchEvent(e); }
关于Settings 切换已选语言的处理流程基本就这些了,然后说下添加的流程。
添加流程 前面的介绍在 注释1_6 处,LocaleListEditor.java 的添加语言的按钮,点击监听事件里是流程的入口,会跳转
src/com/android/settings/localepicker/LocalePickerWithRegionActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Override public void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); getActionBar().setDisplayHomeAsUpEnabled(true ); final LocalePickerWithRegion selector = LocalePickerWithRegion.createLanguagePicker( this , LocalePickerWithRegionActivity.this , false ); getFragmentManager() .beginTransaction() .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .replace(android.R.id.content, selector) .addToBackStack(PARENT_FRAGMENT_NAME) .commit(); }
所以,添加的流程,Settings 的添加流程到这里也就结束了
Frameworks 部分 切换流程 由前面分析可知:切换语言Settings 处理跳转到 framework 的入口是LocalePicker 的updateLocales 方法
base/core/java/com/android/internal/app/LocalePicker.java
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 ...... @UnsupportedAppUsage public static void updateLocales (LocaleList locales) { if (locales != null ) { locales = removeExcludedLocales(locales); } try { final IActivityManager am = ActivityManager.getService(); final Configuration config = am.getConfiguration(); config.setLocales(locales); config.userSetLocale = true ; am.updatePersistentConfigurationWithAttribution(config, ActivityThread.currentOpPackageName(), null ); BackupManager.dataChanged("com.android.providers.settings" ); } catch (RemoteException e) { } } ......
这里是通过 Binder 获取 ActivityManager 的一个服务代理对象,来处理 实现方法是 updatePersistentConfigurationWithAttribution 这里ActivityManager的Binder 实际处理对象是:base/services/core/java/com/android/server/am/ActivityManagerService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Override public void updatePersistentConfiguration (Configuration values) { updatePersistentConfigurationWithAttribution(values, Settings.getPackageNameForUid(mContext, Binder.getCallingUid()), null ); } @Override public void updatePersistentConfigurationWithAttribution (Configuration values, String callingPackage, String callingAttributionTag) { enforceCallingPermission(CHANGE_CONFIGURATION, "updatePersistentConfiguration()" ); enforceWriteSettingsPermission("updatePersistentConfiguration()" , callingPackage, callingAttributionTag); if (values == null ) { throw new NullPointerException ("Configuration must not be null" ); } int userId = UserHandle.getCallingUserId(); mActivityTaskManager.updatePersistentConfiguration(values, userId); }
上面的mActivityTaskManager的实例即 ActivityTaskManagerService.java ,来看看内部实现
base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java 通过内部层层调用,以及一些判断条件的筛选,最后会执行到(方法内部处理逻辑很多,挑重点看下):
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 int updateGlobalConfigurationLocked (@NonNull Configuration values, boolean initLocale, boolean persistent, int userId) { ...... if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) { final LocaleList locales = values.getLocales(); int bestLocaleIndex = 0 ; if (locales.size() > 1 ) { if (mSupportedSystemLocales == null ) { mSupportedSystemLocales = Resources.getSystem().getAssets().getLocales(); } bestLocaleIndex = Math.max(0 , locales.getFirstMatchIndex(mSupportedSystemLocales)); } SystemProperties.set("persist.sys.locale" , locales.get(bestLocaleIndex).toLanguageTag()); LocaleList.setDefault(locales, bestLocaleIndex); final Message m = PooledLambda.obtainMessage( ActivityTaskManagerService::sendLocaleToMountDaemonMsg, this , locales.get(bestLocaleIndex)); mH.sendMessage(m); } ...... }
切换语言的流程,简单的流程就到这里,其余就不在详细展开了
添加流程 由前面分析可知,添加流程由 Settings 的 LocalePickerWithRegionActivity.java 到 frameworks 的 LocalePickerWithRegion.java base/core/java/com/android/internal/app/LocalePickerWithRegion.java
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 ...... @Override public void onListItemClick (ListView l, View v, int position, long id) { final LocaleStore.LocaleInfo locale = (LocaleStore.LocaleInfo) getListAdapter().getItem(position); if (locale.getParent() != null ) { if (mListener != null ) { mListener.onLocaleSelected(locale); } returnToParentFrame(); } else { LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker( getContext(), mListener, locale, mTranslatedOnly ); if (selector != null ) { getFragmentManager().beginTransaction() .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .replace(getId(), selector).addToBackStack(null ) .commit(); } else { returnToParentFrame(); } } } ...... private static LocalePickerWithRegion createCountryPicker (Context context, LocaleSelectedListener listener, LocaleStore.LocaleInfo parent, boolean translatedOnly) { LocalePickerWithRegion localePicker = new LocalePickerWithRegion (); boolean shouldShowTheList = localePicker.setListener(context, listener, parent, translatedOnly); return shouldShowTheList ? localePicker : null ; } public static LocalePickerWithRegion createLanguagePicker (Context context, LocaleSelectedListener listener, boolean translatedOnly) { LocalePickerWithRegion localePicker = new LocalePickerWithRegion (); localePicker.setListener(context, listener, null , translatedOnly); return localePicker; } ...... private boolean setListener (Context context, LocaleSelectedListener listener, LocaleStore.LocaleInfo parent, boolean translatedOnly) { this .mParentLocale = parent; this .mListener = listener; this .mTranslatedOnly = translatedOnly; setRetainInstance(true ); final HashSet<String> langTagsToIgnore = new HashSet <>(); if (!translatedOnly) { final LocaleList userLocales = LocalePicker.getLocales(); final String[] langTags = userLocales.toLanguageTags().split("," ); Collections.addAll(langTagsToIgnore, langTags); } if (parent != null ) { mLocaleList = LocaleStore.getLevelLocales(context, langTagsToIgnore, parent, translatedOnly); if (mLocaleList.size() <= 1 ) { if (listener != null && (mLocaleList.size() == 1 )) { listener.onLocaleSelected(mLocaleList.iterator().next()); } return false ; } } else { mLocaleList = LocaleStore.getLevelLocales(context, langTagsToIgnore, null , translatedOnly); } return true ;
由于上面的添加流程,已经走完,转向了LocaleStore.java 这里,那就看看
base/core/java/com/android/internal/app/LocaleStore.java
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 ...... @UnsupportedAppUsage public static Set<LocaleInfo> getLevelLocales (Context context, Set<String> ignorables, LocaleInfo parent, boolean translatedOnly) { fillCache(context); String parentId = parent == null ? null : parent.getId(); HashSet<LocaleInfo> result = new HashSet <>(); for (LocaleStore.LocaleInfo li : sLocaleCache.values()) { int level = getLevel(ignorables, li, translatedOnly); if (level == 2 ) { if (parent != null ) { if (parentId.equals(li.getParent().toLanguageTag())) { result.add(li); } } else { if (li.isSuggestionOfType(LocaleInfo.SUGGESTION_TYPE_SIM)) { result.add(li); } else { result.add(getLocaleInfo(li.getParent())); } } } } return result; } ...... @UnsupportedAppUsage public static void fillCache (Context context) { if (sFullyInitialized) { return ; } ...... for (String localeId : LocalePicker.getSupportedLocales(context)) { if (localeId.isEmpty()) { throw new IllformedLocaleException ("Bad locale entry in locale_config.xml" ); } LocaleInfo li = new LocaleInfo (localeId); if (LocaleList.isPseudoLocale(li.getLocale())) { if (isInDeveloperMode) { li.setTranslated(true ); li.mIsPseudo = true ; li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM; } else { continue ; } } if (simCountries.contains(li.getLocale().getCountry())) { li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM; } sLocaleCache.put(li.getId(), li); final Locale parent = li.getParent(); if (parent != null ) { String parentId = parent.toLanguageTag(); if (!sLocaleCache.containsKey(parentId)) { sLocaleCache.put(parentId, new LocaleInfo (parent)); } } } ...... }
看到上一步,其实数据源来自LocalePicker.getSupportedLocales(context),那就看看base/core/java/com/android/internal/app/LocalePicker.java
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 ...... public static String[] getSystemAssetLocales() { return Resources.getSystem().getAssets().getLocales(); } public static String[] getSupportedLocales(Context context) { String[] allLocales = context.getResources().getStringArray(R.array.supported_locales); Predicate<String> localeFilter = getLocaleFilter(); if (localeFilter == null ) { return allLocales; } List<String> result = new ArrayList <>(allLocales.length); for (String locale : allLocales) { if (localeFilter.test(locale)) { result.add(locale); } } int localeCount = result.size(); return (localeCount == allLocales.length) ? allLocales : result.toArray(new String [localeCount]); } ......
好了分析到这里,加载的流程也基本结束了。
最后总结
R.array.supported_locales 获取到的语言列表 是config 里的xml 配置,如果要对系统做语言支持上的变动,可以改这里
Resources.getSystem().getAssets().getLocales()才是系统真正可以支持的语言列表,ActivityTaskManagerService.java的updateGlobalConfigurationLocked 方法里判断选择的语言是不是可用,也是用的这个资源作为判断依据
R.array.supported_locales里的语言配置,可能Resources.getSystem().getAssets().getLocales()不能全部支持,这就会导致我们添加了某个语言,切换到它,但是却没效果,所以有个更好的优化方案这边分享一个:
修改 base/core/java/com/android/internal/app/LocalePicker.java 的 getSupportedLocales 方法
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 public static String[] getSupportedLocales(Context context) { String[] allLocales = context.getResources().getStringArray(R.array.supported_locales); Predicate<String> localeFilter = getLocaleFilter(); List<String> result = new ArrayList <>(allLocales.length); String[] sysAssetLocales = getSystemAssetLocales(); for (String locale : allLocales){ if (!isCongenericLocales(sysAssetLocales,locale)){ continue ; } if (localeFilter == null ){ result.add(locale); }else { if (localeFilter.test(locale)) { result.add(locale); } } } int localeCount = result.size(); return (localeCount == allLocales.length) ? allLocales : result.toArray(new String [localeCount]); } private static boolean isCongenericLocales (String[] sysAssetLocales, String xmlLocales) { boolean result = false ; try { if (xmlLocales !=null ){ String[] xmlPartHead = xmlLocales.split("-" ,2 ); for (String assetLocales : sysAssetLocales){ String[] sysPartHead = assetLocales.split("-" ,2 ); if (xmlPartHead[0 ].equals(sysPartHead[0 ])){ result = true ; break ; } } } }catch (Exception e){ Log.e(TAG, "Failed to deal sysAssetLocales and xmlLocales compare!" , e); } return result; }
本文链接: http://longzhiye.top/2023/04/22/2023-04-22/