Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 旋屏事件上報流程分析

旋屏事件上報流程分析

編輯:關於Android編程

近期手頭處理了一個橫豎屏切換的問題單,特地把這期間了解的旋屏事件上報流程給記錄了下來。跟蹤了一下源碼(Android 6.0)   WindowManagerService.java (android-6.0\frameworks\base\services\core\java\com\android\server\wm)
    private void initPolicy() {
        UiThread.getHandler().runWithScissors(new Runnable() {
            @Override
            public void run() {
                WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());

                mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);
            }
        }, 0);
    }
在WMS構造方法中就調用initPolicy方法。在這個方法中,很明顯是在初始化mPolicy:WindowManagerPolicy這個變量。 final WindowManagerPolicy mPolicy = new PhoneWindowManager(); 在WMS中,該變量實質是PhoneWindowManager對象。PhoneWindowManager類實現了WindowManagerPolicy接口類。       PhoneWindowManager.java (android-6.0\frameworks\base\services\core\java\com\android\server\policy)
   /** {@inheritDoc} */
    @Override
    public void init(Context context, IWindowManager windowManager,
            WindowManagerFuncs windowManagerFuncs) {
        mContext = context;
        mWindowManager = windowManager;
        mWindowManagerFuncs = windowManagerFuncs;
......
        mOrientationListener = new MyOrientationListener(mContext, mHandler);
        try {
            mOrientationListener.setCurrentRotation(windowManager.getRotation());
        } catch (RemoteException ex) { }
......
    }
發現在初始化的過程中,實例化一個方位監聽對象MyOrientationListener,並且給它設置了初始值。該初始值從WMS中獲取,默認為0。 MyOrientationListener類是PhoneWindowManager的一個內部類,它繼承了WindowOrientationListener類。 class MyOrientationListener extends WindowOrientationListener {       WindowOrientationListener.java (android-6.0\frameworks\base\services\core\java\com\android\server\policy)
    private WindowOrientationListener(Context context, Handler handler, int rate) {
        mHandler = handler;
        mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
        mRate = rate;
        mSensor = mSensorManager.getDefaultSensor(USE_GRAVITY_SENSOR
                ? Sensor.TYPE_GRAVITY : Sensor.TYPE_ACCELEROMETER);
        if (mSensor != null) {
            // Create listener only if sensors do exist
            mSensorEventListener = new SensorEventListenerImpl(context);
        }
    }
看到這邊,一下子就明白旋屏事件上報的大致流程。首先由傳感器計算數據確認是否上報,然後通過Handler或者回調方法來處理。這麼想是因為構造器中傳遞進來一個Handler對象,另外本身就是通過其子類調用才進入WindowOrientationListener。具體是怎麼一個流程,還要分析接下來的代碼。 在這裡,默認采用的傳感器是加速度傳感器,USE_GRAVITY_SENSOR:false 在Android7.0中,默認采用的是方向傳感器。 WindowOrientationListener構造器中,mSensorManager、mSensor、mSensorEventListener對象。在後面的enable方法中,通過mSensorManager調用registerListener為mSensor注冊監聽事件mSensorEventListener。    
     * Enables the WindowOrientationListener so it will monitor the sensor and call
     * {@link #onProposedRotationChanged(int)} when the device orientation changes.
     */
    public void enable() {
......
                mSensorManager.registerListener(mSensorEventListener, mSensor, mRate, mHandler);
                mEnabled = true;
            }
        }
    }
這裡不關注這些個點,還是去看傳感器監聽處理這一塊內容。     SensorEventListenerImpl是WindowOrientationListener的內部類,它實現了接口。 final class SensorEventListenerImpl implements SensorEventListener {
        @Override
        public void onSensorChanged(SensorEvent event) {
            int proposedRotation;
            int oldProposedRotation;

            synchronized (mLock) {
                // The vector given in the SensorEvent points straight up (towards the sky) under
                // ideal conditions (the phone is not accelerating).  I'll call this up vector
                // elsewhere.
                float x = event.values[ACCELEROMETER_DATA_X];
                float y = event.values[ACCELEROMETER_DATA_Y];
                float z = event.values[ACCELEROMETER_DATA_Z];
                }
            }
......
            // Tell the listener.
            if (proposedRotation != oldProposedRotation && proposedRotation >= 0) {
                if (LOG) {
                    Slog.v(TAG, "Proposed rotation changed!  proposedRotation=" + proposedRotation
                            + ", oldProposedRotation=" + oldProposedRotation);
                }
                onProposedRotationChanged(proposedRotation);
            }
        }
