关键字:Configuration,屏幕旋转,语言切换,字体切换, 源码
前段时间做了关于系统字体切换的功能,其中涉及到较多ConfigurationChanged的流程。屏幕旋转、mcc、系统语言切换等均是通过该流程来实现的。
网上少有这方面的描述,故将该部分总结提炼出来做个小结。
以下以屏幕旋转为例。关于屏幕旋转对于Activity的生命周期的影响也在以下可以体现,如何处理屏幕旋转对Act的影响也有启示作用。
ConfigurationChange Flow
讲太多也不如一个图来得实在,下图也是整个ConfigurationChange通用流程的概括,该流程是基于Android 5.1 和6.0画出,kk版本应该也是差不多的
流程跟踪
下面是对屏幕旋转的事件跟踪,尽量干货.
注:有序列表标号代表对应上图中的时序节点
1.屏幕旋转事件上传
G-Sensor将旋转事件由底层上传到FW处理,改变Configuration中orientation的值并将事件继续上传:
Configuration中对屏幕方向的定义:
/**
* Overall orientation of the screen. May be one of
* {@link #ORIENTATION_LANDSCAPE}, {@link #ORIENTATION_PORTRAIT}.
*/
public int orientation;
调用ActivityManagerNative.getDefault().updatePersistentConfiguration(newConfig)将事件上传
2.ActivityManagerNative中使用远程代理通过Binder调用AMS的同名方法updatePersistentConfiguration
(远程代理这块不作深入了解)
public void updatePersistentConfiguration(Configuration values) throws RemoteException
{
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
values.writeToParcel(data, 0);
mRemote.transact(UPDATE_PERSISTENT_CONFIGURATION_TRANSACTION, data, reply, 0);
reply.readException();
data.recycle();
reply.recycle();
}
然后在AMS中,遍历每一个最近运行的程序,同步顺序执行以下方法
/**
* 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 starting is being destroyed to match the new
* configuration.
* @param persistent TODO
*/
boolean updateConfigurationLocked(Configuration values, ActivityRecord starting, boolean persistent, boolean initLocale) {
...
mSystemThread.applyConfigurationToResources(configCopy);
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) {
}
}
...
}
3.通过代理对每个进程上传事件
得到远程服务对象在ActivityManagerService在本地的代理,最终调用了AMS的updateConfiguration()来更新系统Configuration。
其中scheduleConfigurationChanged()实际是通过Binder远程调用(该过程同步)ActivityThread中的同名方法//咳咳:
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();
}
public void scheduleConfigurationChanged(Configuration config) {
updatePendingConfiguration(config);
sendMessage(H.CONFIGURATION_CHANGED, config);
}
4.每个进程进行事件响应
将该进程下执行对应CONFIGURATION_CHANGED处理:
final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
...
mResourcesManager.applyConfigurationToResourcesLocked(config, compat);
...
configDiff = mConfiguration.diff(config);
mConfiguration.updateFrom(config);
config = applyCompatConfiguration(mCurDefaultDisplayDpi);
ArrayList callbacks = collectComponentCallbacks(false, config);
freeTextLayoutCachesIfNeeded(configDiff);
if (callbacks != null) {
final int N = callbacks.size();
for (int i=0; i
5.更新资源
Reload New Resources: 将Config应用到Resource的一系列操作。
public final boolean applyConfigurationToResourcesLocked(Configuration config,
CompatibilityInfo compat) {
... //对Configuration的比较以及更新到Resource
Resources.updateSystemConfiguration(config, defaultDisplayMetrics, compat);
ApplicationPackageManager.configurationChanged();//清空Icon和String缓存
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) {
...//更新Resource的Config
} else {
//Slog.i(TAG, "Removing old resources " + v.getKey());
mActiveResources.removeAt(i);
}
}
return changes != 0;
}
7.8.更新资源Res
在Resource中更新资源,在重新加载时就会使用新的资源
public void updateConfiguration(Configuration config, DisplayMetrics metrics, CompatibilityInfo compat) {
synchronized (mAccessLock) {
...
//更新Resource指向
//{@kth add 20151127 start
//像字体大小切换、语言切换等都会在此处开始更新资源的指向
//kth add 20151127 end@}
...
//清空drawable资源
clearDrawableCachesLocked(mDrawableCache, configChanges);
clearDrawableCachesLocked(mColorDrawableCache, configChanges);
mAnimatorCache.onConfigurationChange(configChanges);
mStateListAnimatorCache.onConfigurationChange(configChanges);
mColorStateListCache.clear();
flushLayoutCache();
}
...
}
10.回调反馈
当Configuration的操作执行完后,实现了ComponentCallbacks2接口的组件如Activity、Services、Application等将会执行回调onConfigurationChanged()方法(接口回调),从而实现正在运行的app中所有组件对Config的更新响应。针对屏幕旋转更新前台显示,其他Configuration如字体、语言等需要通知所有。
该方法针对同一进程下Activity的状态进行甄别,将符合条件的Act放入list以方便后面操作.。
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;
...
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 (shouldChangeConfig) {
cb.onConfigurationChanged(config);
if (activity != null) {
...
activity.mCurrentConfig = new Configuration(config);
}
}
}
Activity、Service、Application、Provider同样实现了ComponentCallbacks接口,从而实现四大组件全部更新状态和资源
ArrayList collectComponentCallbacks(boolean allActivities, Configuration newConfig) {
ArrayList callbacks= new ArrayList();
synchronized (mResourcesManager) {
final int NAPP = mAllApplications.size();// Application
for (int i=0; i