赞
踩
首先是语言和输入的设置界面
src/com/android/settings/language/LanguageAndInputSettings.java
......
// 注释1_1:加载了language_and_input 这个布局文件
@Override
protected int getPreferenceScreenResId() {
return R.xml.language_and_input;
}
......
然后看下language_and_input.xml这个布局文件
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/language_settings"> <!-- 注释1_2: 这里的 phone_language 即对应着 界面上的语言选项 下面的 keyboards_category 则对应着键盘的设置选项 注意:这里的 fragment 属性设置的是com.android.settings.localepicker.LocaleListEditor 即我们点开语言选项,即由这个页面来实现--> <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
...... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); // 注释1_4: 这里是已经添加列表数据的初始化,获取和填充 这里的 LocaleStore getUserLocaleList() // 所涉及的 LocaleList LocalePiker 都是frameworks层的实现,后面会说到 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); //注释1_4: 这里加载了布局 locale_order_list.xml 比较简单 一个列表 和 一个添加按钮 final View myLayout = inflater.inflate(R.layout.locale_order_list, (ViewGroup) result); configureDragAndDrop(myLayout); return result; } ...... // 注释1_5: 这里的方法,对应上面注释1_4。即已添加的语言列表数据 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); // 注释1_6: 这里是添加按钮的监听 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
...... 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); // 注释1_7: 前面都是做准备工作,这里调用这个做更实质的处理 updateLocalesWhenAnimationStops(ll); } private LocaleList mLocalesToSetNext = null; private LocaleList mLocalesSetLast = null; public void updateLocalesWhenAnimationStops(final LocaleList localeList) { if (localeList.equals(mLocalesToSetNext)) { return; } // This will only update the Settings application to make things feel more responsive, // the system will be updated later, when animation stopped. 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)) { // All animations finished, but the locale list did not change return; } // 注释1_8: 当已添加的语言数目发生了改变,则调用frameworks 层的 // LocalePicker的 updateLocales 方法处理,具体后面看 LocalePicker.updateLocales(mLocalesToSetNext); mLocalesSetLast = mLocalesToSetNext; new ShortcutsUpdateTask(mContext).execute(); mLocalesToSetNext = null; mNumberFormatter = NumberFormat.getNumberInstance(Locale.getDefault()); } }); }
初看可能有点疑惑,这个doTheUpdate方法 在哪里调用和触发的呢?
LocaleRecyclerView.java
@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) {
// 注释1_9: 这里一目了然,当列表的触摸事件手指离开的时候,便会触发这个更新
adapter.doTheUpdate();
}
}
return super.onTouchEvent(e);
}
关于Settings 切换已选语言的处理流程基本就这些了,然后说下添加的流程。
前面的介绍在 注释1_6 处,LocaleListEditor.java的添加语言的按钮,点击监听事件里是流程的入口,会跳转
src/com/android/settings/localepicker/LocalePickerWithRegionActivity.java
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActionBar().setDisplayHomeAsUpEnabled(true);
// 注释1_10: 这里的页面以及逻辑实现都交给了 LocalePickerWithRegion ,
// 这个类的实现也是在frameworks 层
final LocalePickerWithRegion selector = LocalePickerWithRegion.createLanguagePicker(
this, LocalePickerWithRegionActivity.this, false /* translate only */);
getFragmentManager()
.beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.replace(android.R.id.content, selector)
.addToBackStack(PARENT_FRAGMENT_NAME)
.commit();
}
所以,添加的流程,Settings 的添加流程到这里也就结束了
由前面分析可知:切换语言Settings 处理跳转到 framework 的入口是LocalePicker的updateLocales方法
base/core/java/com/android/internal/app/LocalePicker.java
...... /** * Requests the system to update the list of system locales. * Note that the system looks halted for a while during the Locale migration, * so the caller need to take care of it. */ @UnsupportedAppUsage public static void updateLocales(LocaleList locales) { if (locales != null) { locales = removeExcludedLocales(locales); } // Note: the empty list case is covered by Configuration.setLocales(). try { final IActivityManager am = ActivityManager.getService(); final Configuration config = am.getConfiguration(); // 注释2_1:这里对切换语言后的数据封装到Configuration里,用于后面流程处理 config.setLocales(locales); config.userSetLocale = true; // 注释2_2: 这里通过ActivityManager的一个Binder服务,调用 // updatePersistentConfigurationWithAttribution 继续处理 am.updatePersistentConfigurationWithAttribution(config, ActivityThread.currentOpPackageName(), null); // Trigger the dirty bit for the Settings Provider. BackupManager.dataChanged("com.android.providers.settings"); } catch (RemoteException e) { // Intentionally left blank } } ......
这里是通过 Binder 获取 ActivityManager 的一个服务代理对象,来处理 实现方法是 updatePersistentConfigurationWithAttribution
这里ActivityManager的Binder 实际处理对象是:
base/services/core/java/com/android/server/am/ActivityManagerService.java
@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(); // 注释2_3:可以看到,这里流程将处理方法又传递给了 mActivityTaskManager 实例的updatePersistentConfiguration 方法 mActivityTaskManager.updatePersistentConfiguration(values, userId); }
上面的mActivityTaskManager的实例即 ActivityTaskManagerService.java,来看看内部实现
base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
通过内部层层调用,以及一些判断条件的筛选,最后会执行到(方法内部处理逻辑很多,挑重点看下):
/** Update default (global) configuration and notify listeners about changes. */ int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale, boolean persistent, int userId) { ...... // 注释2_4:这里对切换语言后封装的Configuration做后续处理的预检查和判断 if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) { final LocaleList locales = values.getLocales(); int bestLocaleIndex = 0; if (locales.size() > 1) { if (mSupportedSystemLocales == null) { // 注释2_5:这里是获取系统资源配置支持的语言,也就是说实际能真的支持的语言 mSupportedSystemLocales = Resources.getSystem().getAssets().getLocales(); } // 注释2_6:这里是通过方法计算得到在所有支持语言列表里 最匹配的语言的所在列表的 索引 // 具体算法可getFirstMatchIndex 一路点进去看 bestLocaleIndex = Math.max(0, locales.getFirstMatchIndex(mSupportedSystemLocales)); } // 注释2_7:这里是修改系统属性值,即 当前系统的默认语言 SystemProperties.set("persist.sys.locale", locales.get(bestLocaleIndex).toLanguageTag()); LocaleList.setDefault(locales, bestLocaleIndex); // 注释2_8:这里是将切换语言的动作通过 handler - message 的形式分发出去,以通知系统各个地方刷新 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
...... @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 { // 注释2_9:这里是语言列表的点击事件,即代表这开启了被点击语言的添加流程 // 这里是 用到本类的 createCountryPicker 方法 LocalePickerWithRegion selector = LocalePickerWithRegion.createCountryPicker( getContext(), mListener, locale, mTranslatedOnly /* translate only */); if (selector != null) { getFragmentManager().beginTransaction() .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .replace(getId(), selector).addToBackStack(null) .commit(); } else { returnToParentFrame(); } } } ...... // 注释2_10_1:看上一步,调用的是这个4个参数的方法 private static LocalePickerWithRegion createCountryPicker(Context context, LocaleSelectedListener listener, LocaleStore.LocaleInfo parent, boolean translatedOnly) { LocalePickerWithRegion localePicker = new LocalePickerWithRegion(); // 注释2_10_2:重要是这一步,调用了localePicker.setListener,localePicker是LocalePickerWithRegion // 实例化的对象,于是将流程传递给了本类的setListener方法 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, /* parent */ 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); // 注释2_11_1:这部分的逻辑是 获取已经添加的语言列表,作为需要忽略的部分,毕竟已经添加的,还能再添加就不合理了 final HashSet<String> langTagsToIgnore = new HashSet<>(); if (!translatedOnly) { final LocaleList userLocales = LocalePicker.getLocales(); final String[] langTags = userLocales.toLanguageTags().split(","); Collections.addAll(langTagsToIgnore, langTags); } // 注释2_11_2:这里做了选择的语言不为空的判断后,就将逻辑流程 传递给了 LocaleStore.java // 的getLevelLocales方法,从而获得一个新的已添加语言列表 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 /* no parent */, translatedOnly); } return true;
由于上面的添加流程,已经走完,转向了LocaleStore.java 这里,那就看看
base/core/java/com/android/internal/app/LocaleStore.java
...... @UnsupportedAppUsage public static Set<LocaleInfo> getLevelLocales(Context context, Set<String> ignorables, LocaleInfo parent, boolean translatedOnly) { // 注释2_12_1:这个方法很关键,后面逻辑处理保存的数据,需要这里先做预处理的 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) { // region selection if (parentId.equals(li.getParent().toLanguageTag())) { result.add(li); } } else { // language selection if (li.isSuggestionOfType(LocaleInfo.SUGGESTION_TYPE_SIM)) { result.add(li); } else { result.add(getLocaleInfo(li.getParent())); } } } } return result; } ......// fillCache方法内部逻辑不少,只看下核心部分 @UnsupportedAppUsage public static void fillCache(Context context) { if (sFullyInitialized) { return; } ...... // 注释2_12_2:LocalePicker.getSupportedLocales(context) 这个方法很关键 ,他是整个数据处理的来源 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 { // Do not display pseudolocales unless in development mode. continue; } } if (simCountries.contains(li.getLocale().getCountry())) { li.mSuggestionFlags |= LocaleInfo.SUGGESTION_TYPE_SIM; } // 注释2_12_3: 这也是2_12_1处后续逻辑处理 sLocaleCache的加载的地方 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
...... // 注释2_13_1:这个方法也很关键,这是获取系统资源配置支持的语言列表 public static String[] getSystemAssetLocales() { return Resources.getSystem().getAssets().getLocales(); } public static String[] getSupportedLocales(Context context) { // 注释2_13:好了,找到这里,似乎真相大白了,所有的语言列表,都是从这个supported_locales 资源数组获取的 // 资源位置:base/core/res/res/values/locale_config.xml 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]); } ......
好了分析到这里,加载的流程也基本结束了。
修改 base/core/java/com/android/internal/app/LocalePicker.java 的 getSupportedLocales 方法
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); } } */ 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; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。