背景
市面上出現(xiàn)了越來越多的計步設(shè)備怠李,也有越來越多的app將計步器這塊添加作為功能模塊.IOS平臺刻获,蘋果公司已經(jīng)開放了提供計步數(shù)據(jù)的api,而android平臺作為一個開發(fā)的平臺旗国,只提供了對應(yīng)的傳感器褐望,具體怎么實現(xiàn)計步,實現(xiàn)方式很多挣轨,有傳感器军熏、定位等等,然而市面上android app計步并不是很精確.
目的
啟動計步開源項目卷扮、致力于打造穩(wěn)定誤差小的android平臺計步器荡澎。
主題
接下來筆者從傳感器角度入手做一個android平臺的計步器.首先我們需要做的優(yōu)先任務(wù)就是完成計步核心代碼編寫.參考網(wǎng)上通用的傳感器計步算法,通過梯度化閾值晤锹、閾值的計算摩幔、 檢測波峰等算法推算出人是否在行走,然后對兩次行走步伐之間的時間差值正常范圍應(yīng)該為200ms-2000ms的判斷鞭铆,進(jìn)行矯正計步精度.
計算梯度化閾值核心代碼如下:
/**
* 梯度化閾值
* 計算數(shù)組的均值
* 通過均值將閾值梯度化在一個范圍里
* @author leibing
* @createTime 2016/08/31
* @lastModify 2016/08/31
* @param value
* @param n
* @return
*/
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) {
Log.v(TAG, "超過8");
ave = (float) 4.3;
} else if (ave >= 7 && ave < 8) {
Log.v(TAG, "7-8");
ave = (float) 3.3;
} else if (ave >= 4 && ave < 7) {
Log.v(TAG, "4-7");
ave = (float) 2.3;
} else if (ave >= 3 && ave < 4) {
Log.v(TAG, "3-4");
ave = (float) 2.0;
} else {
Log.v(TAG, "else");
ave = (float) 1.7;
}
return ave;
}
閾值的計算核心代碼如下:
/**
* 閾值的計算
* 通過波峰波谷的差值計算閾值
* 記錄4個值热鞍,存入tempValue[]數(shù)組中
* 在將數(shù)組傳入函數(shù)averageValue中計算閾值
* @author leibing
* @createTime 2016/08/31
* @lastModify 2016/08/31
* @param value
* @return
*/
public float Peak_Valley_Thread(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;
}
檢測波峰核心代碼如下:
/**
* 檢測波峰
* 以下四個條件判斷為波峰:
* 目前點(diǎn)為下降的趨勢:isDirectionUp為false
* 之前的點(diǎn)為上升的趨勢:lastStatus為true
* 到波峰為止,持續(xù)上升大于等于2次
* 波峰值大于1.2g,小于2g
* 記錄波谷值
* 觀察波形圖衔彻,可以發(fā)現(xiàn)在出現(xiàn)步子的地方,波谷的下一個就是波峰偷办,有比較明顯的特征以及差值
* 所以要記錄每次的波谷值艰额,為了和下次的波峰做對比
* @author leibing
* @createTime 2016/08/31
* @lastModify 2016/08/31
* @param newValue
* @param oldValue
* @return
*/
public boolean DetectorPeak(float newValue, float oldValue) {
lastStatus = isDirectionUp;
if (newValue >= oldValue) {
isDirectionUp = true;
continueUpCount++;
} else {
continueUpFormerCount = continueUpCount;
continueUpCount = 0;
isDirectionUp = false;
}
Log.v(TAG, "oldValue:" + oldValue);
if (!isDirectionUp && lastStatus
&& (continueUpFormerCount >= 2 && (oldValue >= minValue && oldValue < maxValue))) {
peakOfWave = oldValue;
return true;
} else if (!lastStatus && isDirectionUp) {
valleyOfWave = oldValue;
return false;
} else {
return false;
}
}
根據(jù)以上算法得到計步,在此我對計步做了相關(guān)優(yōu)化椒涯,連續(xù)運(yùn)動一段時間才開始計步,屏蔽細(xì)微移動或者駕車時震動所帶來的干擾.停止運(yùn)動一段時間后,需要連續(xù)運(yùn)動一段時間才會計步.至此就完成計步核心服務(wù)類代碼,因此就能從這個核心服務(wù)類中拿到我們所需要的計步數(shù)據(jù).
接下來柄沮,我們需要對計步數(shù)據(jù)做相應(yīng)處理,開啟一個計步服務(wù)用于拿取計步數(shù)據(jù)、更新ui以及緩存數(shù)據(jù).為了避免影響計步app性能祖搓,我們將開啟一個新的進(jìn)程用于計步服務(wù)狱意,這樣我們就需要進(jìn)行進(jìn)程間的通信,這里筆者是采用Messenger進(jìn)行進(jìn)程通信.
計步服務(wù)代碼如下:
/**
* @className: StepService
* @classDescription: 計步服務(wù)
* @author: leibing
* @createTime: 2016/08/31
*/
@TargetApi(Build.VERSION_CODES.CUPCAKE)
public class StepService extends Service implements SensorEventListener {
// TAG
private final String TAG = "StepService";
// 默認(rèn)int錯誤碼
public static final int INT_ERROR = -12;
// 停止廣播動作
public static final String ACTION_STOP_SERVICE = "action_stop_service";
// step key
public final static String STEP_KEY = "step_key";
// 傳感器管理
private SensorManager sensorManager;
// 計步核心類
private StepDcretor stepDetector;
// 自定義Handler
private MsgHandler msgHandler = new MsgHandler();
// Messenger 用于跨進(jìn)程通信
private Messenger messenger = new Messenger(msgHandler);
// 計步需要緩存的數(shù)據(jù)
private StepModel mStepModel;
// 計步服務(wù)廣播
private BroadcastReceiver stepServiceReceiver;
// 是否手動停止服務(wù)
private boolean isNeedStopService = false;
/**
* @className: MsgHandler
* @classDescription: 用于更新客戶端UI
* @author: leibing
* @createTime: 2016/08/31
*/
class MsgHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constant.MSG_FROM_CLIENT:
try {
// 緩存數(shù)據(jù)
cacheStepData(StepService.this,StepDcretor.CURRENT_STEP + "");
// 更新通知欄
updateNotification(msg.getData());
// 回復(fù)消息給Client
Messenger messenger = msg.replyTo;
Message replyMsg = Message.obtain(null, Constant.MSG_FROM_SERVER);
Bundle bundle = new Bundle();
bundle.putInt(STEP_KEY, StepDcretor.CURRENT_STEP);
replyMsg.setData(bundle);
messenger.send(replyMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
/**
* 更新通知欄
* @author leibing
* @createTime 2016/09/02
* @lastModify 2016/09/02
* @param bundle 數(shù)據(jù)
* @return
*/
private void updateNotification(Bundle bundle) {
if (bundle == null) {
NotificationUtils.getInstance(StepService.this).
updateNotification("今日行走" + StepDcretor.CURRENT_STEP + "步");
}else {
// 內(nèi)容
String content = (String) bundle.getSerializable(Constant.CONTENT_KEY);
// ticker
String ticker = (String) bundle.getSerializable(Constant.TICKER_KEY);
// 標(biāo)題
String contentTile = (String) bundle.getSerializable(Constant.CONTENTTITLE_KEY);
// 需要跳轉(zhuǎn)的Activity
Class pendingClass = (Class) bundle.getSerializable(Constant.PENDINGCLASS_KEY);
// 是否不可取消
boolean isOngoing = true;
if (bundle.getSerializable(Constant.ISONGOING_KEY) != null){
isOngoing = (boolean) bundle.getSerializable(Constant.ISONGOING_KEY);
}
// 頭像
int icon = INT_ERROR;
if (bundle.getSerializable(Constant.ICON_KEY) != null){
icon = (int) bundle.getSerializable(Constant.ICON_KEY);
}
// id
int notifyId = INT_ERROR;
if (bundle.getSerializable(Constant.NOTIFYID_KEY) != null){
notifyId = (int) bundle.getSerializable(Constant.NOTIFYID_KEY);
}
if (StringUtil.isEmpty(content)
|| StringUtil.isEmpty(ticker)
|| StringUtil.isEmpty(contentTile)){
NotificationUtils.getInstance(StepService.this).
updateNotification("今日行走" + StepDcretor.CURRENT_STEP + "步");
}else {
NotificationUtils.getInstance(StepService.this).
updateNotification(content + StepDcretor.CURRENT_STEP + "步",
ticker,
contentTile,
StepService.this,
pendingClass,
isOngoing,
notifyId,
icon);
}
}
}
/**
* 啟動服務(wù)為前臺服務(wù)( 讓該service前臺運(yùn)行拯欧,避免手機(jī)休眠時系統(tǒng)自動殺掉該服務(wù))
* @author leibing
* @createTime 2016/09/07
* @lastModify 2016/09/07
* @param
* @return
*/
public void startForeground(){
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
// 設(shè)置頭像
builder.setSmallIcon(R.mipmap.ic_launcher);
// 設(shè)置標(biāo)題
builder.setContentTitle("foreground service");
// 設(shè)置內(nèi)容
builder.setContentText("try to avoid this service be killed!");
// 創(chuàng)建notification
Notification notification = builder.build();
//如果 id 為 0 详囤,那么狀態(tài)欄的 notification 將不會顯示。
startForeground(0, notification);
}
@Override
public void onCreate() {
super.onCreate();
// 初始化計步服務(wù)廣播
initStepServiceReceiver();
// 啟動計步
startStep();
// 啟動服務(wù)為前臺服務(wù)( 讓該service前臺運(yùn)行,避免手機(jī)休眠時系統(tǒng)自動殺掉該服務(wù))
startForeground();
Log.v(TAG,"onCreate");
}
/**
* 初始化計步服務(wù)廣播
* @author leibing
* @createTime 2016/09/01
* @lastModify 2016/09/01
* @param
* @return
*/
private void initStepServiceReceiver() {
final IntentFilter filter = new IntentFilter();
// 添加停止當(dāng)前服務(wù)廣播動作
filter.addAction(ACTION_STOP_SERVICE);
// 實例化廣播接收器
stepServiceReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_STOP_SERVICE.equals(action)){
Log.v(TAG,"停止服務(wù)");
// 停止服務(wù)
isNeedStopService = true;
StepService.this.stopSelf();
}
}
};
// 注冊計步服務(wù)廣播
registerReceiver(stepServiceReceiver, filter);
}
/**
* 啟動計步
* @author leibing
* @createTime 2016/08/31
* @lastModify 2016/08/31
* @param
* @return
*/
private void startStep() {
// 啟動計步器
startStepDetector();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.v(TAG, "onStartCommand");
return START_STICKY;
}
/**
* 緩存計步數(shù)據(jù)
* @author leibing
* @createTime 2016/08/31
* @lastModify 2016/08/31
* @param context 上下文
* @param stepCount 計步數(shù)
* @return
*/
private void cacheStepData(Context context, String stepCount){
mStepModel = new StepModel();
mStepModel.setDate(DateUtils.simpleDateFormat(new Date()));
mStepModel.setStep(stepCount);
DataCache.getInstance().addStepCache(context, mStepModel);
}
@Override
public IBinder onBind(Intent intent) {
Log.v(TAG, "onBind");
return messenger.getBinder();
}
/**
* 啟動計步器
* @author leibing
* @createTime 2016/08/31
* @lastModify 2016/08/31
* @param
* @return
*/
private void startStepDetector() {
if (sensorManager != null && stepDetector != null) {
sensorManager.unregisterListener(stepDetector);
sensorManager = null;
stepDetector = null;
}
// 初始化計步(拿緩存更新計步數(shù))
DataCache.getInstance().getTodayCache(this, new DataCache.DataCacheListener() {
@Override
public void readListCache(StepModel stepModel) {
if (stepModel != null){
StepDcretor.CURRENT_STEP = Integer.parseInt(stepModel.getStep());
}
}
});
sensorManager = (SensorManager) this
.getSystemService(SENSOR_SERVICE);
// 添加自定義
addBasePedoListener();
// 添加傳感器監(jiān)聽
addCountStepListener();
}
/**
* 停止計步器
* @author leibing
* @createTime 2016/09/01
* @lastModify 2016/09/01
* @param
* @return
*/
public void stopStepDetector(){
if (sensorManager != null && stepDetector != null) {
sensorManager.unregisterListener(stepDetector);
sensorManager = null;
stepDetector = null;
}
}
/**
* 添加傳感器監(jiān)聽(步行檢測傳感器镐作、計步傳感器)
* @author leibing
* @createTime 2016/08/31
* @lastModify 2016/08/31
* @param
* @return
*/
private void addCountStepListener() {
// 步行檢測傳感器藏姐,用戶每走一步就觸發(fā)一次事件
Sensor detectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
// 計步傳感器
Sensor countSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
if (detectorSensor != null) {
sensorManager.registerListener(StepService.this, detectorSensor, SensorManager.SENSOR_DELAY_UI);
} else if (countSensor != null) {
sensorManager.registerListener(StepService.this, countSensor, SensorManager.SENSOR_DELAY_UI);
} else {
Log.v(TAG, "Count sensor not available!");
}
}
/**
*添加傳感器監(jiān)聽(加速度傳感器)
* @author leibing
* @createTime 2016/08/31
* @lastModify 2016/08/31
* @param
* @return
*/
private void addBasePedoListener() {
stepDetector = new StepDcretor();
// 獲得傳感器的類型,這里獲得的類型是加速度傳感器
// 此方法用來注冊该贾,只有注冊過才會生效羔杨,參數(shù):SensorEventListener的實例,Sensor的實例杨蛋,更新速率
Sensor sensor = sensorManager
.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
// sensorManager.unregisterListener(stepDetector);
sensorManager.registerListener(stepDetector, sensor,
SensorManager.SENSOR_DELAY_UI);
stepDetector
.setOnSensorChangeListener(new StepDcretor.OnSensorChangeListener() {
@Override
public void onChange() {
}
});
}
@Override
public void onSensorChanged(SensorEvent event) {
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
@Override
public void onDestroy() {
// 取消前臺進(jìn)程
stopForeground(true);
// 解注冊計步服務(wù)廣播
unregisterReceiver(stepServiceReceiver);
// 停止計步器
stopStepDetector();
// 非手動停止服務(wù),則自動重啟服務(wù)
if (!isNeedStopService){
Intent intent = new Intent(this, StepService.class);
startService(intent);
}else {
isNeedStopService = false;
}
Log.v(TAG,"onDestroy");
super.onDestroy();
}
@Override
public boolean onUnbind(Intent intent) {
Log.v(TAG,"onUnbind");
return super.onUnbind(intent);
}
}
創(chuàng)建計步服務(wù)的時候兜材,筆者將計步服務(wù)置為前臺服務(wù),減少計步服務(wù)進(jìn)程被殺幾率逞力。有童鞋會問曙寡,為何不開啟靜態(tài)廣播去定期喚醒計步服務(wù)?其實這樣做,并不能做到適配掏击,android 6.0以后已經(jīng)去掉了相關(guān)的系統(tǒng)靜態(tài)廣播.真正能做到百分百進(jìn)程甭言恚活,只能靠手機(jī)廠家白名單了砚亭,其他的都是浮云灯变,頂多只是降低被殺的幾率而已.對于進(jìn)程保活這塊就不糾結(jié)了.
本項目已開源捅膘,項目地址:JkStepSensor.
如有疑問添祸,請聯(lián)系!
- QQ:872721111
- email:leibing1989@126.com
- github:leibing@github