Android實(shí)現(xiàn)簡(jiǎn)易計(jì)步器功能隔天步數(shù)清零查看歷史運(yùn)動(dòng)紀(jì)錄
最近需要用到計(jì)步功能,這可難壞我了,iOS端倒好,有自帶的計(jì)步功能,讓我驚訝的是連已爬樓層都給做好了,只需要調(diào)接口便可獲得數(shù)據(jù),我有一句MMP,我很想講。
但是抱怨歸抱怨,功能還是得事先的去實(shí)現(xiàn),微信運(yùn)動(dòng),樂(lè)動(dòng)力,都還不錯(cuò),尤其是樂(lè)動(dòng)力的計(jì)步功能真的非常的強(qiáng)大,在UI域用戶(hù)與用戶(hù)交互也做得非常棒,內(nèi)需當(dāng)連續(xù)運(yùn)動(dòng)十步后開(kāi)始計(jì)步。本想著去找他們實(shí)現(xiàn)的算法然后拿來(lái)用,但很明顯這是不可能的。后來(lái)我搜了很多資料發(fā)現(xiàn),在Android4.4 Kitkat 新增的STEP DETECTOR 以及 STEP COUNTER傳感器。但是!Android的這個(gè)傳感器雖然可以計(jì)步,但是所記錄的步數(shù)是從你開(kāi)機(jī)之時(shí)開(kāi)始計(jì)算,不斷累加,隔天也不會(huì)清零,并且,一旦關(guān)機(jī)后,傳感器記錄的數(shù)據(jù)也就清空了!這就很尷尬了,不過(guò)既然直接使用傳感器數(shù)據(jù)不行,那我們就自己動(dòng)手,將數(shù)據(jù)按天來(lái)保存~接下來(lái)進(jìn)入正題,皮皮猿,我們走起~
先來(lái)看下我們需要解決的點(diǎn)有:
1、步數(shù)從開(kāi)機(jī)之后不斷累加,關(guān)機(jī)之后便清零,步數(shù)不能隔天清零
2、不能查看歷史數(shù)據(jù)
這就好辦了。我們只需將當(dāng)前傳感器記錄的步數(shù)以每天為單位存進(jìn)數(shù)據(jù)庫(kù),如果更新的步數(shù)為當(dāng)天的則去更新數(shù)據(jù)庫(kù)!先來(lái)看下我的界面(Demo在文章最后):
第一二張圖為界面效果圖,數(shù)據(jù)均是從數(shù)據(jù)取出繪制在界面上,第三張圖為設(shè)置前臺(tái)進(jìn)程時(shí)所設(shè)置的Notification樣式,當(dāng)然了這個(gè)可以去自定義樣式,再此我就不詳細(xì)解釋了。
工程的目錄結(jié)構(gòu)如下:

