我們經常會看到微信 QQ 以及其他一些運動app里面都有一個計步功能,那它是怎么實現(xiàn)的呢?
今天我們就來實現(xiàn)一下,以下代碼都是從一個整體項目中抽離出來的,為了理解簡單方便我把UI部分數(shù)據(jù)保存部分全部都去掉了,只有單純的計步邏輯和算法。
log日志顯示計步:

image.png
編寫計步邏輯的流程圖,方便理解思路:

image.png
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); //繃定并且開啟一個服務,繃定是為了方便數(shù)據(jù)交換,啟動是為了當當前app不在活動頁的時候,計步服務不會被關閉。需要保證當activity不為活躍狀態(tài)是計步服務在后臺能一直運行!
}
//和繃定服務數(shù)據(jù)交換的橋梁,可以通過IBinder service獲取服務的實例來調用服務的方法或者數(shù)據(jù)
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數(shù)據(jù),就是最新的步數(shù)
Message message = Message.obtain();
message.what = 1;
message.arg1 = stepCount;
handler.sendMessage(message);
Log.i("MainActivity—updateUi","當前步數(shù)"+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繃定并且開啟的服務:當前服務實現(xiàn)了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();
}
/**
* 選擇計步數(shù)據(jù)采集的傳感器
* 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);
//此方法用來注冊,只有注冊過才會生效,參數(shù):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;//通過接口回調獲得當前步數(shù)
updateNotification(); //更新步數(shù)通知
}
});
}
/**
* 通知調用者步數(shù)更新 數(shù)據(jù)交互
*/
private void updateNotification() {
if (mCallback != null) {
Log.i("BindService", "數(shù)據(jù)更新");
mCallback.updateUi(nowBuSu);
}
}
@Override
public IBinder onBind(Intent intent) {
return lcBinder;
}
/**
* 計步傳感器數(shù)據(jù)變化回調接口
*/
@Override
public void onSensorChanged(SensorEvent event) {
//這種類型的傳感器返回步驟的數(shù)量由用戶自上次重新啟動時激活。返回的值是作為浮動(小數(shù)部分設置為0),
// 只在系統(tǒng)重啟復位為0。事件的時間戳將該事件的第一步的時候。這個傳感器是在硬件中實現(xiàn),預計低功率。
if (stepSensorType == Sensor.TYPE_STEP_COUNTER) {
//獲取當前傳感器返回的臨時步數(shù)
int tempStep = (int) event.values[0];
//首次如果沒有獲取手機系統(tǒng)中已有的步數(shù)則獲取一次系統(tǒng)中APP還未開始記步的步數(shù)
if (!hasRecord) {
hasRecord = true;
hasStepCount = tempStep;
} else {
//獲取APP打開到現(xiàn)在的總步數(shù)=本次系統(tǒng)回調的總步數(shù)-APP打開之前已有的步數(shù)
int thisStepCount = tempStep - hasStepCount;
//本次有效步數(shù)=(APP打開后所記錄的總步數(shù)-上一次APP打開后所記錄的總步數(shù))
int thisStep = thisStepCount - previousStepCount;
//總步數(shù)=現(xiàn)有的步數(shù)+本次有效步數(shù)
nowBuSu += (thisStep);
//記錄最后一次APP打開到現(xiàn)在的總步數(shù)
previousStepCount = thisStepCount;
}
}
//這種類型的傳感器觸發(fā)一個事件每次采取的步驟是用戶。只允許返回值是1.0,為每個步驟生成一個事件。
// 像任何其他事件,時間戳表明當事件發(fā)生(這一步),這對應于腳撞到地面時,生成一個高加速度的變化。
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;
}
}
/**
* 數(shù)據(jù)傳遞接口
*
* @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后,那將保留在開始狀態(tài),但是不保留那些傳入的intent。
// 不久后service就會再次嘗試重新創(chuàng)建,因為保留在開始狀態(tài),在創(chuàng)建 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反饋步數(shù)了。但是如果sdk版本小于19,通過加速度傳感器計數(shù)步數(shù)還要通過算法來獲取:
public class StepCount implements StepCountListener {
private int mCount; //當前步數(shù)
private int count; //緩存步數(shù),步數(shù)3秒內小于10步則不計數(shù)
private long timeOfLastPeak = 0;//計時 開始時間 步數(shù)3秒內小于10步則不計數(shù)
private long timeOfThisPeak = 0;//計時 現(xiàn)在時間 步數(shù)3秒內小于10步則不計數(shù)
private StepValuePassListener stepValuePassListener;//接口用來傳遞步數(shù)變化
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","傳感器數(shù)據(jù)刷新回調");
// 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;//接收上層調用傳遞過來的當前步數(shù)
this.count = 0;
timeOfLastPeak = 0;
timeOfThisPeak = 0;
notifyListener();
}
/**
* 用來給調用者獲取SensorEventListener實例
* @return 返回SensorEventListener實例
*/
public StepDetector getStepDetector(){
return stepDetector;
}
/**
* 更新步數(shù),通過接口函數(shù)通過上層調用者
*/
public void notifyListener(){
if(this.stepValuePassListener != null){
Log.i("countStep","數(shù)據(jù)更新");
this.stepValuePassListener.stepChanged(this.mCount); //當前步數(shù)通過接口傳遞給調用者
}
}
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中的數(shù)據(jù)
* 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.連續(xù)記錄10才開始計步
* 2.例如記錄的9步用戶停住超過3秒,則前面的記錄失效,下次從頭開始
* 3.連續(xù)記錄了9步用戶還在運動,之前的數(shù)據(jù)才有效
* */
mStepListeners.countStep();
}
if (timeOfNow - timeOfLastPeak >= TimeInterval
&& (peakOfWave - valleyOfWave >= InitialValue)) {
timeOfThisPeak = timeOfNow;
ThreadValue = peakValleyThread(peakOfWave - valleyOfWave);
}
}
}
gravityOld = values;
}
/*
* 檢測波峰
* 以下四個條件判斷為波峰:
* 1.目前點為下降的趨勢:isDirectionUp為false
* 2.之前的點為上升的趨勢:lastStatus為true
* 3.到波峰為止,持續(xù)上升大于等于2次
* 4.波峰值大于20
* 記錄波谷值
* 1.觀察波形圖,可以發(fā)現(xiàn)在出現(xiàn)步子的地方,波谷的下一個就是波峰,有比較明顯的特征以及差值
* 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[]數(shù)組中
* 3.在將數(shù)組傳入函數(shù)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.計算數(shù)組的均值
* 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