Settings 部分
切換流程
首先是語言和輸入的設(shè)置界面
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 即對應(yīng)著 界面上的語言選項(xiàng)
下面的 keyboards_category 則對應(yīng)著鍵盤的設(shè)置選項(xiàng)
注意:這里的 fragment 屬性設(shè)置的是com.android.settings.localepicker.LocaleListEditor
即我們點(diǎn)開語言選項(xiàng),即由這個頁面來實(shí)現(xiàn)-->
<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(數(shù)據(jù)邏輯管理) ,語言的controller 是 PhoneLanguagePreferenceController ,具體這里不再詳細(xì)展開。
多語言的切換和添加頁面:
src/com/android/settings/localepicker/LocaleListEditor.java
......
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
// 注釋1_4: 這里是已經(jīng)添加列表數(shù)據(jù)的初始化,獲取和填充 這里的 LocaleStore getUserLocaleList()
// 所涉及的 LocaleList LocalePiker 都是frameworks層的實(shí)現(xiàn),后面會說到
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: 這里的方法,對應(yīng)上面注釋1_4。即已添加的語言列表數(shù)據(jù)
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: 這里是添加按鈕的監(jiān)聽
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
數(shù)據(jù)適配器:src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java
來組合實(shí)現(xiàn)的,先看下
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: 前面都是做準(zhǔn)備工作,這里調(diào)用這個做更實(shí)質(zhì)的處理
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: 當(dāng)已添加的語言數(shù)目發(fā)生了改變,則調(diào)用frameworks 層的
// LocalePicker的 updateLocales 方法處理,具體后面看
LocalePicker.updateLocales(mLocalesToSetNext);
mLocalesSetLast = mLocalesToSetNext;
new ShortcutsUpdateTask(mContext).execute();
mLocalesToSetNext = null;
mNumberFormatter = NumberFormat.getNumberInstance(Locale.getDefault());
}
});
}
初看可能有點(diǎn)疑惑,這個doTheUpdate方法 在哪里調(diào)用和觸發(fā)的呢?
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: 這里一目了然,當(dāng)列表的觸摸事件手指離開的時候,便會觸發(fā)這個更新
adapter.doTheUpdate();
}
}
return super.onTouchEvent(e);
}
關(guān)于Settings 切換已選語言的處理流程基本就這些了,然后說下添加的流程。
添加流程
前面的介紹在 注釋1_6 處,LocaleListEditor.java的添加語言的按鈕,點(diǎn)擊監(jiān)聽事件里是流程的入口,會跳轉(zhuǎn)
src/com/android/settings/localepicker/LocalePickerWithRegionActivity.java
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getActionBar().setDisplayHomeAsUpEnabled(true);
// 注釋1_10: 這里的頁面以及邏輯實(shí)現(xiàn)都交給了 LocalePickerWithRegion ,
// 這個類的實(shí)現(xiàn)也是在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 的添加流程到這里也就結(jié)束了
Frameworks 部分
切換流程
由前面分析可知:切換語言Settings 處理跳轉(zhuǎn)到 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:這里對切換語言后的數(shù)據(jù)封裝到Configuration里,用于后面流程處理
config.setLocales(locales);
config.userSetLocale = true;
// 注釋2_2: 這里通過ActivityManager的一個Binder服務(wù),調(diào)用
// updatePersistentConfigurationWithAttribution 繼續(xù)處理
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 的一個服務(wù)代理對象,來處理 實(shí)現(xiàn)方法是 updatePersistentConfigurationWithAttribution
這里ActivityManager的Binder 實(shí)際處理對象是:
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 實(shí)例的updatePersistentConfiguration 方法
mActivityTaskManager.updatePersistentConfiguration(values, userId);
}
上面的mActivityTaskManager的實(shí)例即 ActivityTaskManagerService.java,來看看內(nèi)部實(shí)現(xiàn)
base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
通過內(nèi)部層層調(diào)用,以及一些判斷條件的篩選,最后會執(zhí)行到(方法內(nèi)部處理邏輯很多,挑重點(diǎn)看下):
/** Update default (global) configuration and notify listeners about changes. */
int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
boolean persistent, int userId) {
......
// 注釋2_4:這里對切換語言后封裝的Configuration做后續(xù)處理的預(yù)檢查和判斷
if (!initLocale && !values.getLocales().isEmpty() && values.userSetLocale) {
final LocaleList locales = values.getLocales();
int bestLocaleIndex = 0;
if (locales.size() > 1) {
if (mSupportedSystemLocales == null) {
// 注釋2_5:這里是獲取系統(tǒng)資源配置支持的語言,也就是說實(shí)際能真的支持的語言
mSupportedSystemLocales = Resources.getSystem().getAssets().getLocales();
}
// 注釋2_6:這里是通過方法計(jì)算得到在所有支持語言列表里 最匹配的語言的所在列表的 索引
// 具體算法可getFirstMatchIndex 一路點(diǎn)進(jìn)去看
bestLocaleIndex = Math.max(0, locales.getFirstMatchIndex(mSupportedSystemLocales));
}
// 注釋2_7:這里是修改系統(tǒng)屬性值,即 當(dāng)前系統(tǒng)的默認(rèn)語言
SystemProperties.set("persist.sys.locale",
locales.get(bestLocaleIndex).toLanguageTag());
LocaleList.setDefault(locales, bestLocaleIndex);
// 注釋2_8:這里是將切換語言的動作通過 handler - message 的形式分發(fā)出去,以通知系統(tǒng)各個地方刷新
final Message m = PooledLambda.obtainMessage(
ActivityTaskManagerService::sendLocaleToMountDaemonMsg, this,
locales.get(bestLocaleIndex));
mH.sendMessage(m);
}
......
}
切換語言的流程,簡單的流程就到這里,其余就不在詳細(xì)展開了
添加流程
由前面分析可知,添加流程由 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:這里是語言列表的點(diǎn)擊事件,即代表這開啟了被點(diǎn)擊語言的添加流程
// 這里是 用到本類的 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:看上一步,調(diào)用的是這個4個參數(shù)的方法
private static LocalePickerWithRegion createCountryPicker(Context context,
LocaleSelectedListener listener, LocaleStore.LocaleInfo parent,
boolean translatedOnly) {
LocalePickerWithRegion localePicker = new LocalePickerWithRegion();
// 注釋2_10_2:重要是這一步,調(diào)用了localePicker.setListener,localePicker是LocalePickerWithRegion
// 實(shí)例化的對象,于是將流程傳遞給了本類的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:這部分的邏輯是 獲取已經(jīng)添加的語言列表,作為需要忽略的部分,畢竟已經(jīng)添加的,還能再添加就不合理了
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;
由于上面的添加流程,已經(jīng)走完,轉(zhuǎn)向了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:這個方法很關(guān)鍵,后面邏輯處理保存的數(shù)據(jù),需要這里先做預(yù)處理的
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方法內(nèi)部邏輯不少,只看下核心部分
@UnsupportedAppUsage
public static void fillCache(Context context) {
if (sFullyInitialized) {
return;
}
......
// 注釋2_12_2:LocalePicker.getSupportedLocales(context) 這個方法很關(guān)鍵 ,他是整個數(shù)據(jù)處理的來源
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處后續(xù)邏輯處理 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));
}
}
}
......
}
看到上一步,其實(shí)數(shù)據(jù)源來自LocalePicker.getSupportedLocales(context),那就看看
base/core/java/com/android/internal/app/LocalePicker.java
......
// 注釋2_13_1:這個方法也很關(guān)鍵,這是獲取系統(tǒng)資源配置支持的語言列表
public static String[] getSystemAssetLocales() {
return Resources.getSystem().getAssets().getLocales();
}
public static String[] getSupportedLocales(Context context) {
// 注釋2_13:好了,找到這里,似乎真相大白了,所有的語言列表,都是從這個supported_locales 資源數(shù)組獲取的
// 資源位置: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]);
}
......
好了分析到這里,加載的流程也基本結(jié)束了。
最后總結(jié)
- R.array.supported_locales 獲取到的語言列表 是config 里的xml 配置,如果要對系統(tǒng)做語言支持上的變動,可以改這里
- Resources.getSystem().getAssets().getLocales()才是系統(tǒng)真正可以支持的語言列表,ActivityTaskManagerService.java的updateGlobalConfigurationLocked 方法里判斷選擇的語言是不是可用,也是用的這個資源作為判斷依據(jù)
- R.array.supported_locales里的語言配置,可能Resources.getSystem().getAssets().getLocales()不能全部支持,這就會導(dǎo)致我們添加了某個語言,切換到它,但是卻沒效果,所以有個更好的優(yōu)化方案這邊分享一個:
修改 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);
// 設(shè)置切換語言不支持的問題__配置可支持的語言篩掉沒有系統(tǒng)資源配置的
/*
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)){
// 本地資源配沒有有配置直接跳過繼續(xù)檢查下一個
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]);
}
// 設(shè)置切換語言不支持的問題__篩查系統(tǒng)沒有配置資源的語言
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;
}