我們經常會看到微信 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