公司建网站空间,邵阳网站建设,wordpress主机cdn,做直播网站多少钱Android手机语言切换行为#xff0c;是通过设置-语言和输入法-语言来改变手机的语言#xff0c;其实这个功能很少被用户使用。 以Android5.1工程源码为基础,从设置app入手来分析和学习语言切换的过程:一、语言设置界面#xff1a;首先在设置app中找到语言设置这个Preference… Android手机语言切换行为是通过设置-语言和输入法-语言来改变手机的语言其实这个功能很少被用户使用。 以Android5.1工程源码为基础,从设置app入手来分析和学习语言切换的过程: 一、语言设置界面 首先在设置app中找到语言设置这个Preference目前设置中界面大多都是Fragment先找到语言和输入法的PreferenceScreen与其对应的Fragment是InputMethodAndLanguageSettings.java在其onCreate()方法中首先是增加语言设置的preference [java] view plaincopy addPreferencesFromResource(R.xml.language_settings); 找到language_settings.xml可发现如下代码 [html] view plaincopy PreferenceScreen android:keyphone_language android:titlestring/phone_language android:fragmentcom.android.settings.LocalePicker / 于是断定LocalePicker就是语言设置的Fragment它是ListFragment的子类继承于framework中LocalePicker,并实现了父类的一个接口其回调方法是onLocaleSelected()Locale中文含义大致是语言环境所以可推测这是设置语言后的一个回调方法不确定的话可打断点测试一下。然而此类中并没有关于语言设置界面数据适配的太多逻辑只是通过父类的方法创建了一个view: [java] view plaincopy 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 [java] view plaincopy /** * Constructs an Adapter object containing Locale information. Content is sorted by * {link LocaleInfo#label}. */ public static ArrayAdapterLocaleInfo constructAdapter(Context context) { return constructAdapter(context, R.layout.locale_picker_item, R.id.locale); } public static ArrayAdapterLocaleInfo constructAdapter(Context context, final int layoutId, final int fieldId) { boolean isInDeveloperMode Settings.Global.getInt(context.getContentResolver(), Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) ! 0; //获取系统支持语言的信息 final ListLocaleInfo localeInfos getAllAssetLocales(context, isInDeveloperMode); final LayoutInflater inflater (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); return new ArrayAdapterLocaleInfo(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()方法获取系统支持语言的信息 [java] view plaincopy public static ListLocaleInfo getAllAssetLocales(Context context, boolean isInDeveloperMode) { final Resources resources context.getResources(); //获取系统所支持的语言 final String[] locales Resources.getSystem().getAssets().getLocales(); ListString localeList new ArrayListString(locales.length); Collections.addAll(localeList, locales); // Dont show the pseudolocales unless were 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 ArrayListLocaleInfo localeInfos new ArrayListLocaleInfo(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 [java] view plaincopy /** * Get the locales that this asset manager contains data for. * * pOn SDK 21 (Android 5.0: Lollipop) and above, Locale strings are valid * a hrefhttps://tools.ietf.org/html/bcp47BCP-47/a language tags and can be * parsed using {link java.util.Locale#forLanguageTag(String)}. * * pOn 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浏览下这个文件发现这个家伙有点不得了啊什么resourcetheme等都跟它有关系,看样子还的加油学学JNI啊!然后找到对应的native方法 [java] view plaincopy static jobjectArray android_content_AssetManager_getLocales(JNIEnv* env, jobject clazz) { VectorString8 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 i0; iN; 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()方法 [java] view plaincopy 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()方法来更新系统的语言环境 [java] view plaincopy /** * 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 isnt 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()方法 [java] view plaincopy 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清除了额其实这个方法只是把系统字体的特效清除了比如字体的大小 [java] view plaincopy /** * hide Erase the fields in the Configuration that should be applied * by the settings. */ public static void clearConfiguration(Configuration inoutConfig) { inoutConfig.fontScale 0; } 然后调用updateConfigurationLocked()方法 [java] view plaincopy /** * 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 varstarting/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 imLruProcesses.size()-1; i0; 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 ((changesActivityInfo.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()方法 [java] view plaincopy /** * 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()方法 [java] view plaincopy 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的构造函数中完成 [java] view plaincopy mSystemThread ActivityThread.currentActivityThread(); [java] view plaincopy //此方法属于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 imActiveResources.size()-1; i0; 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都会去更新configurationconfiguration所包含的属性都会遍历到该更新的数据更新该清除的缓存清除。 到这里第一件事算是做完了就要做第二件事让新的configuration更新到所有界面updateConfigurationLocked()方法通过遍历保存在ProcessRecord中的进程然后通过scheduleConfigurationChanged()方法更新它们的configuration [java] view plaincopy for (int imLruProcesses.size()-1; i0; 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()方法函数调用堆栈如图 [java] view plaincopy public void scheduleConfigurationChanged(Configuration config) { updatePendingConfiguration(config); sendMessage(H.CONFIGURATION_CHANGED, config); } [java] view plaincopy 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; [java] view plaincopy 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); } ArrayListComponentCallbacks2 callbacks collectComponentCallbacks(false, config); freeTextLayoutCachesIfNeeded(configDiff); if (callbacks ! null) { final int N callbacks.size(); for (int i0; iN; i) { performConfigurationChanged(callbacks.get(i), config); } } } 到这里设置语言以后代码跑的流程就基本结束了需要一提的是performConfigurationChanged()方法。为什么要提它呢因为有时候写应用的时候activity需要关注一些configChanged如android:configChangesorientation|keyboardHidden|screenSize然后重写onConfigurationChanged()方法。然而触发这个方法回调的触发点在哪里呢这里就以设置语言为例设置语言触发了configuration的改变。先来看下performConfigurationChanged()方法 [java] view plaincopy 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 dont bother calling // onConfigurationChanged int diff activity.mCurrentConfig.diff(config); if (diff ! 0) { // If this activity doesnt handle any of the config changes // then dont bother calling onConfigurationChanged as were // 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。具体就不多说了。 转载于:https://www.cnblogs.com/Free-Thinker/p/5461523.html