您的位置:首页 > 移动开发 > Android开发

Android源码分析一:语言切换机制

2016-02-17 16:25 633 查看
android语言切换是在packages/apps/Settings/com/android/settings/LocalePicker.java的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();

config.locale = locale;

// indicate this isn't some passing default - the user wants this remembered

config.userSetLocale = true;

am.updateConfiguration(config);

// Trigger the dirty bit for the Settings Provider.

BackupManager.dataChanged("com.android.providers.settings");

} catch (RemoteException e) {

// Intentionally left blank

}

}

从注释可以看出, 只要本地local改变就会调用该函数. 查看ActivityManagerNative的getDefault()可以看到, 该函数返回的是远程服务对象ActivityManagerServices.java在本地的一个代理. 最终调用的是ActivityManagerService.java中的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);

}

}

该函数, 首先进行的是权限的校验. 然后调用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 <var>starting</var> is being destroyed to match the new

* configuration.

* @param persistent TODO

*/

public boolean updateConfigurationLocked(Configuration values,

ActivityRecord starting, boolean persistent, boolean initLocale) {

int changes = 0;

boolean kept = true;

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, values.simSetLocale);

}

mConfigurationSeq++;

if (mConfigurationSeq <= 0) {

mConfigurationSeq = 1;

}

newConfig.seq = mConfigurationSeq;

mConfiguration = newConfig;

Slog.i(TAG, "Config changed: " + newConfig);

final Configuration configCopy = new Configuration(mConfiguration);

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);

broadcastIntentLocked(null, null, intent, null, null, 0, null, null,

null, false, false, MY_PID, Process.SYSTEM_UID);

if ((changes&ActivityInfo.CONFIG_LOCALE) != 0) {

broadcastIntentLocked(null, null,

new Intent(Intent.ACTION_LOCALE_CHANGED),

null, null, 0, null, null,

null, false, false, MY_PID, Process.SYSTEM_UID);

}

}

}

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 = mMainStack.topRunningActivityLocked(null);

}

if (starting != null) {

kept = mMainStack.ensureActivityConfigurationLocked(starting, changes);

// And we need to make sure at this point that all other activities

// are made visible with the correct configuration.

mMainStack.ensureActivitiesVisibleLocked(starting, changes);

}

if (values != null && mWindowManager != null) {

mWindowManager.setNewConfiguration(mConfiguration);

}

return kept;

}

整个语言切换就在这个函数中完成. 咋一看似乎没感觉到该函数做了哪些事情. 我们首先来看注释: Do either or both things: (1) change the current configuration, and (2)

make sure the given activity is running with the (now) current. configuration大概意思是: 这个函数做了两件事情. (1). 改变当前的configuration. 意思就是让改变的configuration更新到当前configuration. (2) 确保所有正在运行的activity都能更新改变后的configuration.(这点是关键.)
. 我们按照这个思路看看android是如何更新configuration. 查看代码 , 首先看到 这个函数首先判断values是否为空, 这里values肯定不为空的, 然后changes = newConfig.updateFrom(values); 我们看看updateFrom做了什么操作.

[java] view
plaincopy

/**

* Copy the fields from delta into this Configuration object, keeping

* track of which ones have changed. Any undefined fields in

* <var>delta</var> are ignored and not copied in to the current

* Configuration.

* @return Returns a bit mask of the changed fields, as per

* {@link #diff}.

*/

public int updateFrom(Configuration delta) {

int changed = 0;

...

if (delta.locale != null

&& (locale == null || !locale.equals(delta.locale))) {

changed |= ActivityInfo.CONFIG_LOCALE;

locale = delta.locale != null

? (Locale) delta.locale.clone() : null;

textLayoutDirection = LocaleUtil.getLayoutDirectionFromLocale(locale);

}

if (delta.userSetLocale && (!userSetLocale || ((changed & ActivityInfo.CONFIG_LOCALE) != 0)))

{

userSetLocale = true;

changed |= ActivityInfo.CONFIG_LOCALE;

}

...

return changed;

}

因为语言改变了, 那么 (!locale.equals(delta.locale)) 是true. changed 大于0, 然后return changed. 回到ActivityManagerService.java的updateConfigurationLocked函数, 因为changed不为0 , 所以走if这个流程. 继续看代码

[java] view
plaincopy

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) {

}

}

首先看到的是mLurProcesses 是ArrayList<ProcessRecord>类型. LRU : Least Recently Used保存所有运行过的进程. ProcessRecord进程类, 一个apk文件运行时会对应一个进程. app.thread. 此处的thread代表的是ApplicationThreadNative.java类型.
然后调用其scheduleConfigurationChanged(); 查看该函数

[java] view
plaincopy

public final void scheduleConfigurationChanged(Configuration config)

throws RemoteException {

Parcel data = Parcel.obtain();

data.writeInterfaceToken(IApplicationThread.descriptor);

config.writeToParcel(data, 0);

mRemote.transact(SCHEDULE_CONFIGURATION_CHANGED_TRANSACTION, data, null,

IBinder.FLAG_ONEWAY);

data.recycle();

}

