Android計(jì)步模塊優(yōu)化(今日步數(shù))

最近在項(xiàng)目中研究計(jì)步模塊,主要功能記錄當(dāng)天步數(shù),類似微信運(yùn)動(dòng),支付寶計(jì)步,咕咚今日步數(shù)。
開(kāi)發(fā)之前的調(diào)研工作,搜遍baidu,google,github都沒(méi)有找到我想要的demo和文章,大多數(shù)都是需要Service?;睢?br> 對(duì)于各大手機(jī)廠商為了提高電池的續(xù)航里程AlertManager、BOOT_COMPLETED、Service的START_STICKY基本上都是不起作用的,Service后臺(tái)?;罡遣豢赡堋?br> 下面是我實(shí)現(xiàn)的計(jì)步模塊和大家一起學(xué)習(xí)
github地址
之前也有一篇文章寫(xiě)計(jì)步模塊,這篇文章是對(duì)上面文章的優(yōu)化,github代碼已經(jīng)更新到最新了。
Android計(jì)步模塊(類似微信運(yùn)動(dòng))
目前已經(jīng)對(duì)現(xiàn)有的庫(kù)進(jìn)行優(yōu)化V2.0.0如下這篇文章
Android計(jì)步模塊優(yōu)化(今日步數(shù))V2.0.0

App計(jì)步模塊優(yōu)化的三個(gè)過(guò)程

第一個(gè)過(guò)程上線:
由于功能著急上線,項(xiàng)目最開(kāi)始計(jì)步模塊單獨(dú)使用加速度傳感器Sensor.TYPE_ACCELEROMETER進(jìn)行計(jì)算步數(shù),同時(shí)Service需要在后臺(tái)存活才能計(jì)步,否則不能計(jì)步。

第二個(gè)過(guò)程計(jì)步器:
項(xiàng)目運(yùn)行一段時(shí)間公司開(kāi)始推廣走路計(jì)步這個(gè)模塊,所以開(kāi)始重新開(kāi)發(fā)計(jì)步模塊,這次使用了Android4.4以上提供的計(jì)步傳感器Sensor.TYPE_STEP_COUNTER來(lái)完成,這次重新開(kāi)發(fā)整個(gè)計(jì)步模塊有了質(zhì)的飛躍,由于采用了計(jì)步傳感器不在需要后臺(tái)?;頢ervice,同時(shí)計(jì)步傳感器的功耗特別低,整個(gè)模塊也更省電了。

第三個(gè)過(guò)程優(yōu)化計(jì)步:
用戶量變大了投訴也變多了,android各種各樣的機(jī)型真是讓人蛋疼,終于到了第三個(gè)階段優(yōu)化階段。
目前最多的問(wèn)題:
1.Android4.4以上的系統(tǒng)但是手機(jī)沒(méi)有計(jì)步協(xié)處理器
2.部分手機(jī)步數(shù)出現(xiàn)暴增現(xiàn)象,有可能一天幾十萬(wàn)步
3.部分手機(jī)出現(xiàn)一天清零好多次。
4.開(kāi)機(jī)計(jì)步不能自啟動(dòng),需要打開(kāi)app(我已經(jīng)監(jiān)聽(tīng)BOOT_COMPLETED廣播)
5.隔天分隔(0點(diǎn)分隔)不好用每天早上需要打開(kāi)app(我已經(jīng)設(shè)置AlertManager)

這篇文章就來(lái)介紹現(xiàn)在app的計(jì)步模塊,已經(jīng)解決上述問(wèn)題1、2、3,至于4、5問(wèn)題是系統(tǒng)問(wèn)題正在尋找解決方案,大家也可以幫幫忙在評(píng)論中給我提示。
已經(jīng)將計(jì)步模塊單獨(dú)封裝成libModule上傳github如果有開(kāi)發(fā)者需要的或者想交流的可以很方便的使用下載,點(diǎn)擊這里下載。

計(jì)步方式背景知識(shí)

1.加速度傳感器Sensor.TYPE_ACCELEROMETER計(jì)步方式:
這種方式是有開(kāi)源的算法根據(jù)加速度傳感器進(jìn)行計(jì)算步數(shù),點(diǎn)擊這里查看原作者源碼;
優(yōu)點(diǎn):只要有加速度傳感器的設(shè)備都可以使用,相對(duì)來(lái)說(shuō)可以使用的設(shè)備較多。
缺點(diǎn):步數(shù)的準(zhǔn)確性取決于算法且算法比較難優(yōu)化;需要后臺(tái)?;頢ervice否則不能計(jì)步;計(jì)步算法比較費(fèi)電;部分手機(jī)鎖屏不能計(jì)步;

