旋屏事件上报流程分析

来源:互联网 发布:淘宝男包推荐 编辑:程序博客网 时间:2024/04/29 18:16
2016-9-1:无意间发现的这篇Blog,在知识点深度、广度都比我这篇厉害,推荐大家去看。而且文章条理性非常明显,值得学习。
http://blog.csdn.net/jinzhuojun/article/details/50085491

近期手头处理了一个横竖屏切换的问题单,特地把这期间了解的旋屏事件上报流程给记录了下来。跟踪了一下源码(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这个变量。
在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类。

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的内部类,它实现了接口。
@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,

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 <var>starting</var> 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。

IApplicationThread是一个aidl文件。这里最终调用的是ApplicationThread对象,而ApplicationThread类是ActivityThread的一个内部类。

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

    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重新创建。
0 0
原创粉丝点击