又是通过binder调用, 所以 , binder在android中是一个很重要的概念. 此处远程调用的是ActivityThread.java中的私有内部内ApplicationThread

[java] view
plaincopy

private class ApplicationThread extends ApplicationThreadNative {

private static final String HEAP_COLUMN = "%13s %8s %8s %8s %8s %8s %8s";

private static final String ONE_COUNT_COLUMN = "%21s %8d";

private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d";

private static final String TWO_COUNT_COLUMNS_DB = "%21s %8d %21s %8d";

private static final String DB_INFO_FORMAT = " %8s %8s %14s %14s %s";

...

public void scheduleConfigurationChanged(Configuration config) {

updatePendingConfiguration(config);

queueOrSendMessage(H.CONFIGURATION_CHANGED, config);

}

...

}

而ApplicationThread中的handler的CONFIGURATION_CHANGED是调用handleConfigurationChanged()

[java] view
plaincopy

final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {

ArrayList<ComponentCallbacks2> callbacks = null;

... ...

applyConfigurationToResourcesLocked(config, compat);

...

callbacks = collectComponentCallbacksLocked(false, config);

...

if (callbacks != null) {

final int N = callbacks.size();

for (int i=0; i<N; i++) {

performConfigurationChanged(callbacks.get(i), config);

}

}

这个函数首先是调用applyConfigurationToResourcesLocked(). 看函数名大概可以推测: 将configuration应用到resources.这里configuration改变的是local 本地语言. 那而resources资源包含不就包含了语言, 图片这些资源吗.

[java] view
plaincopy

final boolean applyConfigurationToResourcesLocked(Configuration config,

CompatibilityInfo compat) {

int changes = mResConfiguration.updateFrom(config);

DisplayMetrics dm = getDisplayMetricsLocked(null, true);

if (compat != null && (mResCompatibilityInfo == null ||

!mResCompatibilityInfo.equals(compat))) {

mResCompatibilityInfo = compat;

changes |= ActivityInfo.CONFIG_SCREEN_LAYOUT

| ActivityInfo.CONFIG_SCREEN_SIZE

| ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;

}

...

Resources.updateSystemConfiguration(config, dm, compat);

...

Iterator<WeakReference<Resources>> it =

mActiveResources.values().iterator();

while (it.hasNext()) {

WeakReference<Resources> v = it.next();

Resources r = v.get();

if (r != null) {

if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "

+ r + " config to: " + config);

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());

it.remove();

}

}

return changes != 0;

}

Resources.updateSystemConfiguration()清除一部分系统资源, 并且将config更新到Resources, 而Resources包含了一个AssetManager对象, 该对象的核心实现是在AssetManager.cpp中完成的. 然后循环清空mActivityResources资源. 再回到handleConfigurationChanged()函数, 执行完updateSystemConfiguration后, 会循环该进程的所有activity:

if (callbacks != null) {

final int N = callbacks.size();

for (int i=0; i<N; i++) {

performConfigurationChanged(callbacks.get(i), config);

}

}

再来看performConfigurationChanged的实现:

[java] view
plaincopy

private final 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是否改变, 如果改变那么shouldChangeConfig为true. 然后调用activity的onConfigurationChange(config);

[java] view
plaincopy

/**

* Called by the system when the device configuration changes while your

* activity is running. Note that this will <em>only</em> be called if

* you have selected configurations you would like to handle with the

* {@link android.R.attr#configChanges} attribute in your manifest. If

* any configuration change occurs that is not selected to be reported

* by that attribute, then instead of reporting it the system will stop

* and restart the activity (to have it launched with the new

* configuration).

*

* <p>At the time that this function has been called, your Resources

* object will have been updated to return resource values matching the

* new configuration.

*

* @param newConfig The new device configuration.

*/

public void onConfigurationChanged(Configuration newConfig) {

mCalled = true;

mFragments.dispatchConfigurationChanged(newConfig);

if (mWindow != null) {

// Pass the configuration changed event to the window

mWindow.onConfigurationChanged(newConfig);

}

if (mActionBar != null) {

// Do this last; the action bar will need to access

// view changes from above.

mActionBar.onConfigurationChanged(newConfig);

}

}

查看注释, 大概意思是: 如果你的activity运行 , 设备信息有改变(即configuration改变)时由系统调用. 如果你在manifest.xml中配置了configChnages属性则表示有你自己来处理configuration change. 否则就重启当前这个activity. 而重启之前, 旧的resources已经被清空, 那么就会装载新的资源, 整个过程就完成了语言切换后 , 能够让所有app使用新的语言. 语言切换流程大概分为三步:

第一步: 判断configuration的local是否已经改变, 如果改变则将local更新到当前的configuration

第二步: 清空旧的资源.

第三步: 重启所有所有进程并加装新资源.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 资源