轻+计步相关实现
?
1,概述
現在人們越來越注重健康,相應的健康類app也是種類繁多,使用的最多的就是減肥健身類和計步跑步類的應用。而現在安卓上的計步無非就是利用手機自帶的傳感器,獲取實時返回的數據后,再利用各自的算法過濾掉無效的步數,通過應用開啟的服務保持后臺持續進行,監測一整天的步行。
但因為市面上的安卓手機千差萬別,不同的廠商定制出來的系統又是不同的,所以傳感器的處理也是有所差別的,我們通過長時間收集用戶反饋不斷進行優化,本文將針對我們應用自帶的計步功能實現和遇到的困難和解決方法一一詳解,希望對讀者有所幫助,也歡迎讀者指出缺點互相改進。
2,實現
2.1?計步service
1、進到計步頁的時候,我們需要啟動一個service來保持后臺計步,所以首先定義一個自定義的Service。(一開始設計的時候沒有考慮到后臺進程這些問題,所以啟動service的方法是放在計步頁的,后來證實不可取,后面的相關問題會談到這個問題)
class StepService extends Service{}開始計步服務
private void startStepService() {Intent intent = new Intent(PedometerActivity.this, StepService.class);Bundle bundle = new Bundle(); //intent帶參數需要Bundlebundle.putInt("op", 1);intent.putExtras(bundle);startService(intent); }上面啟動service傳遞的參數是為了控制開始暫停的,下面會講到。
2、綁定service,為了能夠在計步頁實時顯示計步服務中拿回的步數,我們還需要在計步頁綁定一下service,只需要在計步頁綁定,退出計步頁時解除綁定。
/*** 這里綁定是為了計步頁面實時顯示service傳回的步數*/ private void bindStepService() {bindService(new Intent(PedometerActivity.this, StepService.class), mConnection, Context.BIND_AUTO_CREATE); }private StepService mService; private ServiceConnection mConnection = new ServiceConnection() {public void onServiceConnected(ComponentName className, IBinder service) {mService = ((StepService.StepBinder) service).getService();mService.registerCallback(mCallback);mService.reloadSettings();}public void onServiceDisconnected(ComponentName className) {mService = null;} };以下是service的回調
private StepService.ICallback mCallback = new StepService.ICallback() { public void stepsChanged(int value) {mHandler.sendMessage(mHandler.obtainMessage(STEPS_MSG, value, 0));}public void paceChanged(int value) {mHandler.sendMessage(mHandler.obtainMessage(PACE_MSG, value, 0));}public void distanceChanged(float value) {mHandler.sendMessage(mHandler.obtainMessage(DISTANCE_MSG, (int) (value * 1000), 0));}public void speedChanged(float value) {mHandler.sendMessage(mHandler.obtainMessage(SPEED_MSG, (int) (value * 1000), 0));}public void caloriesChanged(float value) {mHandler.sendMessage(mHandler.obtainMessage(CALORIES_MSG, (int) (value), 0));}@Overridepublic void timeChanged(String value) {Message msg = new Message();msg.what = TIME_MSG;msg.obj = value;mHandler.sendMessage(msg);}};2.2?service中相關監聽
3、?在計步service的onCreate()方法中初始化步數監聽。
首先定義一個獲取步數的接口:
public interface StepListener { void onStep(); void passValue(); }然后需要定義一些計步相關的監聽類
StepDisplayer?mStepDisplayer;?//步數
DistanceNotifier?mDistanceNotifier;?//距離
SpeedNotifier?mSpeedNotifier;?//速度
CaloriesNotifier?mCaloriesNotifier;?//熱量
以獲取步數的監聽類為例:?
/** 將計算的步數傳遞給activity顯示 */ public class StepDisplayer implements StepListener{ public static int mCount = 0;public StepDisplayer(PedometerSettings settings){ notifyListener(); } public void setSteps(int steps){ mCount = steps; notifyListener(); }@Override public void onStep() { mCount ++; notifyListener(); }@Override public void passValue() { } public void reloadSettings(){ notifyListener(); } public interface Listener{ void stepsChanged(int value); void passValue(); } private ArrayList<Listener> mListeners = new ArrayList<Listener>(); public void addListener(Listener l){ mListeners.add(l); } public void notifyListener(){ for(Listener listener:mListeners){ listener.stepsChanged(mCount); } }}然后需要設置相關的監聽:
mStepDisplayer.addListener(mStepListener); mStepDetector.addStepListener(mStepDisplayer);
(mStepDetector為StepDetector聲明的對象,StepDetector類實現SensorEventListener接口)
所以直接在步數通知的回調中,可以顯示處理拿回步數。
private StepDisplayer.Listener mStepListener = new StepDisplayer.Listener() {public void stepsChanged(int value) {mSteps = value;//此處拿到每次的步數,做相應的處理}}2.3?傳感器的處理
????說到這里,只是講到了在service中設置相關的通知,方便實時拿回步數,但還沒講到傳感器的處理,所以下面是介紹傳感器獲取步數的操作。
2.3.1?onStart()的不同處理
???上面講到的啟動服務傳遞的參數,是為了控制開始和暫停計步的,其實就是控制傳感器的注冊和取消注冊,啟動服務的方法可以多次調用,在service的onStart()里接收不同的參數進行不同的處理。
@Overridepublic void onStart(Intent intent, int startId) {if (intent != null) {Bundle bundle = intent.getExtras();if (bundle != null) {int op = bundle.getInt("op");switch(op){case 1: //開始registerDetector();break;case 2: //暫停unregisterDetector();mNM.cancel(R.string.app_name);break;}}}}2.3.2?定義傳感器相關類
SensorEventListener類是手機傳感器的監聽類,SensorManager類為傳感器管理類;
2.3.3?注冊傳感器
mSensor = mSensorManager.getDefaultSensor(sensorType);mSensorManager.registerListener(mStepDetector,mSensor,SensorManager.SENSOR_DELAY_NORMAL);這里的mSensor為Sensor類。上面第三個參數為采樣率:最快、游戲、普通、用戶界面。當應用程序請求特定的采樣率時,其實只是對傳感器子系統的一個建議,不保證特定的采樣率可用。
最快:?SensorManager.SENSOR_DELAY_FASTEST
最低延遲,一般不是特別敏感的處理不推薦使用,該種模式可能造成手機電力大量消耗,由于傳遞的為原始數據,算法不處理好將會影響游戲邏輯和UI的性能。
游戲:?SensorManager.SENSOR_DELAY_GAME
游戲延遲,一般絕大多數的實時性較高的游戲都使用該級別。
普通:?SensorManager.SENSOR_DELAY_NORMAL
標準延遲,對于一般的益智類或EASY級別的游戲可以使用,但過低的采樣率可能對一些賽車類游戲有跳幀現象。
用戶界面:?SensorManager.SENSOR_DELAY_UI
一般對于屏幕方向自動旋轉使用,相對節省電能和邏輯處理,一般游戲開發中我們不使用。
2.3.4?兩種傳感器處理
???現在使用到的兩種模式,用戶可以在計步設置頁手動選擇,分別為普通模式和內置計步器模式。普通模式為采用加速度傳感器,需要算法過濾掉無效的步數;內置計步器則不需要現在大部分手機(4.4以上)自帶計步傳感器,可以直接在回調里拿回步數直接顯示,不需要考慮步數是不是有效的,傳感器本身已經幫你處理了。
Sensor.TYPE_ACCELEROMETER;//默認采用加速度傳感器來處理,需要算法過濾無效步數
Sensor.TYPE_STEP_DETECTOR;//采用手機4.4以上自帶計步處理器,不需要算法過濾無效步數
?所以需要判斷手機是不是自帶內置計步器,相關代碼如下:
/*** 判斷手機是否支持4.4以上自帶計步處理器** @return*/ public void judgeIsSupportStepDetector() {int sensorTypeC = Sensor.TYPE_STEP_COUNTER;PackageManager pm = getPackageManager();boolean flag = pm.hasSystemFeature(PackageManager.FEATURE_SENSOR_STEP_DETECTOR);if (flag) {SensorManager mSensorManager = (SensorManager) this.getSystemService(Context.SENSOR_SERVICE);if (mSensorManager.getDefaultSensor(sensorTypeC) != null) {//支持內置計步器}}}
2.3.5?傳感器的回調
???SensorEventListener的onSensorChange()方法里,根據注冊傳感器時的類型,分別判斷是哪種傳感器回調的數據。
???onSensorChanged(SensorEvent?event)方法里通過SensorEvent?獲取類型和相關參數:
如果event.sensor.getType()?==?Sensor.TYPE_STEP_DETECTOR?則表示設置了內置計步器模式,判斷event.values[0]?==?1.0的話,調用我們前面定義的接口stepListener.onStep()本地步數直接加1;
如果event.sensor.getType()?==?Sensor.TYPE_ACCELEROMETER,表示采用加速度傳感器,我們就不能像內置計步器那樣直接本地步數加1,而是要通過自己的算法來過濾無效的步數。下面會介紹下過濾的算法的實現原理。
2.3.6?計步過濾算法實現原理(只是過濾傳感器無效的振幅)
(只針對普通模式,也就是加速度傳感器)
普通模式下,傳感器會不斷傳回不同的值,這個時候我們需要設置相關條件去過濾掉這些無效的振幅,得出我們最后需要的值,也就是有效的那一步。
?(1)?第一個條件:時間間隔不能太短。
定義一個最后的有效步數的時間a,默認是0毫秒,需滿足最后?(1)(2)(3)?所有條件得出一個有效步數后才把當前時間賦給a,在每次傳感器回調中獲取當前時間b,第一個判斷成立的條件是a-b?>?90,即兩次振幅的間隔必須大于90毫秒。
因為設置了靈敏度調節,所以這個時間90是可設置的,設置越小則靈敏度越高。
?(2)?第二個條件:振幅速度不能太小。
?????計算當前振幅速度speed?:
long?diff?=?now?-?mLastTime;?(now?為當前時間,mLastTime為條件1的a)
float?speed?=?Math.abs(event.values[SensorManager.DATA_X]? +?event.values[SensorManager.DATA_Y]?+?event.values[SensorManager.DATA_Z]?-?mLastX?-?mLastY?-?mLastZ)?/?diff?*?10000;
?mLastX?,mLastY?,mLastZ?分別為x軸,y軸,z軸的值。
?以下賦值只需要在第一個判斷成立時:
mLastTime?=?now;
mLastX?=?event.values[SensorManager.DATA_X];
mLastY?=?event.values[SensorManager.DATA_Y];
mLastZ?=?event.values[SensorManager.DATA_Z];
默認速度speed?大于?60為條件2成立,因為設置了靈敏度調節,所以這個60是變化的,設置越小則靈敏度越高。
?(補充一點,介紹android?的坐標系是如何定義x,?y?,z?軸的。
x軸的方向是沿著屏幕的水平方向從左向右,如果手機不是正方形的話,較短的邊需要水平放置,較長的邊需要垂直放置。
Y軸的方向是從屏幕的左下角開始沿著屏幕的的垂直方向指向屏幕的頂端。
將手機放在桌子上,z軸的方向是從手機指向天空。)
?(3)?第三個條件:
同時滿足?((++mShakeCount?>=?8)?且(now?-?mLastShake?>?520)?,
這里mShakeCount?為有效震動次數,必須大于8才開始計算步數,什么情況下歸零呢?
在條件一前面加多一個處理,
if?((now?-?mLastForce)?>?2000)?{?
???mShakeCount?=?0;?
}
這里的mLastForce為條件二成立時賦予當前時間,如果兩次震動間隔大于2000毫秒,則上面的mShakeCount?歸零。
mLastShake為上一次獲取到有效步數時記錄的時間,兩次震動的最小時間間隔低于520毫秒則不算有效步伐。
?根據上述三個條件同時依次成立,得出來的視為有效步數。
(請參考源代碼)
?
到這里,大概計步的實現流程就結束了。但實際情況下,遇到的問題卻是千奇百怪,單單是寫這樣的流程是無法滿足我們的計步應用在后臺長時間運行的。所以我們總結了用戶的反饋后,整理出了以下的問題和相應的解決方法。
3,?遇到的問題及相應的解決辦法
1、計步不準。
???在以前的版本中,我們只是利用速度傳感器去處理計步,而忽略了不同手機廠商速度傳感器的敏感度時不同的,所以導致在相同的過濾算法下,不同手機計步會有很大偏差。
解決方法:
(1)?增加調節靈敏度調節入口,只針對速度傳感器類型的,通過設置速度和兩次有效步數時間間隔來提高或降低敏感度。
(2)?安卓4.4新增了一個新的傳感器類型Sensor.TYPE_STEP_DETECTOR計步傳感器,經測試發現注冊這個類型的傳感器,步數最為準確。后來發現不只是4.4的系統,4.4以上的也支持,但不是都支持。
(3)?增加普通模式和內置計步器模式,分別為速度傳感器和計步傳感器模式供用戶自主選擇。
2、鎖屏不計步。
???遇到過很多用戶反饋鎖屏后計步完全停滯了,檢查發現并不是計步的服務停止了,而是傳感器根本沒響應,但屏幕亮了之后又能正常計步。有些手機廠商為了省電,會在鎖屏下把一些傳感器關閉或者敏感度降的極低。當然這是一兩年前發現的問題,現在大部分手機應該不會有這個問題。
解決方法:
增加強制計步模式。也就是監聽用戶鎖屏后,強制喚醒屏幕,但是屏幕亮度為最低,保證喚醒傳感器的同時,不至于太耗電。這里不贅述強制喚醒屏幕的方法。
3、計步了一段時間后不計步了,要進入計步頁才能恢復。
???我們原來只是在計步頁開啟和綁定計步服務,而且并沒有判斷計步服務是否正常,所以導致應用的進程因為各種情況結束掉后,必須進入計步頁才能恢復。
解決方法:
(1)?判斷計步服務是否正常運行
代碼如下:
/*** 判斷某個服務是否正在運行的方法** @param mContext* @param setpPakcageName 是包名+服務的類名(例net.loonggg.testbackstage.TestService)* @return true代表正在運行,false代表服務沒有正在運行*/ public static boolean isStepServiceWork(Context mContext) {boolean isWork = false;ActivityManager myAM = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);List<ActivityManager.RunningServiceInfo> myList = myAM.getRunningServices(40);if (myList.size() <= 0) {return false;}for (int i = 0; i < myList.size(); i++) {String mName = myList.get(i).service.getClassName().toString();if (mName.equals(setpPakcageName)) {isWork = true;break;}}return isWork; }(2)?父類MainActivity類的onResume()判斷計步服務是否正常,如果已經關閉了則重新啟動服務,不需要進入計步頁才啟動;
(3)?推送消息的監聽里判斷計步服務,同上;
(4)?增加鎖屏和解鎖屏幕的廣播,在廣播里判斷計步服務是否正常;
(5)?點擊手機硬件menu鍵或home鍵,彈出正在運行的應用列表,找到我們的應用,下拉后松開,鎖住應用,就能防止應用進程被系統殺掉。(最可靠)
?4、計步過于靈敏,不走動搖晃手機都會計步。
???使用過樂動力或動動的都會發現,你剛開始走步的時候它不會立馬更新步數,等走了大概10步后才會開始更新,如果走了幾步又停下來一定時間之后,發現又得重新走大概10步才會更新步數。
? ?解決方法:
新增一個穩定步數范圍的算法,原理如下:
(1)?定義一個有效步數累計的對象startUsefulStep
(2)?每獲取一個有效步數時記錄下時間,兩步間隔大于于5秒的話,重新開始過濾有效步數;
(3)?每步間隔小于5秒,startUsefulStep累加,連續走了10步;
/*** 是否開始有效步數的計步*/ public static boolean isStartUsefulStep(int step) {boolean isStartPed = false;//是否正式開始計步SharedPreferences mSetting = null;PedometerSettings mPedometerSettings = null;if (mSetting == null || mPedometerSettings == null) {mSetting = PreferenceManager.getDefaultSharedPreferences(ApplicationEx.getInstance());mPedometerSettings = new PedometerSettings(mSetting, ApplicationEx.getInstance());}startUsefulStep += step;//兩步的間隔大于5秒,重新開始過濾無效步數if ((new Date().getTime() - mPedometerSettings.getLastPedTime()) > 5000) {startUsefulStep = 0;}mPedometerSettings.saveLastPedTime(new Date().getTime());//每步間隔為5秒內,連續走了10步,視為正式開始計步if (startUsefulStep >= 10) {isStartPed = true;}return isStartPed;}這樣的話,當執行完這個方法后結果為true時,要先在原步數上加10步再開始累加。
?5、應用因為崩潰或其他原因導致程序退出,下次進來時步數不見了
???這種情況很好處理,在計步的回調方法里,判斷當前的步數的倍數是不是9的倍數(這個數字自己定義,但是不能太大,最好不要超過50;也不要每一步就保存;最好也不要使用10的倍數,不然看起來每次恢復的步數都是10的倍數,會有點假。),是的話保存一次步數到文件,下次進來的時候判斷一下文件里保存的步數。
4,未能解決的問題
1、進程被系統干掉。例如oppo,自帶的安全中心-省電管理中,如果未將應用加入到可信賴的列表或者是關閉省電模式,鎖屏后應用進程容易被殺掉;我們告知用戶的操作方法是點擊硬件menu鍵或home鍵,彈出正在運行的應用列表,找到我們的應用,下拉一次后松開鎖住應用,就能防止應用進程被殺掉。
2、計步算法不夠準確,不同手機上的結果還是有較大偏差;
3、當前的算法不支持智能識別跑步
5,總結
計步本身其實沒什么太多的技術難點,復雜的是過濾有效振幅的算法(當前我們的算法還是比較弱智的水平),能夠較智能(我不認為現在計步比較好的像樂動力,動動的算法就是百分百智能的)識別不同的場景。而且不同手機的傳感器硬件也是不同的,所以可能相同的算法處理出來的邏輯又會有所不同。所以這個還需要不斷的優化,但也從中更加認識安卓平臺碎片化的嚴重性,也能更好的知道如何應對同設備帶來的不同問題,從中也能了解到不同用戶的使用習慣,更好的改進計步的功能。
?
總結
- 上一篇: 解决百度地图使用出现的has leake
- 下一篇: Oracle11gR2_ADG管理之恢复