致力于穩(wěn)定誤差小的android平臺計步器

背景

市面上出現(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;
    }

檢測波峰核心代碼如下:

    /**
     * 檢測波峰
     * 以下四個條件判斷為波峰:
     * 目前點為下降的趨勢:isDirectionUp為false
     * 之前的點為上升的趨勢: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ù)運動一段時間才開始計步,屏蔽細(xì)微移動或者駕車時震動所帶來的干擾.停止運動一段時間后,需要連續(xù)運動一段時間才會計步.至此就完成計步核心服務(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前臺運行,避免手機休眠時系統(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前臺運行,避免手機休眠時系統(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ìn)程?;钸@塊就不糾結(jié)了.

本項目已開源,項目地址:JkStepSensor.

如有疑問,請聯(lián)系!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容