傳感器數據計算過程這裡省略了,在onSensorChanged方法的最後通過函數回調上報旋屏事件。回顧上面的內容,驗證的確如此。    
    /**
     * Called when the rotation view of the device has changed.
     *
     * This method is called whenever the orientation becomes certain of an orientation.
     * It is called each time the orientation determination transitions from being
     * uncertain to being certain again, even if it is the same orientation as before.
     *
     * @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants.
     * @see android.view.Surface
     */
    public abstract void onProposedRotationChanged(int rotation);
onProposedRotationChanged方法是聲明在WindowOrientationListener類的一個抽象方法,它具體實現在PhoneWindowManager的一個內部類,即MyOrientationListener。       PhoneWindowManager.java (android-6.0\frameworks\base\services\core\java\com\android\server\policy)
        @Override
        public void onProposedRotationChanged(int rotation) {
            if (localLOGV) Slog.v(TAG, "onProposedRotationChanged, rotation=" + rotation);
            updateRotation(false);
        }

 
    void updateRotation(boolean alwaysSendConfiguration) {
        try {
            //set orientation on WindowManager
            mWindowManager.updateRotation(alwaysSendConfiguration, false); //false、false
        } catch (RemoteException e) {
            // Ignore
        }
    }
很明顯,是通知WMS更新rotation。       WindowManagerService.java (android-6.0\frameworks\base\services\core\java\com\android\server\wm)
    /**
     * Recalculate the current rotation.
     *
     * Called by the window manager policy whenever the state of the system changes
     * such that the current rotation might need to be updated, such as when the
     * device is docked or rotated into a new posture.
     */
    @Override
    public void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout) {
        updateRotationUnchecked(alwaysSendConfiguration, forceRelayout);
    }

 
    public void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
......
        boolean changed;
        synchronized(mWindowMap) {
            changed = updateRotationUncheckedLocked(false);
......
        if (changed || alwaysSendConfiguration) {
            sendNewConfiguration();
        }
......
    }
一般情況,rotation都是發生變化的,也就是說updateRotationUncheckedLocked返回值通常為true,故會調用sendNewConfiguration。    
    /*
     * Instruct the Activity Manager to fetch the current configuration and broadcast
     * that to config-changed listeners if appropriate.
     */
    void sendNewConfiguration() {
        try {
            mActivityManager.updateConfiguration(null);
        } catch (RemoteException e) {
        }
    }
mActivityManager本質是AMS Server端,這裡從WMS運行至AMS, mActivityManager = ActivityManagerNative.getDefault();       ActivityManagerService.java (android-6.0\frameworks\base\services\core\java\com\android\server\am)
    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();
            }

            updateConfigurationLocked(values, null, false, false);
......
        }
    }
