所有博文已遷移至個(gè)人網(wǎng)站:http://ravenxrz.ink,請勿再留言評論,本文新鏈接:http://ravenxrz.ink/2019/05/03/android-stepcounting-code.html
前言
Android端計(jì)步代碼,經(jīng)測試較為精準(zhǔn)。使用了計(jì)步傳感器(耗能低,準(zhǔn)確度高)和加速度傳感器(適用度廣,基本上各個(gè)手機(jī)上都有這個(gè)傳感器),代碼中牽涉到數(shù)據(jù)的操作,數(shù)據(jù)庫我使用的是GreenDao,相關(guān)代碼就不貼了,因?yàn)榕渲靡幌戮秃昧耍吘怪卦谟?jì)步嘛
主要使用到的技術(shù):
- 傳感器的使用
- 廣播監(jiān)聽
- 計(jì)時(shí)類
- 常駐通知
- 使用Messenger進(jìn)行跨進(jìn)程通信
- 計(jì)步算法
效果圖
實(shí)物不好拍攝,就直接錄了個(gè)GIF,實(shí)際用著還是不錯(cuò)的。
廢話不多說,直接上代碼
public class StepService extends Service implements SensorEventListener {
//TAG
private static String TAG = "StepService";
//存儲間隔
private static int duration = 3000;
//當(dāng)前日期
private static String CURRENT_DATE = "";
//傳感器
private SensorManager sensorManager;
//廣播--監(jiān)聽手機(jī)狀態(tài)變化
private BroadcastReceiver mReceiver;
//===============================================倒計(jì)時(shí)
private TimeCount time;
//===============================================數(shù)據(jù)庫操作
private StepsDao stepsDao = DBUtil.getStepsDao();
//當(dāng)前步數(shù)
private int CURRENT_STEPS;
//期望步數(shù)
private float EXPECT_STEPS;
//計(jì)步傳感器類型 0-counter 1-detector
private static int stepSensor = -1;
//是否記錄
private boolean hasRecord = false;
//已經(jīng)走過的步數(shù)
private int hasStepCount = 0;
//以前走過的步數(shù)
private int previousStepCount = 0;
//===============================================采用加速度傳感器所要用到的變量
public static float SENSITIVITY = 10; // SENSITIVITY靈敏度
private float mLastValues[] = new float[3 * 2];
private float mScale[] = new float[2];
private float mYOffset;
private static long end = 0;
private static long start = 0;
/**
* 最后加速度方向
*/
private float mLastDirections[] = new float[3 * 2];
private float mLastExtremes[][] = { new float[3 * 2], new float[3 * 2] };
private float mLastDiff[] = new float[3 * 2];
private int mLastMatch = -1;
//===============================================messenger
//跨進(jìn)程通信--使用Messenger方式
private Messenger messengerFromService = new Messenger(new MessengerHandler());
private Messenger messengerFromClient;
private class MessengerHandler extends Handler{
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case Constants.MSG_FROM_CLIENT:
messengerFromClient = msg.replyTo;
StepService.this.sendMessage();
break;
default:
break;
}
}
}
//===============================================通知相關(guān)
private static final int NOTIFI_ID = 100;
//格式管理
private DecimalFormat df = new DecimalFormat("#0.0");
@Override
public void onCreate() {
super.onCreate();
EXPECT_STEPS = 8000f;
//初始化廣播
initBroadcastReceiver();
new Thread(new Runnable() {
@Override
public void run() {
//獲取傳感器類型
startStepDetector();
}
}).start();
startTimeCount();
initTodayData();
}
/**
* 取得今日日期
* @return
*/
private String getTodayDate(){
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.format(date);
}
/**
* 初始化當(dāng)天的步數(shù)
*/
private void initTodayData() {
CURRENT_DATE = getTodayDate();
//===============================================在這里進(jìn)行數(shù)據(jù)新的一列的存儲
//通過日期匹配,當(dāng)數(shù)據(jù)中有今日步數(shù)的行,那么將步數(shù)值進(jìn)行讀取,如果沒有那么久新增一行,并將CURRENT_STEP存儲進(jìn)去
QueryBuilder qb = stepsDao.queryBuilder();
qb.where(StepsDao.Properties.Date.eq(getTodayDate()));
Steps steps = (Steps) qb.unique();
if(steps!=null){
CURRENT_STEPS = steps.getUStep();
}else{
//增加一行
Steps stepsAdd = new Steps();
stepsAdd.setDate(CURRENT_DATE);
stepsAdd.setUStep(0);
stepsAdd.setHasUpLoad(false);
stepsDao.insert(stepsAdd);
}
}
/**
* 獲取當(dāng)前步數(shù)占有率
*/
private String getCurrentOccupancy(){
//默認(rèn)8000,完善時(shí)在Service啟動的時(shí)候進(jìn)行復(fù)制
return df.format((float)CURRENT_STEPS/EXPECT_STEPS*100);
}
/**
* 獲取傳感器實(shí)例
*/
private void startStepDetector() {
if(sensorManager!=null){
sensorManager = null;
}
sensorManager = (SensorManager) this.getSystemService(SENSOR_SERVICE);
addCountStepListener();
}
Sensor countSensor ;
Sensor detectorSensor ;
Sensor accelerateSensor;
private void addCountStepListener() {
countSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
detectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
//利用加速度傳感器
accelerateSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if(countSensor!=null){
//選擇計(jì)步傳感器
stepSensor = 0;
Log.i(TAG,"計(jì)步傳感器");
sensorManager.registerListener(StepService.this,countSensor,SENSOR_DELAY_NORMAL);
}else if(detectorSensor!=null){
//步數(shù)檢測器
stepSensor = 1;
Log.i(TAG,"步數(shù)監(jiān)測器");
sensorManager.registerListener(StepService.this,detectorSensor,SENSOR_DELAY_FASTEST);
}else if(accelerateSensor != null){
stepSensor = 2;
int h = 480;
mYOffset = h * 0.5f;
mScale[0] = -(h * 0.5f * (1.0f / (SensorManager.STANDARD_GRAVITY * 2)));
mScale[1] = -(h * 0.5f * (1.0f / (SensorManager.MAGNETIC_FIELD_EARTH_MAX)));
Log.i(TAG,"加速度傳感器");
sensorManager.registerListener(StepService.this,accelerateSensor,SENSOR_DELAY_FASTEST);
}
}
/**
* 傳感器回調(diào)
* @param event
*/
@Override
public void onSensorChanged(SensorEvent event) {
if(stepSensor == 0){
int tempStep = (int) event.values[0];
if(!hasRecord){
hasRecord = true;
hasStepCount = tempStep;
}else{
int thisStepCount = tempStep -hasStepCount;
CURRENT_STEPS+=(thisStepCount-previousStepCount);
previousStepCount = thisStepCount;
}
sendMessage();
setNotification();
}else if(stepSensor == 1){
if(event.values[0] == 1.0){
hasRecord = true;
CURRENT_STEPS++;
sendMessage();
setNotification();
}
}else if(stepSensor == 2){
hasRecord = true;
synchronized (this) {
float vSum = 0;
for (int i = 0; i < 3; i++) {
final float v = mYOffset + event.values[i] * mScale[1];
vSum += v;
}
int k = 0;
float v = vSum / 3;
float direction = (v > mLastValues[k] ? 1
: (v < mLastValues[k] ? -1 : 0));
if (direction == -mLastDirections[k]) {
// Direction changed
int extType = (direction > 0 ? 0 : 1); // minumum or
// maximum?
mLastExtremes[extType][k] = mLastValues[k];
float diff = Math.abs(mLastExtremes[extType][k]
- mLastExtremes[1 - extType][k]);
if (diff > SENSITIVITY) {
boolean isAlmostAsLargeAsPrevious = diff > (mLastDiff[k] * 2 / 3);
boolean isPreviousLargeEnough = mLastDiff[k] > (diff / 3);
boolean isNotContra = (mLastMatch != 1 - extType);
if (isAlmostAsLargeAsPrevious && isPreviousLargeEnough
&& isNotContra) {
end = System.currentTimeMillis();
if (end - start > 500) {// 此時(shí)判斷為走了一步
CURRENT_STEPS++;
sendMessage();
setNotification();
mLastMatch = extType;
start = end;
}
} else {
mLastMatch = -1;
}
}
mLastDiff[k] = diff;
}
mLastDirections[k] = direction;
mLastValues[k] = v;
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
private void sendMessage(){
Message msg = Message.obtain(null,Constants.MSG_FROM_SERVICE);
Bundle bundle = new Bundle();
bundle.putInt("currentSteps",CURRENT_STEPS);
msg.setData(bundle);
try {
if(hasRecord&&messengerFromClient!=null) {
messengerFromClient.send(msg);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
/**
* 注冊廣播
*/
private void initBroadcastReceiver() {
final IntentFilter filter = new IntentFilter();
// 屏幕滅屏廣播
filter.addAction(Intent.ACTION_SCREEN_OFF);
//關(guān)機(jī)廣播
filter.addAction(Intent.ACTION_SHUTDOWN);
// 屏幕亮屏廣播
filter.addAction(Intent.ACTION_SCREEN_ON);
// 屏幕解鎖廣播
filter.addAction(Intent.ACTION_USER_PRESENT);
// 當(dāng)長按電源鍵彈出“關(guān)機(jī)”對話或者鎖屏?xí)r系統(tǒng)會發(fā)出這個(gè)廣播
// 所以監(jiān)聽這個(gè)廣播,當(dāng)收到時(shí)就隱藏自己的對話,如點(diǎn)擊pad右下角部分彈出的對話框
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
//監(jiān)聽日期變化
filter.addAction(Intent.ACTION_DATE_CHANGED);
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIME_TICK);
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(Intent.ACTION_SCREEN_ON.equals(action)){
Log.i(TAG,"ScreenOn");
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
Log.i(TAG,"ScreenOff");
//60s
duration = 6000;
}else if (Intent.ACTION_USER_PRESENT.equals(action)){
Log.i(TAG,"screen unlock");
save();
//30s
duration = 3000;
}else if(Intent.ACTION_SHUTDOWN.equals(action)){
Log.i(TAG,"shutdown");
save();
}else if(Intent.ACTION_DATE_CHANGED.equals(action)){
save();
isNewDay();
}else if(Intent.ACTION_TIME_CHANGED.equals(action)){
save();
isNewDay();
}else if(Intent.ACTION_TIME_TICK.equals(action)){
save();
// isNewDay();
}else if("ANewacount".equals(action)){
//當(dāng)時(shí)一個(gè)新用戶登錄的時(shí)候
//清除數(shù)據(jù)
Log.i(TAG,"收到新賬戶廣播");
initTodayData();
}
}
};
registerReceiver(mReceiver,filter);
}
/**
* 0點(diǎn)時(shí)初始化數(shù)據(jù)
*/
private void isNewDay() {
String time = "00:00";
if(time.equals(new SimpleDateFormat("HH:mm").format(new Date()))||(!CURRENT_DATE.equals(getTodayDate()))){
initTodayData();
}
}
/**
* 存儲到數(shù)據(jù)中去
*/
private void save() {
int tempStep = CURRENT_STEPS;
QueryBuilder qb = stepsDao.queryBuilder();
qb.where(StepsDao.Properties.Date.eq(getTodayDate()));
Steps steps = (Steps) qb.unique();
//不為空時(shí),說明還未到12點(diǎn),我們進(jìn)行更新就行,為空說明為最后一次存儲
if(steps!=null){
steps.setUStep(tempStep);
steps.setDate(CURRENT_DATE);
stepsDao.update(steps);
}else{
steps = new Steps();
steps.setUStep(tempStep);
steps.setDate(CURRENT_DATE);
stepsDao.update(steps);
}
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return messengerFromService.getBinder();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
setNotification();
return START_STICKY;
//如果被系統(tǒng)kill掉,系統(tǒng)會自動將kill時(shí)的狀態(tài)保留為開始狀態(tài),之后進(jìn)行重連
}
private void setNotification() {
PendingIntent pd = PendingIntent.getActivity(this,0,new Intent(this, MainViewActivity.class),0);
//在這里進(jìn)行前臺服務(wù)
Notification.Builder builder = new Notification.Builder(this)
.setOngoing(true)
.setSmallIcon(R.drawable.poplog)
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(),R.drawable.poplog))
.setContentTitle("當(dāng)前步數(shù)"+CURRENT_STEPS)
.setContentText("今日完成百分比"+getCurrentOccupancy()+"%")
.setWhen(System.currentTimeMillis())
.setContentIntent(pd);
startForeground(NOTIFI_ID,builder.build());
}
@Override
public void onDestroy() {
super.onDestroy();
//取消前臺進(jìn)程
stopForeground(true);
unregisterReceiver(mReceiver);
//注銷各傳感器
if(countSensor!= null || detectorSensor != null || accelerateSensor != null){
sensorManager.unregisterListener(StepService.this);
}
}
/**
* 開始計(jì)時(shí)
*/
private void startTimeCount() {
time = new TimeCount(duration,1000);
time.start();
}
private class TimeCount extends CountDownTimer{
/**
* @param millisInFuture The number of millis in the future from the call
* to {@link #start()} until the countdown is done and {@link #onFinish()}
* is called.
* @param countDownInterval The interval along the way to receive
* {@link #onTick(long)} callbacks.
*/
public TimeCount(long millisInFuture, long countDownInterval) {
super(millisInFuture, countDownInterval);
}
@Override
public void onTick(long millisUntilFinished) {
}
@Override
public void onFinish() {
//如果計(jì)時(shí)器正常結(jié)束,則開始計(jì)步
time.cancel();
save();
startTimeCount();
}
}
}
代碼分析
onCreate
@Override
public void onCreate() {
super.onCreate();
EXPECT_STEPS = 8000f;
//初始化廣播
initBroadcastReceiver();
new Thread(new Runnable() {
@Override
public void run() {
//獲取傳感器類型
startStepDetector();
}
}).start();
startTimeCount();
initTodayData();
}
代碼上寫得很簡單明了,即:初始廣播,獲取傳感器類型,開始計(jì)時(shí),以及初始化日期
初始化廣播
/**
* 注冊廣播
*/
private void initBroadcastReceiver() {
final IntentFilter filter = new IntentFilter();
// 屏幕滅屏廣播
filter.addAction(Intent.ACTION_SCREEN_OFF);
//關(guān)機(jī)廣播
filter.addAction(Intent.ACTION_SHUTDOWN);
// 屏幕亮屏廣播
filter.addAction(Intent.ACTION_SCREEN_ON);
// 屏幕解鎖廣播
filter.addAction(Intent.ACTION_USER_PRESENT);
// 當(dāng)長按電源鍵彈出“關(guān)機(jī)”對話或者鎖屏?xí)r系統(tǒng)會發(fā)出這個(gè)廣播
// 所以監(jiān)聽這個(gè)廣播,當(dāng)收到時(shí)就隱藏自己的對話,如點(diǎn)擊pad右下角部分彈出的對話框
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
//監(jiān)聽日期變化
filter.addAction(Intent.ACTION_DATE_CHANGED);
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIME_TICK);
mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if(Intent.ACTION_SCREEN_ON.equals(action)){
Log.i(TAG,"ScreenOn");
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
Log.i(TAG,"ScreenOff");
//60s
duration = 6000;
}else if (Intent.ACTION_USER_PRESENT.equals(action)){
Log.i(TAG,"screen unlock");
save();
//30s
duration = 3000;
}else if(Intent.ACTION_SHUTDOWN.equals(action)){
Log.i(TAG,"shutdown");
save();
}else if(Intent.ACTION_DATE_CHANGED.equals(action)){
save();
isNewDay();
}else if(Intent.ACTION_TIME_CHANGED.equals(action)){
save();
isNewDay();
}else if(Intent.ACTION_TIME_TICK.equals(action)){
save();
// isNewDay();
}else if("ANewacount".equals(action)){
//當(dāng)時(shí)一個(gè)新用戶登錄的時(shí)候
//清除數(shù)據(jù)
Log.i(TAG,"收到新賬戶廣播");
initTodayData();
}
}
};
registerReceiver(mReceiver,filter);
}
在這段代碼里,主要是監(jiān)聽用戶手機(jī)狀態(tài)的改變,根據(jù)狀態(tài)的改變進(jìn)行不同的操作,如“用戶關(guān)機(jī)時(shí),將當(dāng)前步數(shù)進(jìn)行保存”、“當(dāng)?shù)竭_(dá)凌晨0點(diǎn)時(shí)需要進(jìn)行新的一天的數(shù)據(jù)庫更新”等等。別忘了注銷Receiver哦
獲取傳感器
/**
* 獲取傳感器實(shí)例
*/
private void startStepDetector() {
if(sensorManager!=null){
sensorManager = null;
}
sensorManager = (SensorManager) this.getSystemService(SENSOR_SERVICE);
addCountStepListener();
}
Sensor countSensor ;
Sensor detectorSensor ;
Sensor accelerateSensor;
private void addCountStepListener() {
countSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
detectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
//利用加速度傳感器
accelerateSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
if(countSensor!=null){
//選擇計(jì)步傳感器
stepSensor = 0;
Log.i(TAG,"計(jì)步傳感器");
sensorManager.registerListener(StepService.this,countSensor,SENSOR_DELAY_NORMAL);
}else if(detectorSensor!=null){
//步數(shù)檢測器
stepSensor = 1;
Log.i(TAG,"步數(shù)監(jiān)測器");
sensorManager.registerListener(StepService.this,detectorSensor,SENSOR_DELAY_FASTEST);
}else if(accelerateSensor != null){
stepSensor = 2;
int h = 480;
mYOffset = h * 0.5f;
mScale[0] = -(h * 0.5f * (1.0f / (SensorManager.STANDARD_GRAVITY * 2)));
mScale[1] = -(h * 0.5f * (1.0f / (SensorManager.MAGNETIC_FIELD_EARTH_MAX)));
Log.i(TAG,"加速度傳感器");
sensorManager.registerListener(StepService.this,accelerateSensor,SENSOR_DELAY_FASTEST);
}
}
這個(gè)沒什么好說的,就是獲取各個(gè)傳感器對象,首先選擇的是計(jì)步傳感器,如果沒有計(jì)步傳感器那么久檢查是否有加速度傳感器,同樣的別忘了注銷傳感器對象。
計(jì)步的核心代碼
/**
* 傳感器回調(diào)
* @param event
*/
@Override
public void onSensorChanged(SensorEvent event) {
if(stepSensor == 0){
int tempStep = (int) event.values[0];
if(!hasRecord){
hasRecord = true;
hasStepCount = tempStep;
}else{
int thisStepCount = tempStep -hasStepCount;
CURRENT_STEPS+=(thisStepCount-previousStepCount);
previousStepCount = thisStepCount;
}
sendMessage();
setNotification();
}else if(stepSensor == 1){
if(event.values[0] == 1.0){
hasRecord = true;
CURRENT_STEPS++;
sendMessage();
setNotification();
}
}else if(stepSensor == 2){
hasRecord = true;
synchronized (this) {
float vSum = 0;
for (int i = 0; i < 3; i++) {
final float v = mYOffset + event.values[i] * mScale[1];
vSum += v;
}
int k = 0;
float v = vSum / 3;
float direction = (v > mLastValues[k] ? 1
: (v < mLastValues[k] ? -1 : 0));
if (direction == -mLastDirections[k]) {
// Direction changed
int extType = (direction > 0 ? 0 : 1); // minumum or
// maximum?
mLastExtremes[extType][k] = mLastValues[k];
float diff = Math.abs(mLastExtremes[extType][k]
- mLastExtremes[1 - extType][k]);
if (diff > SENSITIVITY) {
boolean isAlmostAsLargeAsPrevious = diff > (mLastDiff[k] * 2 / 3);
boolean isPreviousLargeEnough = mLastDiff[k] > (diff / 3);
boolean isNotContra = (mLastMatch != 1 - extType);
if (isAlmostAsLargeAsPrevious && isPreviousLargeEnough
&& isNotContra) {
end = System.currentTimeMillis();
if (end - start > 500) {// 此時(shí)判斷為走了一步
CURRENT_STEPS++;
sendMessage();
setNotification();
mLastMatch = extType;
start = end;
}
} else {
mLastMatch = -1;
}
}
mLastDiff[k] = diff;
}
mLastDirections[k] = direction;
mLastValues[k] = v;
}
}
}
- 計(jì)步傳感器
首先從傳感器中拿到tempStep,它代表著從計(jì)步以來的總步數(shù),所以需要記錄上次走的步數(shù),用這次步數(shù)減去上次走的步數(shù)得到的差值,再疊加,就可以得到當(dāng)前走的總步數(shù)了。 - 加速度傳感器
這部分的算法似乎是某個(gè)google的大神寫的,我也沒看懂 - 另外
sendMessage
和setNotification
分別為更新UI界面和更新通知欄。
寫在最后
因?yàn)閷戇@個(gè)功能的時(shí)候參看了不少代碼,而且時(shí)間較為久了,參看了不少博文,忘了具體的博主了,這里就感謝所有博主了。ok,上課去了。