我們經(jīng)常會(huì)看到微信 QQ 以及其他一些運(yùn)動(dòng)app里面都有一個(gè)計(jì)步功能,那它是怎么實(shí)現(xiàn)的呢屿笼?
今天我們就來實(shí)現(xiàn)一下,以下代碼都是從一個(gè)整體項(xiàng)目中抽離出來的,為了理解簡(jiǎn)單方便我把UI部分?jǐn)?shù)據(jù)保存部分全部都去掉了辅甥,只有單純的計(jì)步邏輯和算法。
log日志顯示計(jì)步:
編寫計(jì)步邏輯的流程圖燎竖,方便理解思路:
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); //繃定并且開啟一個(gè)服務(wù)璃弄,繃定是為了方便數(shù)據(jù)交換,啟動(dòng)是為了當(dāng)當(dāng)前app不在活動(dòng)頁的時(shí)候构回,計(jì)步服務(wù)不會(huì)被關(guān)閉夏块。需要保證當(dāng)activity不為活躍狀態(tài)是計(jì)步服務(wù)在后臺(tái)能一直運(yùn)行!
}
//和繃定服務(wù)數(shù)據(jù)交換的橋梁纤掸,可以通過IBinder service獲取服務(wù)的實(shí)例來調(diào)用服務(wù)的方法或者數(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) {
//當(dāng)前接收到stepCount數(shù)據(jù)脐供,就是最新的步數(shù)
Message message = Message.obtain();
message.what = 1;
message.arg1 = stepCount;
handler.sendMessage(message);
Log.i("MainActivity—updateUi","當(dāng)前步數(shù)"+stepCount);
}
});
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onStart() {
super.onStart();
}
@Override
public void onDestroy() { //app被關(guān)閉之前,service先解除綁定借跪,如果不解除綁定下次Activity切換到活動(dòng)界面的時(shí)候又會(huì)重新開啟一個(gè)新的計(jì)步線程政己。
super.onDestroy();
if (isBind) {
this.unbindService(serviceConnection);
}
}
}
activity繃定并且開啟的服務(wù):當(dāng)前服務(wù)實(shí)現(xiàn)了SensorEventListener接口,SensorEventListener接口是計(jì)步傳感器的一個(gè)回調(diào)接口掏愁。
@Override
public void onCreate() {
super.onCreate();
Log.i("BindService—onCreate", "開啟計(jì)步");
new Thread(new Runnable() {
@Override
public void run() {
startStepDetector();
Log.i("BindService—子線程", "startStepDetector()");
}
}).start();
}
/**
* 選擇計(jì)步數(shù)據(jù)采集的傳感器
* SDK大于等于19歇由,開啟計(jì)步傳感器,小于開啟加速度傳感器
*/
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開啟計(jì)步傳感器
addCountStepListener();
} else { //小于就使用加速度傳感器
addBasePedometerListener();
}
}
/**
* 啟動(dòng)計(jì)步傳感器計(jì)步
*/
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("計(jì)步傳感器類型", "Sensor.TYPE_STEP_COUNTER");
} else if (detectorSensor != null) {
stepSensorType = Sensor.TYPE_STEP_DETECTOR;
sensorManager.registerListener(BindService.this, detectorSensor, SensorManager.SENSOR_DELAY_NORMAL);
} else {
addBasePedometerListener();
}
}
/**
* 啟動(dòng)加速度傳感器計(jì)步
*/
private void addBasePedometerListener() {
Log.i("BindService", "加速度傳感器");
mStepCount = new StepCount();
mStepCount.setSteps(nowBuSu);
//獲取傳感器類型 獲得加速度傳感器
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
//此方法用來注冊(cè)托猩,只有注冊(cè)過才會(huì)生效印蓖,參數(shù):SensorEventListener的實(shí)例,Sensor的實(shí)例京腥,更新速率
boolean isAvailable = sensorManager.registerListener(mStepCount.getStepDetector(), sensor, SensorManager.SENSOR_DELAY_UI);
mStepCount.initListener(new StepValuePassListener() {
@Override
public void stepChanged(int steps) {
nowBuSu = steps;//通過接口回調(diào)獲得當(dāng)前步數(shù)
updateNotification(); //更新步數(shù)通知
}
});
}
/**
* 通知調(diào)用者步數(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;
}
/**
* 計(jì)步傳感器數(shù)據(jù)變化回調(diào)接口
*/
@Override
public void onSensorChanged(SensorEvent event) {
//這種類型的傳感器返回步驟的數(shù)量由用戶自上次重新啟動(dòng)時(shí)激活赦肃。返回的值是作為浮動(dòng)(小數(shù)部分設(shè)置為0),
// 只在系統(tǒng)重啟復(fù)位為0。事件的時(shí)間戳將該事件的第一步的時(shí)候公浪。這個(gè)傳感器是在硬件中實(shí)現(xiàn),預(yù)計(jì)低功率他宛。
if (stepSensorType == Sensor.TYPE_STEP_COUNTER) {
//獲取當(dāng)前傳感器返回的臨時(shí)步數(shù)
int tempStep = (int) event.values[0];
//首次如果沒有獲取手機(jī)系統(tǒng)中已有的步數(shù)則獲取一次系統(tǒng)中APP還未開始記步的步數(shù)
if (!hasRecord) {
hasRecord = true;
hasStepCount = tempStep;
} else {
//獲取APP打開到現(xiàn)在的總步數(shù)=本次系統(tǒng)回調(diào)的總步數(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ā)一個(gè)事件每次采取的步驟是用戶。只允許返回值是1.0,為每個(gè)步驟生成一個(gè)事件欠气。
// 像任何其他事件,時(shí)間戳表明當(dāng)事件發(fā)生(這一步),這對(duì)應(yīng)于腳撞到地面時(shí),生成一個(gè)高加速度的變化厅各。
else if (stepSensorType == Sensor.TYPE_STEP_DETECTOR) {
if (event.values[0] == 1.0) {
nowBuSu++;
}
}
updateNotification();
}
/**
* 計(jì)步傳感器精度變化回調(diào)接口
*/
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
/**
* 綁定回調(diào)接口
*/
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 :在運(yùn)行onStartCommand后service進(jìn)程被kill后,那將保留在開始狀態(tài)预柒,但是不保留那些傳入的intent队塘。
// 不久后service就會(huì)再次嘗試重新創(chuàng)建袁梗,因?yàn)楸A粼陂_始狀態(tài),在創(chuàng)建 service后將保證調(diào)用onstartCommand憔古。
// 如果沒有傳遞任何開始命令給service遮怜,那將獲取到null的intent。
return START_STICKY;
}
@Override
public void onDestroy() {
super.onDestroy();
//取消前臺(tái)進(jìn)程
stopForeground(true);
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
}
如果sdk版本大于等于19鸿市,到這里計(jì)步服務(wù)就能向activity反饋步數(shù)了锯梁。但是如果sdk版本小于19,通過加速度傳感器計(jì)數(shù)步數(shù)還要通過算法來獲妊媲椤:
public class StepCount implements StepCountListener {
private int mCount; //當(dāng)前步數(shù)
private int count; //緩存步數(shù)陌凳,步數(shù)3秒內(nèi)小于10步則不計(jì)數(shù)
private long timeOfLastPeak = 0;//計(jì)時(shí) 開始時(shí)間 步數(shù)3秒內(nèi)小于10步則不計(jì)數(shù)
private long timeOfThisPeak = 0;//計(jì)時(shí) 現(xiàn)在時(shí)間 步數(shù)3秒內(nèi)小于10步則不計(jì)數(shù)
private StepValuePassListener stepValuePassListener;//接口用來傳遞步數(shù)變化
private StepDetector stepDetector;//傳感器SensorEventListener子類實(shí)例
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ù)刷新回調(diào)");
// 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 {//超時(shí)
this.count = 1;//為1,不是0
}
}
public void setSteps(int initNowBusu){
this.mCount = initNowBusu;//接收上層調(diào)用傳遞過來的當(dāng)前步數(shù)
this.count = 0;
timeOfLastPeak = 0;
timeOfThisPeak = 0;
notifyListener();
}
/**
* 用來給調(diào)用者獲取SensorEventListener實(shí)例
* @return 返回SensorEventListener實(shí)例
*/
public StepDetector getStepDetector(){
return stepDetector;
}
/**
* 更新步數(shù),通過接口函數(shù)通過上層調(diào)用者
*/
public void notifyListener(){
if(this.stepValuePassListener != null){
Log.i("countStep","數(shù)據(jù)更新");
this.stepValuePassListener.stepChanged(this.mCount); //當(dāng)前步數(shù)通過接口傳遞給調(diào)用者
}
}
public void initListener(StepValuePassListener listener){
this.stepValuePassListener = listener;
}
}
@Override
public void onSensorChanged(SensorEvent event) {//當(dāng)傳感器值改變回調(diào)此方法
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;
}
/*
* 檢測(cè)步子内舟,并開始計(jì)步
* 1.傳入sersor中的數(shù)據(jù)
* 2.如果檢測(cè)到了波峰合敦,并且符合時(shí)間差以及閾值的條件,則判定為1步
* 3.符合時(shí)間差條件谒获,波峰波谷差值大于initialValue蛤肌,則將該差值納入閾值的計(jì)算中
* */
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;
/*
* 更新界面的處理壁却,不涉及到算法
* 一般在通知更新界面之前批狱,增加下面處理,為了處理無效運(yùn)動(dòng):
* 1.連續(xù)記錄10才開始計(jì)步
* 2.例如記錄的9步用戶停住超過3秒展东,則前面的記錄失效赔硫,下次從頭開始
* 3.連續(xù)記錄了9步用戶還在運(yùn)動(dòng),之前的數(shù)據(jù)才有效
* */
mStepListeners.countStep();
}
if (timeOfNow - timeOfLastPeak >= TimeInterval
&& (peakOfWave - valleyOfWave >= InitialValue)) {
timeOfThisPeak = timeOfNow;
ThreadValue = peakValleyThread(peakOfWave - valleyOfWave);
}
}
}
gravityOld = values;
}
/*
* 檢測(cè)波峰
* 以下四個(gè)條件判斷為波峰:
* 1.目前點(diǎn)為下降的趨勢(shì):isDirectionUp為false
* 2.之前的點(diǎn)為上升的趨勢(shì):lastStatus為true
* 3.到波峰為止盐肃,持續(xù)上升大于等于2次
* 4.波峰值大于20
* 記錄波谷值
* 1.觀察波形圖爪膊,可以發(fā)現(xiàn)在出現(xiàn)步子的地方,波谷的下一個(gè)就是波峰砸王,有比較明顯的特征以及差值
* 2.所以要記錄每次的波谷值推盛,為了和下次的波峰做對(duì)比
* */
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;
}
}
/*
* 閾值的計(jì)算
* 1.通過波峰波谷的差值計(jì)算閾值
* 2.記錄4個(gè)值,存入tempValue[]數(shù)組中
* 3.在將數(shù)組傳入函數(shù)averageValue中計(jì)算閾值
* */
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.計(jì)算數(shù)組的均值
* 2.通過均值將閾值梯度化在一個(gè)范圍里
* */
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;
}
}
全部代碼已經(jīng)上傳到github:
https://github.com/Guojiankai/JiBu