AMS中,第一步:檢查權限,沒有權限則拋一個異常;第二步:從WMS中獲取Configuration值;第三步:去真正更新Configuration值。    
    /**
     * 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,
            ctivityRecord starting, boolean persistent, boolean initLocale) {
        int changes = 0;

        if (values != null) {
            Configuration newConfig = new Configuration(mConfiguration);
            changes = newConfig.updateFrom(values);
......
                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_CONFIGURATION, "Sending to proc "
                                    + app.processName + " new config " + mConfiguration);
                            app.thread.scheduleConfigurationChanged(configCopy);
                        }
                    } catch (Exception e) {
                    }
                }
......
        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;
    }
通常當發生橫豎屏切換的時候,Activity的生命周期通常是:onConfigureationChanged --> onDestroy --> onCreate --> onStart --> onResume。一看這裡就分為兩步驟,一:通知Configuration已經改變;二:獲取棧頂的Activity,重新運行該Activity,以適配新的Configuration。     app:ProcessRecord IApplicationThread thread; // the actual proc... may be null only if // 'persistent' is true (in which case we // are in the process of launching the app) IApplicationThread是一個aidl文件。這裡最終調用的是ApplicationThread對象,而ApplicationThread類是ActivityThread的一個內部類。 private class ApplicationThread extends ApplicationThreadNative {   ActivityThread.java (android-6.0\frameworks\base\core\java\android\app)
        public void scheduleConfigurationChanged(Configuration config) {
            updatePendingConfiguration(config);
            sendMessage(H.CONFIGURATION_CHANGED, config);
        }
直接通過Handler機制與ActivityThread進行通信。    
        public void handleMessage(Message msg) {
......
                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;
            }
            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
        }
 
    final void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
......
        ArrayList callbacks = collectComponentCallbacks(false, config);

        freeTextLayoutCachesIfNeeded(configDiff);

        if (callbacks != null) {
            final int N = callbacks.size();
            for (int i=0; i



    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 (shouldChangeConfig) {
            cb.onConfigurationChanged(config);
......
    }
很明顯,在這裡調用了onConfigurationChanged方法。也就是常說在橫豎屏切換的時候先調用Activity的onConfigurationChanged,通常會重寫這個方法,做一些保存參數之類的操作。     在調用完onConfigurationChanged後,Activity會重新創建。所以就回到AMS的updateConfigurationLocked方法中。   kept = mainStack.ensureActivityConfigurationLocked(starting, changes); mainStack:ActivityStack。   ActivityStack.java (android-6.0\frameworks\base\services\core\java\com\android\server\am)
    /**
     * Make sure the given activity matches the current configuration.  Returns
     * false if the activity had to be destroyed.  Returns true if the
     * configuration is the same, or the activity will remain running as-is
     * for whatever reason.  Ensures the HistoryRecord is updated with the
     * correct configuration and all other bookkeeping is handled.
     */
    final boolean ensureActivityConfigurationLocked(ActivityRecord r,
            int globalChanges) {
......
        if ((changes&(~r.info.getRealConfigChanged())) != 0 || r.forceNewConfig) {
            // Aha, the activity isn't handling the change, so DIE DIE DIE.
            r.configChangeFlags |= changes;
            r.startFreezingScreenLocked(r.app, globalChanges);
            r.forceNewConfig = false;
            if (r.app == null || r.app.thread == null) {
                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
                        "Config is destroying non-running " + r);
                destroyActivityLocked(r, true, "config");
            } else if (r.state == ActivityState.PAUSING) {
                // A little annoying: we are waiting for this activity to
                // finish pausing.  Let's not do anything now, but just
                // flag that it needs to be restarted when done pausing.
                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
                        "Config is skipping already pausing " + r);
                r.configDestroy = true;
                return true;
            } else if (r.state == ActivityState.RESUMED) {
                // Try to optimize this case: the configuration is changing
                // and we need to restart the top, resumed activity.
                // Instead of doing the normal handshaking, just say
                // "restart!".
                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
                        "Config is relaunching resumed " + r);
                relaunchActivityLocked(r, r.configChangeFlags, true);
                r.configChangeFlags = 0;
            } else {
                if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
                        "Config is relaunching non-resumed " + r);
                relaunchActivityLocked(r, r.configChangeFlags, false);
                r.configChangeFlags = 0;
            }

            // All done...  tell the caller we weren't able to keep this
            // activity around.
            return false;
        }
......
        return true;
    }
   
    private boolean relaunchActivityLocked(ActivityRecord r, int changes, boolean andResume) {
......
        try {
            if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_SWITCH,
                    "Moving to " + (andResume ? "RESUMED" : "PAUSED") + " Relaunching " + r);
            r.forceNewConfig = false;
            r.app.thread.scheduleRelaunchActivity(r.appToken, results, newIntents, changes,
                    !andResume, new Configuration(mService.mConfiguration),
                    new Configuration(mOverrideConfig));
            // Note: don't need to call pauseIfSleepingLocked() here, because
            // the caller will only pass in 'andResume' if this activity is
            // currently resumed, which implies we aren't sleeping.
        } catch (RemoteException e) {
            if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_SWITCH, "Relaunch failed", e);
        }
......
        return true;
    }
一眼就看明白,同樣是在ApplicationThread內部通過Handler與ActivityThead進行通信,然後真正去執行relaunch操作。       總結: 旋屏事件上報流程: 1、傳感器(默認為加速度傳感器)計算數據,決定是否上報旋屏事件。 2、上報是通過回調函數實現的,在PhoneWindowManger中實現指定接口。 3、PhoneWindowManger與WMS進行交互,通知其更新rotation。 4、WMS更新rotation後,發現的確發生改變了,去通知AMS處理。 5、AMS獲取WMS中rotation數據,然後更新處理。通常流程是通過ApplicationThread與ActivityThread交互。最後調用流程是 onConfigurationChanged --> Activity重新創建。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved