關鍵字: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 <var>starting</var> 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<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);
}
}
}
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<ComponentCallbacks2> collectComponentCallbacks(boolean allActivities, Configuration newConfig) {
ArrayList<ComponentCallbacks2> callbacks= new ArrayList<ComponentCallbacks2>();
synchronized (mResourcesManager) {
final int NAPP = mAllApplications.size();// Application
for (int i=0; i<NAPP; i++) {
callbacks.add(mAllApplications.get(i));
}
final int NACT = mActivities.size();// Activity
for (int i=0; i<NACT; i++) {
ActivityClientRecord ar = mActivities.valueAt(i);
Activity a = ar.activity;
if (a != null) {
Configuration thisConfig = applyConfigCompatMainThread(
mCurDefaultDisplayDpi, newConfig,
ar.packageInfo.getCompatibilityInfo());
if (!ar.activity.mFinished && (allActivities || !ar.paused)) {
// If the activity is currently resumed, its configuration
// needs to change right now.
callbacks.add(a);
} else if (thisConfig != null) {
// Otherwise, we will tell it about the change
// the next time it is resumed or shown. Note that
// the activity manager may, before then, decide the
// activity needs to be destroyed to handle its new
// configuration.
if (DEBUG_CONFIGURATION) {
Slog.v(TAG, "Setting activity " + ar.activityInfo.name + " newConfig=" + thisConfig);
}
ar.newConfig = thisConfig;
}
}
}
final int NSVC = mServices.size();// Service
for (int i=0; i<NSVC; i++) {
callbacks.add(mServices.valueAt(i));
}
}
synchronized (mProviderMap) {
final int NPRV = mLocalProviders.size();// Provider
for (int i=0; i<NPRV; i++) {
callbacks.add(mLocalProviders.valueAt(i).mLocalProvider);
}
}
return callbacks;
}
11.四大組件事件響應
如果在manifest.xml中配置了configChnages屬性則表示由app自己來處理configuration change,就會回調Activity等組件的onConfigurationChanged方法。否則就重啟當前這個activity(這個重啟步驟位于當activity回到前臺時執行onDestroy->onStart->onResume),而重啟之前,舊的resources已經被清空, 那么就會裝載新的資源。對于未啟動的應用則會在啟動時加載新的資源。參考如下:
(引自Activity.java)
/**
* 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.
*/
順道貼下ConfigurationChanged時Act的生命周期以便理解:
01-02 18:01:44.039 11305 11305 E MainActivity: onPause
01-02 18:01:44.040 11305 11305 E MainActivity: onSaveInstanceState
01-02 18:01:44.040 11305 11305 E MainActivity: onStop
01-02 18:01:44.040 11305 11305 E MainActivity: onDestroy
01-02 18:01:44.063 11305 11305 E MainActivity: onStart
01-02 18:01:44.064 11305 11305 E MainActivity: onRestoreInstanceState
01-02 18:01:44.064 11305 11305 E MainActivity: onResume
小結
以上就是Configuration時間的流程梳理,好記性不如爛筆頭。主要是流程時序圖麻煩,從自己知道到寫下來中間也來回經歷好些個小時翻源碼。
記錄以供需要的人參考。
Fucking The Source Code
附上參考過的csdn鏈接
七號大蒜
android源碼分析(一) - 語言切換機制
謝捧場,如有疏漏謬誤還請指正
=。=