依照惯例,在介绍下一个状态切换之前,先来回顾一下上一个状态中的伏笔。在 Doze模式专题(四)——状态切换剖析之INACTIVE–>IDLE_PENDING 这篇中讲到了状态切换到IDLE_PENDING时需要做的事情,其中之一就是调用了scheduleAlarmLocked(mConstants.IDLE_AFTER_INACTIVE_TIMEOUT, false)设定了一个Alarm来在30分钟之后作为下一个状态切换的发起。于是,下面的故事就发生在这个动作的30分钟之后。
当Alarm被触发时,Broadcast将再次接收到ACTION_STEP_IDLE_STATE这个广播,然后调用stepIdleStateLocked()这个核心方法来进行状态切换的一系列处理。我们来看一下当前状态为STATE_IDLE_PENDING时会怎么样进行判断和切换。
case STATE_IDLE_PENDING:
mState = STATE_SENSING;
if (DEBUG) Slog.d(TAG, "Moved from STATE_IDLE_PENDING to STATE_SENSING.");
EventLogTags.writeDeviceIdle(mState, "step");
scheduleSensingAlarmLocked(mConstants.SENSING_TIMEOUT);
cancelSensingAlarmLocked();
cancelLocatingLocked();
mAnyMotionDetector.checkForAnyMotion();
mNotMoving = false;
mLocated = false;
mLastGenericLocation = null;
mLastGpsLocation = null;
break;
首先将当前的状态mState改写为了STATE_SENSING,然后还是调用了scheduleSensingAlarmLocked来设定了一个4分钟的Alarm,看过前几个状态切换的很容易就明白这个Alarm一定是为了触发下一个状态的切换。然而下一个步骤就有点难以理解了,紧接着又调用了cancelSensingAlarmLocked方法。
void cancelSensingAlarmLocked() {
if (mSensing) {
if (DEBUG) Slog.d(TAG, "cancelSensingAlarmLocked()");
mAlarmManager.cancel(mSensingAlarmIntent);
mSensing = false;
}
}
而这个方法取消的Alarm正是中的Intent正是前面设置的mSensingAlarmIntent。这么来看在IDLE_PENDING切换到SENSING状态时,设定了一个Alarm然后又立即取消了,这么来看不是做了两个无效的动作吗?没错,代码看到这里我也没明白这个意义,继续往下看。然后调用了cancelLocatingLocked来取消了位置监听。
void cancelLocatingLocked() {
if (mLocating) {
mLocationManager.removeUpdates(mGenericLocationListener);
mLocationManager.removeUpdates(mGpsLocationListener);
mLocating = false;
}
}
其实这mGenericLocationListener和mGpsLocationListener这两个监听器是在SENSING状态时才被注册的,所以这边即将要进入SENSING之前需要先重置下状态。然后调用了mAnyMotionDetector.checkForAnyMotion(),这个函数在AnyMotionDetector.java中
public void checkForAnyMotion() {
if (DEBUG) Slog.d(TAG, "checkForAnyMotion(). mState = " + mState);
if (mState != STATE_ACTIVE) {
mState = STATE_ACTIVE;
if (DEBUG) Slog.d(TAG, "Moved from STATE_INACTIVE to STATE_ACTIVE.");
mCurrentGravityVector = null;
mPreviousGravityVector = null;
startOrientationMeasurement();
}
}
这里核心的步骤就是掉用startOrientationMeasurement来进行行为的检测
private void startOrientationMeasurement() {
if (DEBUG) Slog.d(TAG, "startOrientationMeasurement: mMeasurementInProgress=" +
mMeasurementInProgress + ", (mAccelSensor != null)=" + (mAccelSensor != null));
if (!mMeasurementInProgress && mAccelSensor != null) {
if (mSensorManager.registerListener(mListener, mAccelSensor,
SAMPLING_INTERVAL_MILLIS * 1000)) {
mWakeLock.acquire();
mMeasurementInProgress = true;
mRunningStats.reset();
}
Message msg = Message.obtain(mHandler, mMeasurementTimeout);
msg.setAsynchronous(true);
mHandler.sendMessageDelayed(msg, ACCELEROMETER_DATA_TIMEOUT_MILLIS);
}
}
这里注册了一个加速度计的监听器,来检测设备的行动状态。
private final SensorEventListener mListener = new SensorEventListener() {
@Override
public void onSensorChanged(SensorEvent event) {
int status = RESULT_UNKNOWN;
synchronized (mLock) {
Vector3 accelDatum = new Vector3(SystemClock.elapsedRealtime(), event.values[0],
event.values[1], event.values[2]);
mRunningStats.accumulate(accelDatum);
// If we have enough samples, stop accelerometer data acquisition.
if (mRunningStats.getSampleCount() >= mNumSufficientSamples) {
status = stopOrientationMeasurementLocked();
}
}
if (status != RESULT_UNKNOWN) {
mCallback.onAnyMotionResult(status);
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
};
在最后检测的状态不等于RESULT_UNKNOWN会回调onAnyMotionResult来返回结果,这个回调函数又会把流程带回到DeviceIdleController中。然后状态切换即将开始。
@Override
public void onAnyMotionResult(int result) {
if (DEBUG) Slog.d(TAG, "onAnyMotionResult(" + result + ")");
if (result == AnyMotionDetector.RESULT_MOVED) {
if (DEBUG) Slog.d(TAG, "RESULT_MOVED received.");
synchronized (this) {
handleMotionDetectedLocked(mConstants.INACTIVE_TIMEOUT, "sense_motion");
}
} else if (result == AnyMotionDetector.RESULT_STATIONARY) {
if (DEBUG) Slog.d(TAG, "RESULT_STATIONARY received.");
if (mState == STATE_SENSING) {
// If we are currently sensing, it is time to move to locating.
synchronized (this) {
mNotMoving = true;
stepIdleStateLocked();
}
} else if (mState == STATE_LOCATING) {
// If we are currently locating, note that we are not moving and step
// if we have located the position.
synchronized (this) {
mNotMoving = true;
if (mLocated) {
stepIdleStateLocked();
}
}
}
}
}
当检测结果为MOVED时,调用handleMotionDetectedLocked重新去将状态切成ACTIVE或者INACTIVE(满足INACTIVE条件时),如果结果为RESULT_STATIONARY,当前状态为SENSING时,就调用stopIdleStateLocked来进行状态切换,也就是进入到LOCATING状态,那么如果当前为LOCATING时,需要另外对mLocated变量进行判断才决定是否进行下一次切换,这个状态切换看下一篇。
讲到这里也就已经剖析了从IDLE_PENDING到SENSING的切换,以及在SENSING状态时所做的时和下一次切换的准备。那么再回过头来看下最开始的疑问,在进入SENSING状态时为什么设置了一个ALARM来作为状态切换的唤醒,然后又取消了?因为SENSING状态到下一个状态的切换发起者不是由ALARM来唤醒的,而是根据onAnyMotionResult这个回调函数的结果来处理,所以之前设置的Alarm根本就没有用是可以取消掉的,然后为什么要多此一举呢?也没想明白设计者的目的,也许是为了和之前的几个状态切换保持一致的风格吧,或者以后会更明白的。
参考资料:
eCourse:M Doze&AppStandby
eService:关于doze模式是否支持的疑问