Android-语言设置流程分析
2016-01-21 15:20
627 查看
Android手机语言切换行为,是通过设置-语言和输入法-语言来改变手机的语言,其实这个功能很少被用户使用。
以Android5.1工程源码为基础,从设置app入手来分析和学习语言切换的过程:
一、语言设置界面:
首先在设置app中找到语言设置这个Preference,目前设置中界面大多都是Fragment,先找到语言和输入法的PreferenceScreen,与其对应的Fragment是InputMethodAndLanguageSettings.java,在其onCreate()方法中,首先是增加语言设置的preference:
找到language_settings.xml,可发现如下代码:
于是断定LocalePicker就是语言设置的Fragment,它是ListFragment的子类,继承于framework中LocalePicker,并实现了父类的一个接口,其回调方法是onLocaleSelected(),Locale中文含义大致是语言环境,所以可推测这是设置语言后的一个回调方法,不确定的话,可打断点测试一下。然而此类中并没有关于语言设置界面数据适配的太多逻辑,只是通过父类的方法创建了一个view:
所以更多逻辑应该在framework中的LocalePicker.java中。既然是ListFragment,那就必须有Adapter,在此类中有构建了一个Adapter:
此方法中还会通过Resources.getSystem().getAssets().getLocales()去获得系统支持的语言信息,然后添加LocaleInfo里边,再通过Adapter适配到ListView中。getLocales()方法属于类AssetManager.java:
乍一看,是个native方法,那不就是跟JNI有关系了,所以只能到相应JNI目录下去找了,路径:android5.1\frameworks\base\core\jni,对应文件:android_util_AssetManager.cpp(浏览下这个文件,发现这个家伙有点不得了啊,什么resource,theme等都跟它有关系,看样子还的加油学学JNI啊!),然后找到对应的native方法:
二、语言设置功能实现过程:
上面提到了设置中的LocalePicker类实现了父类接口中的onLocaleSelected()方法:
此方法中最终调用了其父类的updateLocale()方法来更新系统的语言环境:
又看到ActivityManagerNative.getDefault(),所以可以直接到ActivityManagerService.java中找对应的方法,此方法中先是把选择的语言设置到Configuration中,记录下来。设置了不代表系统就知道这档子事,所以还需要am去更新一下,说的俗气一点:am老大知道了这档子事,然后大吼一声,我这里有个东西改变了,小伙伴们刷新一下!在ActivityManagerService中找到updateConfiguration()方法:
看到Settings.System.clearConfiguration(values)不要以为这里把values清除了额,其实这个方法只是把系统字体的特效清除了,比如字体的大小:
然后调用updateConfigurationLocked()方法:
有了新的数据就要保存,保存在configuration中不是个事。对于Android系统而言,改变语言,有两个地方的数据需要更新,一个是SystemProperties,另一个是数据库。前者以键值对的形式存放数据,多用于System,后者保存于DataBase中,多用于应用程序获取,算是对外开放的数据。上面方法中对这两个地方都进行了数据保存操作:
1)SystemProperties:调用saveLocaleLocked()方法:
2)database:调用Settings.System.putConfiguration()方法:
该保存的数据保存了,但是Resource还不知道这档子事,因为Android代码和资源是分开的,Resource不知道Configuration发生了变化,Resource就不会去加载正确的资源。所以接下来此方法调用了mSystemThread.applyConfigurationToResources(configCopy)来完成这件事,mSystemThread是一个ActivityThread对象,其初始化在ActivityManagerService的构造函数中完成:
此方法中Resource和ApplicationPackageManager都会去更新configuration,configuration所包含的属性都会遍历到,该更新的数据更新,该清除的缓存清除。
到这里,第一件事算是做完了,就要做第二件事,让新的configuration更新到所有界面,updateConfigurationLocked()方法通过遍历保存在ProcessRecord中的进程,然后通过scheduleConfigurationChanged()方法更新它们的configuration:
此处通过Binder机制调用ApplicationThreadNative.java中的scheduleConfigurationChanged()方法,最后调用到ActivityThread中的内部类ApplicationThread的scheduleConfigurationChanged()方法,函数调用堆栈如图:
到这里设置语言以后,代码跑的流程就基本结束了,需要一提的是performConfigurationChanged()方法。为什么要提它呢?因为有时候写应用的时候activity需要关注一些configChanged,如:android:configChanges="orientation|keyboardHidden|screenSize",然后重写onConfigurationChanged()方法。然而触发这个方法回调的触发点在哪里呢?这里就以设置语言为例,设置语言触发了configuration的改变。先来看下performConfigurationChanged()方法:
如果configuration确实改变了,那么此方法中就会调用cb.onConfigurationChanged(config)。cb代表ComponentCallbacks2,而ComponentCallbacks2 又继承于ComponentCallbacks,所以onConfigurationChanged()方法属于ComponentCallbacks,同样Activity类也实现了ComponentCallbacks2这个接口,如此一来这个回调的过程就连接上了。也充分说明了为什么在configuration改变以后,activity关注的config会回调其父类的onConfigurationChanged()方法。
最后就是广播configuration改变了,updateConfigurationLocked()广播了ACTION_CONFIGURATION_CHANGED和ACTION_LOCALE_CHANGED,使用的方法是broadcastIntentLocked(),此方法广播成功返回BROADCAST_SUCCESS。具体就不多说了。
以Android5.1工程源码为基础,从设置app入手来分析和学习语言切换的过程:
一、语言设置界面:
首先在设置app中找到语言设置这个Preference,目前设置中界面大多都是Fragment,先找到语言和输入法的PreferenceScreen,与其对应的Fragment是InputMethodAndLanguageSettings.java,在其onCreate()方法中,首先是增加语言设置的preference:
addPreferencesFromResource(R.xml.language_settings);
找到language_settings.xml,可发现如下代码:
<PreferenceScreen android:key="phone_language" android:title="@string/phone_language" android:fragment="com.android.settings.LocalePicker" />
于是断定LocalePicker就是语言设置的Fragment,它是ListFragment的子类,继承于framework中LocalePicker,并实现了父类的一个接口,其回调方法是onLocaleSelected(),Locale中文含义大致是语言环境,所以可推测这是设置语言后的一个回调方法,不确定的话,可打断点测试一下。然而此类中并没有关于语言设置界面数据适配的太多逻辑,只是通过父类的方法创建了一个view:
@Override public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = super.onCreateView(inflater, container, savedInstanceState); final ListView list = (ListView) view.findViewById(android.R.id.list); Utils.forcePrepareCustomPreferencesList(container, view, list, false); return view; }
所以更多逻辑应该在framework中的LocalePicker.java中。既然是ListFragment,那就必须有Adapter,在此类中有构建了一个Adapter:
/** * Constructs an Adapter object containing Locale information. Content is sorted by * {@link LocaleInfo#label}. */ public static ArrayAdapter<LocaleInfo> constructAdapter(Context context) { return constructAdapter(context, R.layout.locale_picker_item, R.id.locale); } public static ArrayAdapter<LocaleInfo> constructAdapter(Context context, final int layoutId, final int fieldId) { boolean isInDeveloperMode = Settings.Global.getInt(context.getContentResolver(), Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0; //获取系统支持语言的信息 final List<LocaleInfo> localeInfos = getAllAssetLocales(context, isInDeveloperMode); final LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); return new ArrayAdapter<LocaleInfo>(context, layoutId, fieldId, localeInfos) { @Override public View getView(int position, View convertView, ViewGroup parent) { View view; TextView text; if (convertView == null) { view = inflater.inflate(layoutId, parent, false); text = (TextView) view.findViewById(fieldId); view.setTag(text); } else { view = convertView; text = (TextView) view.getTag(); } LocaleInfo item = getItem(position); text.setText(item.toString()); text.setTextLocale(item.getLocale()); return view; } }; }而此方法通过getAllAssetLocales()方法获取系统支持语言的信息:
public static List<LocaleInfo> getAllAssetLocales(Context context, boolean isInDeveloperMode) { final Resources resources = context.getResources(); //获取系统所支持的语言 final String[] locales = Resources.getSystem().getAssets().getLocales(); List<String> localeList = new ArrayList<String>(locales.length); Collections.addAll(localeList, locales); // Don't show the pseudolocales unless we're in developer mode. if (!isInDeveloperMode) { localeList.remove("ar-XB"); localeList.remove("en-XA"); } Collections.sort(localeList); final String[] specialLocaleCodes = resources.getStringArray(R.array.special_locale_codes); final String[] specialLocaleNames = resources.getStringArray(R.array.special_locale_names); final ArrayList<LocaleInfo> localeInfos = new ArrayList<LocaleInfo>(localeList.size()); for (String locale : localeList) { final Locale l = Locale.forLanguageTag(locale.replace('_', '-')); if (l == null || "und".equals(l.getLanguage()) || l.getLanguage().isEmpty() || l.getCountry().isEmpty()) { continue; } if (localeInfos.isEmpty()) { if (DEBUG) { Log.v(TAG, "adding initial "+ toTitleCase(l.getDisplayLanguage(l))); } localeInfos.add(new LocaleInfo(toTitleCase(l.getDisplayLanguage(l)), l)); } else { // check previous entry: // same lang and a country -> upgrade to full name and // insert ours with full name // diff lang -> insert ours with lang-only name final LocaleInfo previous = localeInfos.get(localeInfos.size() - 1); if (previous.locale.getLanguage().equals(l.getLanguage()) && !previous.locale.getLanguage().equals("zz")) { if (DEBUG) { Log.v(TAG, "backing up and fixing " + previous.label + " to " + getDisplayName(previous.locale, specialLocaleCodes, specialLocaleNames)); } previous.label = toTitleCase(getDisplayName( previous.locale, specialLocaleCodes, specialLocaleNames)); if (DEBUG) { Log.v(TAG, " and adding "+ toTitleCase( getDisplayName(l, specialLocaleCodes, specialLocaleNames))); } localeInfos.add(new LocaleInfo(toTitleCase( getDisplayName(l, specialLocaleCodes, specialLocaleNames)), l)); } else { String displayName = toTitleCase(l.getDisplayLanguage(l)); if (DEBUG) { Log.v(TAG, "adding "+displayName); } localeInfos.add(new LocaleInfo(displayName, l)); } } } Collections.sort(localeInfos); return localeInfos; }
此方法中还会通过Resources.getSystem().getAssets().getLocales()去获得系统支持的语言信息,然后添加LocaleInfo里边,再通过Adapter适配到ListView中。getLocales()方法属于类AssetManager.java:
/** * Get the locales that this asset manager contains data for. * * <p>On SDK 21 (Android 5.0: Lollipop) and above, Locale strings are valid * <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> language tags and can be * parsed using {@link java.util.Locale#forLanguageTag(String)}. * * <p>On SDK 20 (Android 4.4W: Kitkat for watches) and below, locale strings * are of the form {@code ll_CC} where {@code ll} is a two letter language code, * and {@code CC} is a two letter country code. */ public native final String[] getLocales();
乍一看,是个native方法,那不就是跟JNI有关系了,所以只能到相应JNI目录下去找了,路径:android5.1\frameworks\base\core\jni,对应文件:android_util_AssetManager.cpp(浏览下这个文件,发现这个家伙有点不得了啊,什么resource,theme等都跟它有关系,看样子还的加油学学JNI啊!),然后找到对应的native方法:
static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject clazz) { Vector<String8> locales; AssetManager* am = assetManagerForJavaObject(env, clazz); if (am == NULL) { return NULL; } am->getLocales(&locales); const int N = locales.size(); jobjectArray result = env->NewObjectArray(N, g_stringClass, NULL); if (result == NULL) { return NULL; } for (int i=0; i<N; i++) { jstring str = env->NewStringUTF(locales[i].string()); if (str == NULL) { return NULL; } env->SetObjectArrayElement(result, i, str); env->DeleteLocalRef(str); } return result; }通过上面初步的分析,语言的List界面就基本出来了,在getAllAssetLocales()方法中打了个断点,查看了下locales被赋值以后的值:
二、语言设置功能实现过程:
上面提到了设置中的LocalePicker类实现了父类接口中的onLocaleSelected()方法:
public static interface LocaleSelectionListener { // You can add any argument if you really need it... public void onLocaleSelected(Locale locale); } @Override public void onLocaleSelected(final Locale locale) { if (Utils.hasMultipleUsers(getActivity())) { mTargetLocale = locale; showDialog(DLG_SHOW_GLOBAL_WARNING); } else { getActivity().onBackPressed(); LocalePicker.updateLocale(locale); } }
此方法中最终调用了其父类的updateLocale()方法来更新系统的语言环境:
/** * Requests the system to update the system locale. Note that the system looks halted * for a while during the Locale migration, so the caller need to take care of it. */ public static void updateLocale(Locale locale) { try { IActivityManager am = ActivityManagerNative.getDefault(); Configuration config = am.getConfiguration(); // Will set userSetLocale to indicate this isn't some passing default - the user // wants this remembered config.setLocale(locale); am.updateConfiguration(config); // Trigger the dirty bit for the Settings Provider. BackupManager.dataChanged("com.android.providers.settings"); } catch (RemoteException e) { // Intentionally left blank } }
又看到ActivityManagerNative.getDefault(),所以可以直接到ActivityManagerService.java中找对应的方法,此方法中先是把选择的语言设置到Configuration中,记录下来。设置了不代表系统就知道这档子事,所以还需要am去更新一下,说的俗气一点:am老大知道了这档子事,然后大吼一声,我这里有个东西改变了,小伙伴们刷新一下!在ActivityManagerService中找到updateConfiguration()方法:
public void updateConfiguration(Configuration values) { enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, "updateConfiguration()"); synchronized(this) { if (values == null && mWindowManager != null) { // sentinel: fetch the current configuration from the window manager values = mWindowManager.computeNewConfiguration(); } if (mWindowManager != null) { mProcessList.applyDisplaySize(mWindowManager); } final long origId = Binder.clearCallingIdentity(); if (values != null) { Settings.System.clearConfiguration(values); } updateConfigurationLocked(values, null, false, false); Binder.restoreCallingIdentity(origId); } }
看到Settings.System.clearConfiguration(values)不要以为这里把values清除了额,其实这个方法只是把系统字体的特效清除了,比如字体的大小:
/** * @hide Erase the fields in the Configuration that should be applied * by the settings. */ public static void clearConfiguration(Configuration inoutConfig) { inoutConfig.fontScale = 0; }
然后调用updateConfigurationLocked()方法:
/** * Do either or both things: (1) change the current configuration, and (2) * make sure the given activity is running with the (now) current * configuration. Returns true if the activity has been left running, or * false if <var>starting</var> is being destroyed to match the new * configuration. * @param persistent TODO */ boolean updateConfigurationLocked(Configuration values, ActivityRecord starting, boolean persistent, boolean initLocale) { int changes = 0; if (values != null) { Configuration newConfig = new Configuration(mConfiguration); changes = newConfig.updateFrom(values); if (changes != 0) { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) { Slog.i(TAG, "Updating configuration to: " + values); } EventLog.writeEvent(EventLogTags.CONFIGURATION_CHANGED, changes); if (values.locale != null && !initLocale) { saveLocaleLocked(values.locale, !values.locale.equals(mConfiguration.locale), values.userSetLocale); } mConfigurationSeq++; if (mConfigurationSeq <= 0) { mConfigurationSeq = 1; } newConfig.seq = mConfigurationSeq; mConfiguration = newConfig; Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + newConfig); mUsageStatsService.reportConfigurationChange(newConfig, mCurrentUserId); //mUsageStatsService.noteStartConfig(newConfig); final Configuration configCopy = new Configuration(mConfiguration); // TODO: If our config changes, should we auto dismiss any currently // showing dialogs? mShowDialogs = shouldShowDialogs(newConfig); AttributeCache ac = AttributeCache.instance(); if (ac != null) { ac.updateConfiguration(configCopy); } // Make sure all resources in our process are updated // right now, so that anyone who is going to retrieve // resource values after we return will be sure to get // the new ones. This is especially important during // boot, where the first config change needs to guarantee // all resources have that config before following boot // code is executed. mSystemThread.applyConfigurationToResources(configCopy); if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) { Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG); msg.obj = new Configuration(configCopy); mHandler.sendMessage(msg); } for (int i=mLruProcesses.size()-1; i>=0; i--) { ProcessRecord app = mLruProcesses.get(i); try { if (app.thread != null) { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc " + app.processName + " new config " + mConfiguration); app.thread.scheduleConfigurationChanged(configCopy); } } catch (Exception e) { } } Intent intent = new Intent(Intent.ACTION_CONFIGURATION_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_REPLACE_PENDING | Intent.FLAG_RECEIVER_FOREGROUND); broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, AppOpsManager.OP_NONE, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL); if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) { intent = new Intent(Intent.ACTION_LOCALE_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, AppOpsManager.OP_NONE, false, false, MY_PID, Process.SYSTEM_UID, UserHandle.USER_ALL); } } } boolean kept = true; final ActivityStack mainStack = mStackSupervisor.getFocusedStack(); // mainStack is null during startup. if (mainStack != null) { if (changes != 0 && starting == null) { // If the configuration changed, and the caller is not already // in the process of starting an activity, then find the top // activity to check if its configuration needs to change. starting = mainStack.topRunningActivityLocked(null); } if (starting != null) { kept = mainStack.ensureActivityConfigurationLocked(starting, changes); // And we need to make sure at this point that all other activities // are made visible with the correct configuration. mStackSupervisor.ensureActivitiesVisibleLocked(starting, changes); } } if (values != null && mWindowManager != null) { mWindowManager.setNewConfiguration(mConfiguration); } return kept; }此方法主要做两件事:第一,改变当前的configuration,将新的数据放进去。第二,保证正在运行的应用程序界面更新最新的configuration。先调用updateFrom()方法,遍历configuration包含的属性是否改变,如果有改变就返回一个对应的整数,如果没有改变就返回0。就语言改变而言,根据上面的分析,configuration至少有3个属性发生了改变:fontscale(之前没有设置字体的效果就不会改变)、locale和布局的direction。
有了新的数据就要保存,保存在configuration中不是个事。对于Android系统而言,改变语言,有两个地方的数据需要更新,一个是SystemProperties,另一个是数据库。前者以键值对的形式存放数据,多用于System,后者保存于DataBase中,多用于应用程序获取,算是对外开放的数据。上面方法中对这两个地方都进行了数据保存操作:
1)SystemProperties:调用saveLocaleLocked()方法:
/** * Save the locale. You must be inside a synchronized (this) block. */ private void saveLocaleLocked(Locale l, boolean isDiff, boolean isPersist) { if(isDiff) { SystemProperties.set("user.language", l.getLanguage()); SystemProperties.set("user.region", l.getCountry()); } if(isPersist) { SystemProperties.set("persist.sys.language", l.getLanguage()); SystemProperties.set("persist.sys.country", l.getCountry()); SystemProperties.set("persist.sys.localevar", l.getVariant()); mHandler.sendMessage(mHandler.obtainMessage(SEND_LOCALE_TO_MOUNT_DAEMON_MSG, l)); } }
2)database:调用Settings.System.putConfiguration()方法:
if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) { Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG); msg.obj = new Configuration(configCopy); mHandler.sendMessage(msg); } ... case UPDATE_CONFIGURATION_MSG: { final ContentResolver resolver = mContext.getContentResolver(); Settings.System.putConfiguration(resolver, (Configuration)msg.obj); } break;
该保存的数据保存了,但是Resource还不知道这档子事,因为Android代码和资源是分开的,Resource不知道Configuration发生了变化,Resource就不会去加载正确的资源。所以接下来此方法调用了mSystemThread.applyConfigurationToResources(configCopy)来完成这件事,mSystemThread是一个ActivityThread对象,其初始化在ActivityManagerService的构造函数中完成:
mSystemThread = ActivityThread.currentActivityThread();
//此方法属于ActivityThread public final void applyConfigurationToResources(Configuration config) { synchronized (mResourcesManager) { mResourcesManager.applyConfigurationToResourcesLocked(config, null); } } //此方法属于ResourcesManage public final boolean applyConfigurationToResourcesLocked(Configuration config, CompatibilityInfo compat) { if (mResConfiguration == null) { mResConfiguration = new Configuration(); } if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq=" + mResConfiguration.seq + ", newSeq=" + config.seq); return false; } int changes = mResConfiguration.updateFrom(config); flushDisplayMetricsLocked(); DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked(Display.DEFAULT_DISPLAY); if (compat != null && (mResCompatibilityInfo == null || !mResCompatibilityInfo.equals(compat))) { mResCompatibilityInfo = compat; changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_SCREEN_SIZE | ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; } // set it for java, this also affects newly created Resources if (config.locale != null) { Locale.setDefault(config.locale); } Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat); ApplicationPackageManager.configurationChanged(); //Slog.i(TAG, "Configuration changed in " + currentPackageName()); Configuration tmpConfig = null; for (int i=mActiveResources.size()-1; i>=0; i--) { ResourcesKey key = mActiveResources.keyAt(i); Resources r = mActiveResources.valueAt(i).get(); if (r != null) { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " + r + " config to: " + config); int displayId = key.mDisplayId; boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); DisplayMetrics dm = defaultDisplayMetrics; final boolean hasOverrideConfiguration = key.hasOverrideConfiguration(); if (!isDefaultDisplay || hasOverrideConfiguration) { if (tmpConfig == null) { tmpConfig = new Configuration(); } tmpConfig.setTo(config); if (!isDefaultDisplay) { dm = getDisplayMetricsLocked(displayId); applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig); } if (hasOverrideConfiguration) { tmpConfig.updateFrom(key.mOverrideConfiguration); } r.updateConfiguration(tmpConfig, dm, compat); } else { r.updateConfiguration(config, dm, compat); } //Slog.i(TAG, "Updated app resources " + v.getKey() // + " " + r + ": " + r.getConfiguration()); } else { //Slog.i(TAG, "Removing old resources " + v.getKey()); mActiveResources.removeAt(i); } } return changes != 0; }
此方法中Resource和ApplicationPackageManager都会去更新configuration,configuration所包含的属性都会遍历到,该更新的数据更新,该清除的缓存清除。
到这里,第一件事算是做完了,就要做第二件事,让新的configuration更新到所有界面,updateConfigurationLocked()方法通过遍历保存在ProcessRecord中的进程,然后通过scheduleConfigurationChanged()方法更新它们的configuration:
for (int i=mLruProcesses.size()-1; i>=0; i--) { ProcessRecord app = mLruProcesses.get(i); try { if (app.thread != null) { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending to proc " + app.processName + " new config " + mConfiguration); app.thread.scheduleConfigurationChanged(configCopy); } } catch (Exception e) { } }
此处通过Binder机制调用ApplicationThreadNative.java中的scheduleConfigurationChanged()方法,最后调用到ActivityThread中的内部类ApplicationThread的scheduleConfigurationChanged()方法,函数调用堆栈如图:
public void scheduleConfigurationChanged(Configuration config) { updatePendingConfiguration(config); sendMessage(H.CONFIGURATION_CHANGED, config); }
case CONFIGURATION_CHANGED: Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged"); mCurDefaultDisplayDpi = ((Configuration)msg.obj).densityDpi; handleConfigurationChanged((Configuration)msg.obj, null); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break;
final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) { int configDiff = 0; synchronized (mResourcesManager) { if (mPendingConfiguration != null) { if (!mPendingConfiguration.isOtherSeqNewer(config)) { config = mPendingConfiguration; mCurDefaultDisplayDpi = config.densityDpi; updateDefaultDensity(); } mPendingConfiguration = null; } if (config == null) { return; } if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: " + config); mResourcesManager.applyConfigurationToResourcesLocked(config, compat); if (mConfiguration == null) { mConfiguration = new Configuration(); } if (!mConfiguration.isOtherSeqNewer(config) && compat == null) { return; } configDiff = mConfiguration.diff(config); mConfiguration.updateFrom(config); config = applyCompatConfiguration(mCurDefaultDisplayDpi); } ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config); freeTextLayoutCachesIfNeeded(configDiff); if (callbacks != null) { final int N = callbacks.size(); for (int i=0; i<N; i++) { performConfigurationChanged(callbacks.get(i), config); } } }
到这里设置语言以后,代码跑的流程就基本结束了,需要一提的是performConfigurationChanged()方法。为什么要提它呢?因为有时候写应用的时候activity需要关注一些configChanged,如:android:configChanges="orientation|keyboardHidden|screenSize",然后重写onConfigurationChanged()方法。然而触发这个方法回调的触发点在哪里呢?这里就以设置语言为例,设置语言触发了configuration的改变。先来看下performConfigurationChanged()方法:
private static void performConfigurationChanged(ComponentCallbacks2 cb, Configuration config) { // Only for Activity objects, check that they actually call up to their // superclass implementation. ComponentCallbacks2 is an interface, so // we check the runtime type and act accordingly. Activity activity = (cb instanceof Activity) ? (Activity) cb : null; if (activity != null) { activity.mCalled = false; } boolean shouldChangeConfig = false; if ((activity == null) || (activity.mCurrentConfig == null)) { shouldChangeConfig = true; } else { // If the new config is the same as the config this Activity // is already running with then don't bother calling // onConfigurationChanged int diff = activity.mCurrentConfig.diff(config); if (diff != 0) { // If this activity doesn't handle any of the config changes // then don't bother calling onConfigurationChanged as we're // going to destroy it. if ((~activity.mActivityInfo.getRealConfigChanged() & diff) == 0) { shouldChangeConfig = true; } } } if (DEBUG_CONFIGURATION) Slog.v(TAG, "Config callback " + cb + ": shouldChangeConfig=" + shouldChangeConfig); if (shouldChangeConfig) { cb.onConfigurationChanged(config); if (activity != null) { if (!activity.mCalled) { throw new SuperNotCalledException( "Activity " + activity.getLocalClassName() + " did not call through to super.onConfigurationChanged()"); } activity.mConfigChangeFlags = 0; activity.mCurrentConfig = new Configuration(config); } } }
如果configuration确实改变了,那么此方法中就会调用cb.onConfigurationChanged(config)。cb代表ComponentCallbacks2,而ComponentCallbacks2 又继承于ComponentCallbacks,所以onConfigurationChanged()方法属于ComponentCallbacks,同样Activity类也实现了ComponentCallbacks2这个接口,如此一来这个回调的过程就连接上了。也充分说明了为什么在configuration改变以后,activity关注的config会回调其父类的onConfigurationChanged()方法。
最后就是广播configuration改变了,updateConfigurationLocked()广播了ACTION_CONFIGURATION_CHANGED和ACTION_LOCALE_CHANGED,使用的方法是broadcastIntentLocked(),此方法广播成功返回BROADCAST_SUCCESS。具体就不多说了。
相关文章推荐
- android studio中xml文件代码提示问题
- Android4.4新的特性,在应用内开启透明状态栏和透明虚拟按钮
- android下应用的替换
- Android studio 通过以servlet搭建的服务器访问 PC端 mysql数据库(二)
- Android 应用图标
- Android开发记录16-友盟第三方登录、分享实现
- Android中实现局部的图片滑动指引效果
- Android-自定义Dialog
- Android-Parcelable接口用法
- Android jni开发资料--NDK环境搭建
- Android 关于创建桌面快捷图标的几点笔记
- Android6.0 设备Idle状态(三) PowerManagerService
- android 刷新媒体库(版本判断)
- Android,使用activity与fragment的小结与使用的细节
- Android中巧妙的位运算
- AndroidStudio中使用.9图片
- 条款弹框提示实现
- 使用kotlin编写Android第一个Activity
- Android 的 Runnable 是什么东西?
- I.MX6 android 设置 默认 动态桌面