我们经常会看到微信 QQ 以及其他一些运动app里面都有一个计步功能,那它是怎么实现的呢?
今天我们就来实现一下,以下代码都是从一个整体项目中抽离出来的,为了理解简单方便我把UI部分数据保存部分全部都去掉了,只有单纯的计步逻辑和算法。
log日志显示计步:
编写计步逻辑的流程图,方便理解思路:
MainActivity :
public class MainActivity extends AppCompatActivity {
private BindService bindService;
private TextView textView;
private boolean isBind;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
textView.setText(msg.arg1 + "");
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.busu);
Intent intent = new Intent(MainActivity.this, BindService.class);
isBind = bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
startService(intent); //绷定并且开启一个服务,绷定是为了方便数据交换,启动是为了当当前app不在活动页的时候,计步服务不会被关闭。需要保证当activity不为活跃状态是计步服务在后台能一直运行!
}
//和绷定服务数据交换的桥梁,可以通过IBinder service获取服务的实例来调用服务的方法或者数据
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
BindService.LcBinder lcBinder = (BindService.LcBinder) service;
bindService = lcBinder.getService();
bindService.registerCallback(new UpdateUiCallBack() {
@Override
public void updateUi(int stepCount) {
//当前接收到stepCount数据,就是最新的步数
Message message = Message.obtain();
message.what = 1;
message.arg1 = stepCount;
handler.sendMessage(message);
Log.i("MainActivity—updateUi","当前步数"+stepCount);
}
});
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onStart() {
super.onStart();
}
@Override
public void onDestroy() { //app被关闭之前,service先解除绑定,如果不解除绑定下次Activity切换到活动界面的时候又会重新开启一个新的计步线程。
super.onDestroy();
if (isBind) {
this.unbindService(serviceConnection);
}
}
}
activity绷定并且开启的服务:当前服务实现了SensorEventListener接口,SensorEventListener接口是计步传感器的一个回调接口。
@Override
public void onCreate() {
super.onCreate();
Log.i("BindService—onCreate", "开启计步");
new Thread(new Runnable() {
@Override
public void run() {
startStepDetector();
Log.i("BindService—子线程", "startStepDetector()");
}
}).start();
}
/**
* 选择计步数据采集的传感器
* SDK大于等于19,开启计步传感器,小于开启加速度传感器
*/
private void startStepDetector() {
if (sensorManager != null) {
sensorManager = null;
}
//获取传感器管理类
sensorManager = (SensorManager) this.getSystemService(SENSOR_SERVICE);
int versionCodes = Build.VERSION.SDK_INT;//取得SDK版本
if (versionCodes >= 19) {
//SDK版本大于等于19开启计步传感器
addCountStepListener();
} else { //小于就使用加速度传感器
addBasePedometerListener();
}
}
/**
* 启动计步传感器计步
*/
private void addCountStepListener() {
Sensor countSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
Sensor detectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
if (countSensor != null) {
stepSensorType = Sensor.TYPE_STEP_COUNTER;
sensorManager.registerListener(BindService.this, countSensor, SensorManager.SENSOR_DELAY_NORMAL);
Log.i("计步传感器类型", "Sensor.TYPE_STEP_COUNTER");
} else if (detectorSensor != null) {
stepSensorType = Sensor.TYPE_STEP_DETECTOR;
sensorManager.registerListener(BindService.this, detectorSensor, SensorManager.SENSOR_DELAY_NORMAL);
} else {
addBasePedometerListener();
}
}
/**
* 启动加速度传感器计步
*/
private void addBasePedometerListener() {
Log.i("BindService", "加速度传感器");
mStepCount = new StepCount();
mStepCount.setSteps(nowBuSu);
//获取传感器类型 获得加速度传感器
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
//此方法用来注册,只有注册过才会生效,参数:SensorEventListener的实例,Sensor的实例,更新速率
boolean isAvailable = sensorManager.registerListener(mStepCount.getStepDetector(), sensor, SensorManager.SENSOR_DELAY_UI);
mStepCount.initListener(new StepValuePassListener() {
@Override
public void stepChanged(int steps) {
nowBuSu = steps;//通过接口回调获得当前步数
updateNotification(); //更新步数通知
}
});
}
/**
* 通知调用者步数更新 数据交互
*/
private void updateNotification() {
if (mCallback != null) {
Log.i("BindService", "数据更新");
mCallback.updateUi(nowBuSu);
}
}
@Override
public IBinder onBind(Intent intent) {
return lcBinder;
}
/**
* 计步传感器数据变化回调接口
*/
@Override
public void onSensorChanged(SensorEvent event) {
//这种类型的传感器返回步骤的数量由用户自上次重新启动时激活。返回的值是作为浮动(小数部分设置为0),
// 只在系统重启复位为0。事件的时间戳将该事件的第一步的时候。这个传感器是在硬件中实现,预计低功率。
if (stepSensorType == Sensor.TYPE_STEP_COUNTER) {
//获取当前传感器返回的临时步数
int tempStep = (int) event.values[0];
//首次如果没有获取手机系统中已有的步数则获取一次系统中APP还未开始记步的步数
if (!hasRecord) {
hasRecord = true;
hasStepCount = tempStep;
} else {
//获取APP打开到现在的总步数=本次系统回调的总步数-APP打开之前已有的步数
int thisStepCount = tempStep - hasStepCount;
//本次有效步数=(APP打开后所记录的总步数-上一次APP打开后所记录的总步数)
int thisStep = thisStepCount - previousStepCount;
//总步数=现有的步数+本次有效步数
nowBuSu += (thisStep);
//记录最后一次APP打开到现在的总步数
previousStepCount = thisStepCount;
}
}
//这种类型的传感器触发一个事件每次采取的步骤是用户。只允许返回值是1.0,为每个步骤生成一个事件。
// 像任何其他事件,时间戳表明当事件发生(这一步),这对应于脚撞到地面时,生成一个高加速度的变化。
else if (stepSensorType == Sensor.TYPE_STEP_DETECTOR) {
if (event.values[0] == 1.0) {
nowBuSu++;
}
}
updateNotification();
}
/**
* 计步传感器精度变化回调接口
*/
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
/**
* 绑定回调接口
*/
public class LcBinder extends Binder {
BindService getService() {
return BindService.this;
}
}
/**
* 数据传递接口
*
* @param paramICallback
*/
public void registerCallback(UpdateUiCallBack paramICallback) {
this.mCallback = paramICallback;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//返回START_STICKY :在运行onStartCommand后service进程被kill后,那将保留在开始状态,但是不保留那些传入的intent。
// 不久后service就会再次尝试重新创建,因为保留在开始状态,在创建 service后将保证调用onstartCommand。
// 如果没有传递任何开始命令给service,那将获取到null的intent。
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
//取消前台进程
stopForeground(true);
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
}
如果sdk版本大于等于19,到这里计步服务就能向activity反馈步数了。但是如果sdk版本小于19,通过加速度传感器计数步数还要通过算法来获取:
public class StepCount implements StepCountListener {
private int mCount; //当前步数
private int count; //缓存步数,步数3秒内小于10步则不计数
private long timeOfLastPeak = 0;//计时 开始时间 步数3秒内小于10步则不计数
private long timeOfThisPeak = 0;//计时 现在时间 步数3秒内小于10步则不计数
private StepValuePassListener stepValuePassListener;//接口用来传递步数变化
private StepDetector stepDetector;//传感器SensorEventListener子类实例
public StepCount() {
stepDetector = new StepDetector();
stepDetector.initListener(this);
}
@Override
public void countStep() {
this.timeOfLastPeak = this.timeOfThisPeak;
this.timeOfThisPeak = System.currentTimeMillis();
Log.i("countStep","传感器数据刷新回调");
// notifyListener();
if (this.timeOfThisPeak - this.timeOfLastPeak <= 3000L) {
if (this.count < 9) {
this.count++;
} else if (this.count == 9) {
this.count++;
this.mCount += this.count;
notifyListener();
} else {
this.mCount++;
notifyListener();
}
} else {//超时
this.count = 1;//为1,不是0
}
}
public void setSteps(int initNowBusu){
this.mCount = initNowBusu;//接收上层调用传递过来的当前步数
this.count = 0;
timeOfLastPeak = 0;
timeOfThisPeak = 0;
notifyListener();
}
/**
* 用来给调用者获取SensorEventListener实例
* @return 返回SensorEventListener实例
*/
public StepDetector getStepDetector(){
return stepDetector;
}
/**
* 更新步数,通过接口函数通过上层调用者
*/
public void notifyListener(){
if(this.stepValuePassListener != null){
Log.i("countStep","数据更新");
this.stepValuePassListener.stepChanged(this.mCount); //当前步数通过接口传递给调用者
}
}
public void initListener(StepValuePassListener listener){
this.stepValuePassListener = listener;
}
}
@Override
public void onSensorChanged(SensorEvent event) {//当传感器值改变回调此方法
for (int i = 0; i < 3; i++) {
oriValues[i] = event.values[i];
}
gravityNew = (float) Math.sqrt(oriValues[0] * oriValues[0]
+ oriValues[1] * oriValues[1] + oriValues[2] * oriValues[2]);
detectorNewStep(gravityNew);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
//
}
public void initListener(StepCountListener listener) {
this.mStepListeners = listener;
}
/*
* 检测步子,并开始计步
* 1.传入sersor中的数据
* 2.如果检测到了波峰,并且符合时间差以及阈值的条件,则判定为1步
* 3.符合时间差条件,波峰波谷差值大于initialValue,则将该差值纳入阈值的计算中
* */
public void detectorNewStep(float values) {
if (gravityOld == 0) {
gravityOld = values;
} else {
if (detectorPeak(values, gravityOld)) {
timeOfLastPeak = timeOfThisPeak;
timeOfNow = System.currentTimeMillis();
if (timeOfNow - timeOfLastPeak >= TimeInterval
&& (peakOfWave - valleyOfWave >= ThreadValue)) {
timeOfThisPeak = timeOfNow;
/*
* 更新界面的处理,不涉及到算法
* 一般在通知更新界面之前,增加下面处理,为了处理无效运动:
* 1.连续记录10才开始计步
* 2.例如记录的9步用户停住超过3秒,则前面的记录失效,下次从头开始
* 3.连续记录了9步用户还在运动,之前的数据才有效
* */
mStepListeners.countStep();
}
if (timeOfNow - timeOfLastPeak >= TimeInterval
&& (peakOfWave - valleyOfWave >= InitialValue)) {
timeOfThisPeak = timeOfNow;
ThreadValue = peakValleyThread(peakOfWave - valleyOfWave);
}
}
}
gravityOld = values;
}
/*
* 检测波峰
* 以下四个条件判断为波峰:
* 1.目前点为下降的趋势:isDirectionUp为false
* 2.之前的点为上升的趋势:lastStatus为true
* 3.到波峰为止,持续上升大于等于2次
* 4.波峰值大于20
* 记录波谷值
* 1.观察波形图,可以发现在出现步子的地方,波谷的下一个就是波峰,有比较明显的特征以及差值
* 2.所以要记录每次的波谷值,为了和下次的波峰做对比
* */
public boolean detectorPeak(float newValue, float oldValue) {
lastStatus = isDirectionUp;
if (newValue >= oldValue) {
isDirectionUp = true;
continueUpCount++;
} else {
continueUpFormerCount = continueUpCount;
continueUpCount = 0;
isDirectionUp = false;
}
if (!isDirectionUp && lastStatus
&& (continueUpFormerCount >= 2 || oldValue >= 20)) {
peakOfWave = oldValue;
return true;
} else if (!lastStatus && isDirectionUp) {
valleyOfWave = oldValue;
return false;
} else {
return false;
}
}
/*
* 阈值的计算
* 1.通过波峰波谷的差值计算阈值
* 2.记录4个值,存入tempValue[]数组中
* 3.在将数组传入函数averageValue中计算阈值
* */
public float peakValleyThread(float value) {
float tempThread = ThreadValue;
if (tempCount < ValueNum) {
tempValue[tempCount] = value;
tempCount++;
} else {
tempThread = averageValue(tempValue, ValueNum);
for (int i = 1; i < ValueNum; i++) {
tempValue[i - 1] = tempValue[i];
}
tempValue[ValueNum - 1] = value;
}
return tempThread;
}
/*
* 梯度化阈值
* 1.计算数组的均值
* 2.通过均值将阈值梯度化在一个范围里
* */
public float averageValue(float value[], int n) {
float ave = 0;
for (int i = 0; i < n; i++) {
ave += value[i];
}
ave = ave / ValueNum;
if (ave >= 8)
ave = (float) 4.3;
else if (ave >= 7 && ave < 8)
ave = (float) 3.3;
else if (ave >= 4 && ave < 7)
ave = (float) 2.3;
else if (ave >= 3 && ave < 4)
ave = (float) 2.0;
else {
ave = (float) 1.3;
}
return ave;
}
}
全部代码已经上传到github:
https://github.com/Guojiankai/JiBu