ConfigurationChanged流程梳理(屏幕旋轉、語言及字體切換)

關鍵字Configuration,屏幕旋轉,語言切換,字體切換, 源碼

前段時間做了關于系統字體切換的功能,其中涉及到較多ConfigurationChanged的流程。屏幕旋轉、mcc、系統語言切換等均是通過該流程來實現的。
網上少有這方面的描述,故將該部分總結提煉出來做個小結。
以下以屏幕旋轉為例。關于屏幕旋轉對于Activity的生命周期的影響也在以下可以體現,如何處理屏幕旋轉對Act的影響也有啟示作用。

ConfigurationChange Flow

講太多也不如一個圖來得實在,下圖也是整個ConfigurationChange通用流程的概括,該流程是基于Android 5.1 和6.0畫出,kk版本應該也是差不多的

Configuration Flow.png

流程跟蹤

下面是對屏幕旋轉的事件跟蹤,盡量干貨.
注:有序列表標號代表對應上圖中的時序節點

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源碼分析(一) - 語言切換機制
謝捧場,如有疏漏謬誤還請指正
=。=

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容