2.計(jì)步傳感器Sensor.TYPE_STEP_COUNTER計(jì)步方式:
官方解釋翻譯(本人英文不是很好根據(jù)理解翻譯,如有錯(cuò)誤請(qǐng)指出):
這個(gè)傳感器是返回手機(jī)系統(tǒng)啟動(dòng)到當(dāng)前時(shí)間的所有步數(shù)。手機(jī)系統(tǒng)重啟傳感器返回步數(shù)為0。還返回一個(gè)時(shí)間戳,表示最后一次步數(shù)的時(shí)間。這個(gè)計(jì)步傳感器是個(gè)硬件,功耗非常低。如果你想記錄步數(shù),注冊(cè)該傳感器不要注銷,他能自動(dòng)在后臺(tái)計(jì)步,在app喚醒的時(shí)候會(huì)返回計(jì)步總數(shù)。應(yīng)用程序需要注冊(cè)該傳感器,否則不能返回步數(shù)。
優(yōu)點(diǎn):硬件計(jì)步準(zhǔn)確性高;功耗??;只要注冊(cè)不用后臺(tái)Service自動(dòng)計(jì)步;
缺點(diǎn):Android4.4系統(tǒng)以上的部分手機(jī);手機(jī)系統(tǒng)重啟計(jì)步器清零;不能返回步數(shù)明細(xì)(步數(shù)對(duì)應(yīng)時(shí)間),只是返回當(dāng)前時(shí)間的總步數(shù)。

計(jì)步模塊兩種計(jì)步方式都采用:
判斷是否支持Sensor.TYPE_STEP_COUNTER如果支持采用計(jì)步傳感器,如果不支持用加速度傳感器計(jì)步。
使用加速度傳感器計(jì)步需要用戶自己手動(dòng)設(shè)置后臺(tái)自啟動(dòng),否則不能計(jì)步。
使用計(jì)步傳感器需要在程序中克服他的缺點(diǎn):手機(jī)系統(tǒng)重啟計(jì)步器清零;不能返回步數(shù)明細(xì)(步數(shù)對(duì)應(yīng)時(shí)間),只是返回當(dāng)前時(shí)間的總步數(shù)。

先介紹接入方法,在介紹計(jì)步模塊原理

接入方法

1.先下載計(jì)步demo TodayStepCounter
2.demo項(xiàng)目結(jié)構(gòu)如下圖:

TodayStepCounter項(xiàng)目結(jié)構(gòu)圖.png

由圖可見(jiàn)todaystepcounterlib是計(jì)步模塊封裝好的Module,它對(duì)外提供的接口就是ISportStepInterface.aidl
3.如何接入:
查看對(duì)外接口ISportStepInterface.aidl如下代碼:

// ISportStepInterface.aidl
package com.today.step.lib;
interface ISportStepInterface {
    /**
     * 獲取當(dāng)前時(shí)間運(yùn)動(dòng)步數(shù)
     */
     int getCurrentTimeSportStep();
     /**
      * 獲取當(dāng)天步數(shù)列表,json格式
      */
     String getTodaySportStepArray();
}

查看使用代碼MainActivity.java,里面關(guān)鍵代碼有注釋非常簡(jiǎn)單

