致力于穩(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;
    }

檢測波峰核心代碼如下:

    /**
     * 檢測波峰
     * 以下四個條件判斷為波峰:
     * 目前點(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)系!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末寻仗,一起剝皮案震驚了整個濱河市刃泌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌署尤,老刑警劉巖耙替,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異曹体,居然都是意外死亡俗扇,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門箕别,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铜幽,“玉大人滞谢,你說我怎么就攤上這事〕祝” “怎么了狮杨?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長到忽。 經(jīng)常有香客問我橄教,道長,這世上最難降的妖魔是什么绘趋? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任颤陶,我火速辦了婚禮,結(jié)果婚禮上陷遮,老公的妹妹穿的比我還像新娘滓走。我一直安慰自己,他們只是感情好帽馋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布搅方。 她就那樣靜靜地躺著,像睡著了一般绽族。 火紅的嫁衣襯著肌膚如雪姨涡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天吧慢,我揣著相機(jī)與錄音涛漂,去河邊找鬼。 笑死检诗,一個胖子當(dāng)著我的面吹牛匈仗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逢慌,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼悠轩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了攻泼?” 一聲冷哼從身側(cè)響起火架,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎忙菠,沒想到半個月后何鸡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡牛欢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年骡男,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片氢惋。...
    茶點(diǎn)故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡洞翩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出焰望,到底是詐尸還是另有隱情骚亿,我是刑警寧澤,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布熊赖,位于F島的核電站来屠,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏震鹉。R本人自食惡果不足惜俱笛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望传趾。 院中可真熱鬧迎膜,春花似錦、人聲如沸浆兰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽簸呈。三九已至榕订,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蜕便,已是汗流浹背劫恒。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留轿腺,地道東北人两嘴。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像吃溅,于是被迫代替她去往敵國和親溶诞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容