Android电源管理之Doze模式专题系列(六)

状态切换剖析之SENSING-->LOCATION

Posted by Cheson on March 15, 2017

  状态切换剖析之IDLE_PENDING–>SENSING的最后部分提到了从SENSING状态向LOCATION切换的一个入口,这篇将接上文来继续阐述SENSING状态是如何切换到LOCATION的。
  在SENSING状态下最后所做的一件事就是启动了MotionDector来监测是否设备是否有位移发生,监测的结果会通过回调函数返回。如果状态是RESULT_STATIONARY并且当前状态为SENSING那么将调用stepIdleStateLocked来开始切换。

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

  在stepIdleStateLocked方法中判断当前为SENSING的状态,然后做了几件事:1)修改当前状态;2)设置Alarm;3)设置位置监听

    case STATE_SENSING:
        mState = STATE_LOCATING;
        if (DEBUG) Slog.d(TAG, "Moved from STATE_SENSING to STATE_LOCATING.");
        EventLogTags.writeDeviceIdle(mState, "step");
        cancelSensingAlarmLocked();
        scheduleSensingAlarmLocked(mConstants.LOCATING_TIMEOUT);
        mLocating = true;
        mLocationManager.requestLocationUpdates(mLocationRequest, mGenericLocationListener,
                        mHandler.getLooper());
        if (mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
            mHaveGps = true;
            mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 5,
                    mGpsLocationListener, mHandler.getLooper());
        } else {
            mHaveGps = false;
        }
        break;

  第一点,也就是简单的将变量mState的值赋值为了STATE_LOCATING。第二点做的事一如前面几种状态中做过的一样,设置一个Alarm来作为下个状态切换的触发器。在设置之前首先要cancel以前的Alarm,这个被取消的广播类型就是ACTION_STEP_IDLE_STATE,也就是触发状态切换的广播。然后调用scheduleSensingAlarmLocked(mConstants.LOCATING_TIMEOUT)方法来重新设置Alarm,这个Alarm的deley时间就是LOCATING_TIMEOUT。

    LOCATING_TIMEOUT = mParser.getLong(KEY_LOCATING_TIMEOUT,
        !DEBUG ? 30 * 1000L : 15 * 1000L);

  默认DEBUG为false,所以这个timeout默认为30秒,后面也会介绍到在这30秒的时间内用来检测设备的运动状态而在scheduleSensingAlarmLocked方法中看到又进行了一次取消alarm的操作,这里是不是多余的呢?之前刚进行过一次取消。

    void scheduleSensingAlarmLocked(long delay) {
        if (DEBUG) Slog.d(TAG, "scheduleSensingAlarmLocked(" + delay + ")");
        cancelSensingAlarmLocked();
        mNextAlarmTime = SystemClock.elapsedRealtime() + delay;
        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
            mNextAlarmTime, mSensingAlarmIntent);
        mSensing = true;
    }

  第三点就是设置位置监听,首先将mLocating标志位置为true,也就是标示注册了位置监听服务,在后面的取消位置监听时作为标志位来判断。然后通过LocationManager了请求了位置监听服务,先是申请了通过手机本身传感器精度为ACCURACY_FINE的定位服务

    mLocationRequest = new LocationRequest()
            .setQuality(LocationRequest.ACCURACY_FINE)
            .setInterval(0)
            .setFastestInterval(0)
            .setNumUpdates(1);

  而监听器的定义如下

    private final LocationListener mGenericLocationListener = new LocationListener() {
        @Override
        public void onLocationChanged(Location location) {
            synchronized (DeviceIdleController.this) {
                receivedGenericLocationLocked(location);
            }
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {
        }

        @Override
        public void onProviderEnabled(String provider) {
        }

        @Override
        public void onProviderDisabled(String provider) {
        }
    };

  在触发位置变化时去调用receivedGenericLocationLocked(location)

    void receivedGenericLocationLocked(Location location) {
        if (mState != STATE_LOCATING) {
            cancelLocatingLocked();
            return;
        }
        if (DEBUG) Slog.d(TAG, "Generic location: " + location);
        mLastGenericLocation = new Location(location);
        if (location.getAccuracy() > mConstants.LOCATION_ACCURACY && mHaveGps) {
            return;
        }
        mLocated = true;
        if (mNotMoving) {
            stepIdleStateLocked();
        }
    }

  如果开启了Gps的定位监测,并且精度大于LOCATION_ACCURACY,那么这里的监测就忽略(用更精确的GPS数据),用Gps的监测数据,在后面会讲到。否则,如果监测数据为Not move,那么就调用StepIdleStateLocked来进行下一步。在此同时,也去检测了是否支持GPS

    mLocationManager.requestLocationUpdates(mLocationRequest, mGenericLocationListener,
                        mHandler.getLooper());
    if (mLocationManager.getProvider(LocationManager.GPS_PROVIDER) != null) {
        mHaveGps = true;
        mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 5,
                mGpsLocationListener, mHandler.getLooper());
    } else {
        mHaveGps = false;
    }

  如果支持的话就申请注册GPS的位置监测服务,以提供准确度。同时前面ACCURACY_FINE的定位服务实际上就被架空了。来看下这个监听器的定义。

    private final LocationListener mGpsLocationListener = new LocationListener() {
    @Override
    public void onLocationChanged(Location location) {
        synchronized (DeviceIdleController.this) {
            receivedGpsLocationLocked(location);
        }
    }

  重点还是onLocationChanged时调用的receivedGpsLocationLocked方法,和receivedGenericLocationLocked类似

    void receivedGpsLocationLocked(Location location) {
        if (mState != STATE_LOCATING) {
            cancelLocatingLocked();
            return;
        }
        if (DEBUG) Slog.d(TAG, "GPS location: " + location);
        mLastGpsLocation = new Location(location);
        if (location.getAccuracy() > mConstants.LOCATION_ACCURACY) {
            return;
        }
        mLocated = true;
        if (mNotMoving) {
            stepIdleStateLocked();
        }
    }

  和前面的类似,在结果为not move的时候调用stepIdleStateLocked来进入下一步操作。
  至此从SENSING状态切换到LOCATION状态的动作就完成了。