public class MainActivity extends AppCompatActivity {
    private static String TAG = "MainActivity";
    private static final int REFRESH_STEP_WHAT = 0;
    //循環(huán)取當(dāng)前時(shí)刻的步數(shù)中間的間隔時(shí)間
    private long TIME_INTERVAL_REFRESH = 500;
    private Handler mDelayHandler = new Handler(new TodayStepCounterCall());
    private int mStepSum;
    private ISportStepInterface iSportStepInterface;
    private TextView mStepArrayTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化計(jì)步模塊
        TodayStepManager.init(getApplication());
        mStepArrayTextView = (TextView)findViewById(R.id.stepArrayTextView);
        //開(kāi)啟計(jì)步Service,同時(shí)綁定Activity進(jìn)行aidl通信
        Intent intent = new Intent(this, TodayStepService.class);
        startService(intent);
        bindService(intent, new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                //Activity和Service通過(guò)aidl進(jìn)行通信
                iSportStepInterface = ISportStepInterface.Stub.asInterface(service);
                try {
                    mStepSum = iSportStepInterface.getCurrentTimeSportStep();
                    updateStepCount();
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
     mDelayHandler.sendEmptyMessageDelayed(REFRESH_STEP_WHAT, TIME_INTERVAL_REFRESH);

            }
            @Override
            public void onServiceDisconnected(ComponentName name) {
            }
        }, Context.BIND_AUTO_CREATE);
    }
    class TodayStepCounterCall implements Handler.Callback{
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case REFRESH_STEP_WHAT: {
                    //每隔500毫秒獲取一次計(jì)步數(shù)據(jù)刷新UI
                    if (null != iSportStepInterface) {
                        int step = 0;
                        try {
                            step = iSportStepInterface.getCurrentTimeSportStep();
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                        if (mStepSum != step) {
                            mStepSum = step;
                            updateStepCount();
                        }
                    }
                 mDelayHandler.sendEmptyMessageDelayed(REFRESH_STEP_WHAT, TIME_INTERVAL_REFRESH);
                    break;
                }
            }
            return false;
        }
    }
    private void updateStepCount() {
        Log.e(TAG,"updateStepCount : " + mStepSum);
        TextView stepTextView = (TextView)findViewById(R.id.stepTextView);
        stepTextView.setText(mStepSum + "步");
    }
    public void onClick(View view){
        switch (view.getId()){
            case R.id.stepArrayButton:{
                //顯示當(dāng)天計(jì)步數(shù)據(jù)詳細(xì),步數(shù)對(duì)應(yīng)當(dāng)前時(shí)間
                if(null != iSportStepInterface){
                    try {
                        String stepArray = iSportStepInterface.getTodaySportStepArray();
                        mStepArrayTextView.setText(stepArray);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
                break;
            }
            default:break;
        }
    }
}

計(jì)步模塊原理

計(jì)步模塊流程圖

計(jì)步模塊流程圖.png

講解流程圖:
1.整個(gè)計(jì)步模塊是由一個(gè)運(yùn)行在單獨(dú)進(jìn)程的Service(TodayStepService)來(lái)提供,由于運(yùn)行在單獨(dú)的進(jìn)程所以對(duì)外提供的接口采用aidl形式(ISportStepInterface)。
2.零點(diǎn)分隔廣播(TodayStepAlertReceive):用來(lái)解決跨天計(jì)步模塊歸零問(wèn)題,由于計(jì)步傳感器不會(huì)根據(jù)天來(lái)分割只是返回當(dāng)前步數(shù)的總和,所以需要這個(gè)廣播來(lái)對(duì)計(jì)步模塊進(jìn)行分割,只要跨天了計(jì)步模塊就歸零從0開(kāi)始計(jì)步。
3.開(kāi)機(jī)廣播(TodayStepBootCompleteReceiver):開(kāi)機(jī)廣播用來(lái)解決手機(jī)重啟計(jì)步傳感器歸零問(wèn)題,由于計(jì)步傳感器手機(jī)重啟會(huì)歸零,所以收到開(kāi)機(jī)廣播會(huì)做步數(shù)合并,啟動(dòng)Service從上次關(guān)機(jī)的步數(shù)開(kāi)始累加。
4.數(shù)據(jù)庫(kù)(TodayStepDBHelper):用來(lái)記錄當(dāng)天步數(shù)明細(xì),一個(gè)時(shí)間對(duì)應(yīng)一個(gè)步數(shù)
5.加速度傳感器計(jì)步(TodayStepDcretor):由于android4.4以下或者一些特殊的手機(jī)不提供計(jì)步傳感器所以這些機(jī)型采用加速度傳感器進(jìn)行計(jì)步,通過(guò)OnStepCounterListener監(jiān)聽(tīng)返回給TodayStepService .
6.計(jì)步傳感器計(jì)步(TodayStepCounter):android4.4以上提供了計(jì)步協(xié)處理器,可以通過(guò)計(jì)步傳感器計(jì)步功耗小,計(jì)步準(zhǔn),通過(guò)OnStepCounterListener監(jiān)聽(tīng)返回給TodayStepService .
7.關(guān)機(jī)監(jiān)聽(tīng)(TodayStepShutdownReceiver):用來(lái)判斷手機(jī)是否關(guān)機(jī),當(dāng)重啟手機(jī)打開(kāi)計(jì)步Service根據(jù)這個(gè)標(biāo)志來(lái)判斷是否重啟進(jìn)行步數(shù)合并,主要是增加精度有時(shí)開(kāi)機(jī)廣播不能收到。

加速度傳感器計(jì)步流程圖

加速度傳感器計(jì)步流程圖.png

講解流程圖:
Android4.4以下或者一些特殊的手機(jī)不提供計(jì)步傳感器,我只能用加速度傳感器計(jì)步,加速度傳感器的原理就是利用一定的算法模擬出步數(shù)(加速度傳感器計(jì)步算法不在本篇文章討論的范圍之內(nèi)),用這種方式計(jì)步Service一定要在后臺(tái)存活否則不能計(jì)步,這種方式跨天分隔步數(shù)利用Intent.ACTION_TIME_TICK廣播回調(diào)來(lái)判斷當(dāng)前時(shí)間和上次PreferencesHelper記錄的時(shí)間是否相同如果不同步數(shù)歸零從0開(kāi)始計(jì)步,步數(shù)的記錄采用PreferencesHelper來(lái)保存,防止當(dāng)天重啟手機(jī)系統(tǒng)步數(shù)歸零。

計(jì)步傳感器計(jì)步流程圖

計(jì)步傳感器計(jì)步流程圖.png

講解流程圖:
Android4.4以上的可以使用計(jì)步傳感器進(jìn)行計(jì)步,至于計(jì)步傳感器的有點(diǎn)上面已經(jīng)介紹了。這種方式部分手機(jī)可以不需要程序自啟動(dòng)權(quán)限。
跨天分隔步數(shù)采用兩種方式:
1.第一種方式和上面一樣采用Intent.ACTION_TIME_TICK廣播,這里不多說(shuō)了。
2.第二種方式采用AlertManager方式也就是設(shè)置0點(diǎn)鬧鐘,在這個(gè)0點(diǎn)廣播中對(duì)步數(shù)進(jìn)行分隔,這個(gè)AlertManager不是每個(gè)手機(jī)都可以啟動(dòng)的。
手機(jī)系統(tǒng)重啟判斷采用四種方式:
1.開(kāi)機(jī)廣播監(jiān)聽(tīng)BOOT_COMPLETED,這個(gè)監(jiān)聽(tīng)不是每個(gè)手機(jī)都可以收到,如果收到可以啟動(dòng)Service,然后做步數(shù)合并使計(jì)步模塊從上次關(guān)機(jī)時(shí)的步數(shù)開(kāi)始累加,如果收不到只能用下面幾種方式增加重啟的判斷了。
2.關(guān)機(jī)廣播監(jiān)聽(tīng)ACTION_SHUTDOWN,這個(gè)監(jiān)聽(tīng)不是每個(gè)手機(jī)都可以收到,如果收到,在用戶手動(dòng)啟動(dòng) Service中可以判斷系統(tǒng)重啟了。
3.記錄運(yùn)行時(shí)間判斷手機(jī)重啟,上次運(yùn)行的時(shí)間大于當(dāng)前運(yùn)行時(shí)間判斷為重啟,只是增加精度,極端情況下連續(xù)重啟,會(huì)判斷不出來(lái)。
4.上次傳感器步數(shù)總和,當(dāng)前傳感器步數(shù)小于上次傳感器步數(shù)肯定是重新啟動(dòng)了,只是用來(lái)增加精度不是絕對(duì)的

計(jì)步傳感器計(jì)步核心流程
計(jì)步核心流程.png

這個(gè)流程圖的講解都在圖片上。

提高計(jì)步精度:

1.設(shè)置app后臺(tái)自啟動(dòng)
2.各種安全軟件設(shè)置app為白名單,為了保證app不被任何安全軟件在后臺(tái)殺死。
以上兩種方式保證通知欄中一直顯示app的步數(shù)。
3.手機(jī)系統(tǒng)重啟,如果通知欄中沒(méi)有顯示步數(shù),表示app沒(méi)有收到開(kāi)機(jī)監(jiān)聽(tīng),需要手動(dòng)啟動(dòng)app,否則步數(shù)會(huì)丟失 。
app一直在后臺(tái)存活肯定會(huì)耗電,部分手機(jī)可以在后臺(tái)關(guān)閉的情況下計(jì)步,但是這種方式需要每天早上打開(kāi)一次app讓計(jì)步模塊對(duì)步數(shù)進(jìn)行清零否則步數(shù)會(huì)丟失。

需要優(yōu)化:

1.每次傳感器回調(diào)都會(huì)寫(xiě)三次SharedPreferences。
2.計(jì)步模塊在后臺(tái)存活,每天過(guò)0點(diǎn)開(kāi)始計(jì)步都會(huì)丟失一些步數(shù),丟失的步數(shù)跟啟動(dòng)計(jì)步傳感器需要的步數(shù)有關(guān),例如:我的測(cè)試機(jī)連續(xù)走10步才可以啟動(dòng)計(jì)步傳感器回調(diào),所以就丟失10步。

總結(jié):

Android計(jì)步就是在和android系統(tǒng)作斗爭(zhēng),各種系統(tǒng)監(jiān)聽(tīng)回調(diào)都不好用(AlertManager、BOOT_COMPLETED、JobScheduler),還要解決計(jì)步傳感器的一些限制(系統(tǒng)重啟清零,不能自動(dòng)分天,部分手機(jī)進(jìn)程殺死不能計(jì)步),還要規(guī)避不同手機(jī)的問(wèn)題,我們只能盡量做到不丟失步數(shù),提高計(jì)步精度,目前我在測(cè)試計(jì)步發(fā)現(xiàn)支付寶計(jì)步非常準(zhǔn),我猜測(cè)系統(tǒng)為支付寶做了系統(tǒng)進(jìn)程。
只有不斷天坑,優(yōu)化,增加計(jì)步準(zhǔn)確性,也請(qǐng)個(gè)位大神下載代碼一起交流。

特此感謝 https://github.com/finnfu/stepcount 作者

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

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

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