其中主要的代碼都在StepService.class 中了,其中注釋也都非常詳細(xì),我就直接放代碼了:
/**
* Created by fySpring
* Date : 2017/3/24
* To do :
*/
public class StepService extends Service implements SensorEventListener {
public static final String TAG = "StepService";
//當(dāng)前日期
private static String CURRENT_DATE;
//當(dāng)前步數(shù)
private int CURRENT_STEP;
//3秒進(jìn)行一次存儲(chǔ)
private static int saveDuration = 3000;
//傳感器
private SensorManager sensorManager;
//數(shù)據(jù)庫(kù)
private StepDataDao stepDataDao;
//計(jì)步傳感器類(lèi)型 0-counter 1-detector
private static int stepSensor = -1;
//廣播接收
private BroadcastReceiver mInfoReceiver;
//自定義簡(jiǎn)易計(jì)時(shí)器
private TimeCount timeCount;
//發(fā)送消息,用來(lái)和Service之間傳遞步數(shù)
private Messenger messenger = new Messenger(new MessengerHandler());
//是否有當(dāng)天的記錄
private boolean hasRecord;
//未記錄之前的步數(shù)
private int hasStepCount;
//下次記錄之前的步數(shù)
private int previousStepCount;
private Notification.Builder builder;
private NotificationManager notificationManager;
private Intent nfIntent;
@Override
public void onCreate() {
super.onCreate();
initBroadcastReceiver();
new Thread(new Runnable() {
public void run() {
getStepDetector();
}
}).start();
startTimeCount();
initTodayData();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
/**
* 此處設(shè)將Service為前臺(tái),不然當(dāng)APP結(jié)束以后很容易被GC給干掉,這也就是大多數(shù)音樂(lè)播放器會(huì)在狀態(tài)欄設(shè)置一個(gè)
* 原理大都是相通的
*/
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//獲取一個(gè)Notification構(gòu)造器
builder = new Notification.Builder(this.getApplicationContext());
/**
* 設(shè)置點(diǎn)擊通知欄打開(kāi)的界面,此處需要注意了,如果你的計(jì)步界面不在主界面,則需要判斷app是否已經(jīng)啟動(dòng),
* 再來(lái)確定跳轉(zhuǎn)頁(yè)面,這里面太多坑,(別問(wèn)我為什么知道 - -)
* 總之有需要的可以和我交流
*/
nfIntent = new Intent(this, MainActivity.class);
builder.setContentIntent(PendingIntent.getActivity(this, 0, nfIntent, 0)) // 設(shè)置PendingIntent
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher)) // 設(shè)置下拉列表中的圖標(biāo)(大圖標(biāo))
.setContentTitle("今日步數(shù)"+CURRENT_STEP+"步") // 設(shè)置下拉列表里的標(biāo)題
.setSmallIcon(R.mipmap.ic_launcher) // 設(shè)置狀態(tài)欄內(nèi)的小圖標(biāo)
.setContentText("加油,要記得勤加運(yùn)動(dòng)"); // 設(shè)置上下文內(nèi)容
// 獲取構(gòu)建好的Notification
Notification stepNotification = builder.build();
notificationManager.notify(110,stepNotification);
// 參數(shù)一:唯一的通知標(biāo)識(shí);參數(shù)二:通知消息。
startForeground(110, stepNotification);// 開(kāi)始前臺(tái)服務(wù)
return START_STICKY;
}
/**
* 自定義handler
*/
private class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constant.MSG_FROM_CLIENT:
try {
//這里負(fù)責(zé)將當(dāng)前的步數(shù)發(fā)送出去,可以在界面或者其他地方獲取,我這里是在MainActivity中獲取來(lái)更新界面
Messenger messenger = msg.replyTo;
Message replyMsg = Message.obtain(null, Constant.MSG_FROM_SERVER);
Bundle bundle = new Bundle();
bundle.putInt("steps", CURRENT_STEP);
replyMsg.setData(bundle);
messenger.send(replyMsg);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
default:
super.handleMessage(msg);
}
}
}
/**
* 初始化廣播
*/
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_USER_PRESENT);
// 當(dāng)長(zhǎng)按電源鍵彈出“關(guān)機(jī)”對(duì)話或者鎖屏?xí)r系統(tǒng)會(huì)發(fā)出這個(gè)廣播
// example:有時(shí)候會(huì)用到系統(tǒng)對(duì)話框,權(quán)限可能很高,會(huì)覆蓋在鎖屏界面或者“關(guān)機(jī)”對(duì)話框之上,
// 所以監(jiān)聽(tīng)這個(gè)廣播,當(dāng)收到時(shí)就隱藏自己的對(duì)話,如點(diǎn)擊pad右下角部分彈出的對(duì)話框
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
//監(jiān)聽(tīng)日期變化
filter.addAction(Intent.ACTION_DATE_CHANGED);
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIME_TICK);
mInfoReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
// 屏幕滅屏廣播
case Intent.ACTION_SCREEN_OFF:
//屏幕熄滅改為10秒一存儲(chǔ)
saveDuration = 10000;
break;
//關(guān)機(jī)廣播,保存好當(dāng)前數(shù)據(jù)
case Intent.ACTION_SHUTDOWN:
saveStepData();
break;
// 屏幕解鎖廣播
case Intent.ACTION_USER_PRESENT:
saveDuration = 3000;
break;
// 當(dāng)長(zhǎng)按電源鍵彈出“關(guān)機(jī)”對(duì)話或者鎖屏?xí)r系統(tǒng)會(huì)發(fā)出這個(gè)廣播
// example:有時(shí)候會(huì)用到系統(tǒng)對(duì)話框,權(quán)限可能很高,會(huì)覆蓋在鎖屏界面或者“關(guān)機(jī)”對(duì)話框之上,
// 所以監(jiān)聽(tīng)這個(gè)廣播,當(dāng)收到時(shí)就隱藏自己的對(duì)話,如點(diǎn)擊pad右下角部分彈出的對(duì)話框
case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
saveStepData();
break;
//監(jiān)聽(tīng)日期變化
case Intent.ACTION_DATE_CHANGED:
case Intent.ACTION_TIME_CHANGED:
case Intent.ACTION_TIME_TICK:
saveStepData();
isNewDay();
break;
default:
break;
}
}
};
//注冊(cè)廣播
registerReceiver(mInfoReceiver, filter);
}
/**
* 初始化當(dāng)天數(shù)據(jù)
*/
private void initTodayData() {
//獲取當(dāng)前時(shí)間
CURRENT_DATE = TimeUtil.getCurrentDate();
//獲取數(shù)據(jù)庫(kù)
stepDataDao = new StepDataDao(getApplicationContext());
//獲取當(dāng)天的數(shù)據(jù),用于展示
StepEntity entity = stepDataDao.getCurDataByDate(CURRENT_DATE);
//為空則說(shuō)明還沒(méi)有該天的數(shù)據(jù),有則說(shuō)明已經(jīng)開(kāi)始當(dāng)天的計(jì)步了
if (entity == null) {
CURRENT_STEP = 0;
} else {
CURRENT_STEP = Integer.parseInt(entity.getSteps());
}
}
/**
* 監(jiān)聽(tīng)晚上0點(diǎn)變化初始化數(shù)據(jù)
*/
private void isNewDay() {
String time = "00:00";
if (time.equals(new SimpleDateFormat("HH:mm").format(new Date())) ||
!CURRENT_DATE.equals(TimeUtil.getCurrentDate())) {
initTodayData();
}
}
/**
* 獲取傳感器實(shí)例
*/
private void getStepDetector() {
if (sensorManager != null) {
sensorManager = null;
}
// 獲取傳感器管理器的實(shí)例
sensorManager = (SensorManager) this
.getSystemService(SENSOR_SERVICE);
//android4.4以后可以使用計(jì)步傳感器
int VERSION_CODES = Build.VERSION.SDK_INT;
if (VERSION_CODES >= 19) {
addCountStepListener();
}
}
/**
* 添加傳感器監(jiān)聽(tīng)
*/
private void addCountStepListener() {
Sensor countSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER);
Sensor detectorSensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR);
if (countSensor != null) {
stepSensor = 0;
sensorManager.registerListener(StepService.this, countSensor, SensorManager.SENSOR_DELAY_NORMAL);
} else if (detectorSensor != null) {
stepSensor = 1;
sensorManager.registerListener(StepService.this, detectorSensor, SensorManager.SENSOR_DELAY_NORMAL);
}
}
/**
* 由傳感器記錄當(dāng)前用戶(hù)運(yùn)動(dòng)步數(shù),注意:該傳感器只在4.4及以后才有,并且該傳感器記錄的數(shù)據(jù)是從設(shè)備開(kāi)機(jī)以后不斷累加,
* 只有當(dāng)用戶(hù)關(guān)機(jī)以后,該數(shù)據(jù)才會(huì)清空,所以需要做數(shù)據(jù)保護(hù)
*
* @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_STEP += (thisStepCount - previousStepCount);
previousStepCount = thisStepCount;
}
} else if (stepSensor == 1) {
if (event.values[0] == 1.0) {
CURRENT_STEP++;
}
}
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
/**
* 開(kāi)始倒計(jì)時(shí),去存儲(chǔ)步數(shù)到數(shù)據(jù)庫(kù)中
*/
private void startTimeCount() {
timeCount = new TimeCount(saveDuration, 1000);
timeCount.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é)束,則每隔三秒存儲(chǔ)步數(shù)到數(shù)據(jù)庫(kù)
timeCount.cancel();
saveStepData();
startTimeCount();
}
}
/**
* 保存當(dāng)天的數(shù)據(jù)到數(shù)據(jù)庫(kù)中,并去刷新通知欄
*/
private void saveStepData() {
//查詢(xún)數(shù)據(jù)庫(kù)中的數(shù)據(jù)
StepEntity entity = stepDataDao.getCurDataByDate(CURRENT_DATE);
//為空則說(shuō)明還沒(méi)有該天的數(shù)據(jù),有則說(shuō)明已經(jīng)開(kāi)始當(dāng)天的計(jì)步了
if (entity == null) {
//沒(méi)有則新建一條數(shù)據(jù)
entity = new StepEntity();
entity.setCurDate(CURRENT_DATE);
entity.setSteps(String.valueOf(CURRENT_STEP));
stepDataDao.addNewData(entity);
} else {
//有則更新當(dāng)前的數(shù)據(jù)
entity.setSteps(String.valueOf(CURRENT_STEP));
stepDataDao.updateCurData(entity);
}
builder.setContentIntent(PendingIntent.getActivity(this, 0, nfIntent, 0)) // 設(shè)置PendingIntent
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher)) // 設(shè)置下拉列表中的圖標(biāo)(大圖標(biāo))
.setContentTitle("今日步數(shù)"+CURRENT_STEP+"步") // 設(shè)置下拉列表里的標(biāo)題
.setSmallIcon(R.mipmap.ic_launcher) // 設(shè)置狀態(tài)欄內(nèi)的小圖標(biāo)
.setContentText("加油,要記得勤加運(yùn)動(dòng)"); // 設(shè)置上下文內(nèi)容
// 獲取構(gòu)建好的Notification
Notification stepNotification = builder.build();
//調(diào)用更新
notificationManager.notify(110,stepNotification);
}
@Override
public void onDestroy() {
super.onDestroy();
//主界面中需要手動(dòng)調(diào)用stop方法service才會(huì)結(jié)束
stopForeground(true);
unregisterReceiver(mInfoReceiver);
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
} 其中關(guān)于四大組件之一的Service也有很多要去學(xué)習(xí)的,這幾天也是惡補(bǔ)了一下,算是彌補(bǔ)當(dāng)年在學(xué)校沒(méi)有仔細(xì)學(xué)習(xí)這一塊的遺憾吧 - -
主要要說(shuō)的就是以上了,源碼在這里源碼點(diǎn)我點(diǎn)我
以上所述是小編給大家介紹的Android實(shí)現(xiàn)簡(jiǎn)易計(jì)步器功能隔天步數(shù)清零查看歷史運(yùn)動(dòng)紀(jì)錄,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
Flutter Http網(wǎng)絡(luò)請(qǐng)求實(shí)現(xiàn)詳解
這篇文章主要介紹了Flutter Http網(wǎng)絡(luò)請(qǐng)求實(shí)現(xiàn)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
Android自定義左右或上下滑動(dòng)翻頁(yè)效果
這篇文章主要為大家詳細(xì)介紹了Android自定義左右或上下滑動(dòng)翻頁(yè)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12
實(shí)例詳解Android Selector和Shape的用法
shape和selector是Android UI設(shè)計(jì)中經(jīng)常用到的,比如我們要自定義一個(gè)圓角Button,點(diǎn)擊Button有些效果的變化,就要用到shape和selector,通過(guò)本文結(jié)合代碼實(shí)例給大家詳解Android Selector和Shape的用法,感興趣的朋友一起學(xué)習(xí)吧2016-01-01
Android RadioGroup和RadioButton控件簡(jiǎn)單用法示例
這篇文章主要介紹了Android RadioGroup和RadioButton控件簡(jiǎn)單用法,結(jié)合實(shí)例形式分析了Android單選按鈕控件的基本定義、布局與功能實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-07-07
Android ListView和Adapter數(shù)據(jù)適配器的簡(jiǎn)單介紹
這篇文章主要介紹了Android ListView和Adapter數(shù)據(jù)適配器的簡(jiǎn)單介紹,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
解析離線安裝Eclipse的Android ADT開(kāi)發(fā)插件的具體操作(圖文)
本篇文章是對(duì)離線安裝Eclipse的Android ADT開(kāi)發(fā)插件的具體操作進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
Android WebView如何判斷是否滾動(dòng)到底部
大家好,本篇文章主要講的是Android WebView如何判斷是否滾動(dòng)到底部,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下2022-01-01
為Android應(yīng)用增加渠道信息 自動(dòng)化不同渠道的打包過(guò)程的使用詳解
為了統(tǒng)計(jì)渠道信息,就不得不在程序的某個(gè)地方加入渠道的信息,然后針對(duì)不同的渠道打不同的包。一般可以在Manifest文件中加入渠道編號(hào),而不直接寫(xiě)在代碼中。這樣做的好處是,可以針對(duì)不同渠道,自動(dòng)化去修改Manifest文件中的渠道編號(hào),然后自動(dòng)為該渠道打包2013-05-05
Android編程開(kāi)發(fā)之EditText中不輸入特定字符會(huì)顯示相關(guān)提示信息的方法
這篇文章主要介紹了Android編程開(kāi)發(fā)之EditText中不輸入特定字符會(huì)顯示相關(guān)提示信息的方法,涉及Android針對(duì)EditText的布局操作及內(nèi)容判定相關(guān)技巧,需要的朋友可以參考下2015-12-12

