android 計步器

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

參考:https://github.com/linglongxin24/DylanStepCount

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章