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

其中主要的代碼都在StepService.class 中了,其中注釋也都非常詳細,我就直接放代碼了:
/**
* Created by fySpring
* Date : 2017/3/24
* To do :
*/
public class StepService extends Service implements SensorEventListener {
public static final String TAG = "StepService";
//當前日期
private static String CURRENT_DATE;
//當前步數
private int CURRENT_STEP;
//3秒進行一次存儲
private static int saveDuration = 3000;
//傳感器
private SensorManager sensorManager;
//數據庫
private StepDataDao stepDataDao;
//計步傳感器類型 0-counter 1-detector
private static int stepSensor = -1;
//廣播接收
private BroadcastReceiver mInfoReceiver;
//自定義簡易計時器
private TimeCount timeCount;
//發送消息,用來和Service之間傳遞步數
private Messenger messenger = new Messenger(new MessengerHandler());
//是否有當天的記錄
private boolean hasRecord;
//未記錄之前的步數
private int hasStepCount;
//下次記錄之前的步數
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) {
/**
* 此處設將Service為前台,不然當APP結束以後很容易被GC給干掉,這也就是大多數音樂播放器會在狀態欄設置一個
* 原理大都是相通的
*/
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//獲取一個Notification構造器
builder = new Notification.Builder(this.getApplicationContext());
/**
* 設置點擊通知欄打開的界面,此處需要注意了,如果你的計步界面不在主界面,則需要判斷app是否已經啟動,
* 再來確定跳轉頁面,這裡面太多坑,(別問我為什麼知道 - -)
* 總之有需要的可以和我交流
*/
nfIntent = new Intent(this, MainActivity.class);
builder.setContentIntent(PendingIntent.getActivity(this, 0, nfIntent, 0)) // 設置PendingIntent
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher)) // 設置下拉列表中的圖標(大圖標)
.setContentTitle("今日步數"+CURRENT_STEP+"步") // 設置下拉列表裡的標題
.setSmallIcon(R.mipmap.ic_launcher) // 設置狀態欄內的小圖標
.setContentText("加油,要記得勤加運動"); // 設置上下文內容
// 獲取構建好的Notification
Notification stepNotification = builder.build();
notificationManager.notify(110,stepNotification);
// 參數一:唯一的通知標識;參數二:通知消息。
startForeground(110, stepNotification);// 開始前台服務
return START_STICKY;
}
/**
* 自定義handler
*/
private class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constant.MSG_FROM_CLIENT:
try {
//這裡負責將當前的步數發送出去,可以在界面或者其他地方獲取,我這裡是在MainActivity中獲取來更新界面
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);
//關機廣播
filter.addAction(Intent.ACTION_SHUTDOWN);
// 屏幕解鎖廣播
filter.addAction(Intent.ACTION_USER_PRESENT);
// 當長按電源鍵彈出“關機”對話或者鎖屏時系統會發出這個廣播
// example:有時候會用到系統對話框,權限可能很高,會覆蓋在鎖屏界面或者“關機”對話框之上,
// 所以監聽這個廣播,當收到時就隱藏自己的對話,如點擊pad右下角部分彈出的對話框
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
//監聽日期變化
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秒一存儲
saveDuration = 10000;
break;
//關機廣播,保存好當前數據
case Intent.ACTION_SHUTDOWN:
saveStepData();
break;
// 屏幕解鎖廣播
case Intent.ACTION_USER_PRESENT:
saveDuration = 3000;
break;
// 當長按電源鍵彈出“關機”對話或者鎖屏時系統會發出這個廣播
// example:有時候會用到系統對話框,權限可能很高,會覆蓋在鎖屏界面或者“關機”對話框之上,
// 所以監聽這個廣播,當收到時就隱藏自己的對話,如點擊pad右下角部分彈出的對話框
case Intent.ACTION_CLOSE_SYSTEM_DIALOGS:
saveStepData();
break;
//監聽日期變化
case Intent.ACTION_DATE_CHANGED:
case Intent.ACTION_TIME_CHANGED:
case Intent.ACTION_TIME_TICK:
saveStepData();
isNewDay();
break;
default:
break;
}
}
};
//注冊廣播
registerReceiver(mInfoReceiver, filter);
}
/**
* 初始化當天數據
*/
private void initTodayData() {
//獲取當前時間
CURRENT_DATE = TimeUtil.getCurrentDate();
//獲取數據庫
stepDataDao = new StepDataDao(getApplicationContext());
//獲取當天的數據,用於展示
StepEntity entity = stepDataDao.getCurDataByDate(CURRENT_DATE);
//為空則說明還沒有該天的數據,有則說明已經開始當天的計步了
if (entity == null) {
CURRENT_STEP = 0;
} else {
CURRENT_STEP = Integer.parseInt(entity.getSteps());
}
}
/**
* 監聽晚上0點變化初始化數據
*/
private void isNewDay() {
String time = "00:00";
if (time.equals(new SimpleDateFormat("HH:mm").format(new Date())) ||
!CURRENT_DATE.equals(TimeUtil.getCurrentDate())) {
initTodayData();
}
}
/**
* 獲取傳感器實例
*/
private void getStepDetector() {
if (sensorManager != null) {
sensorManager = null;
}
// 獲取傳感器管理器的實例
sensorManager = (SensorManager) this
.getSystemService(SENSOR_SERVICE);
//android4.4以後可以使用計步傳感器
int VERSION_CODES = Build.VERSION.SDK_INT;
if (VERSION_CODES >= 19) {
addCountStepListener();
}
}
/**
* 添加傳感器監聽
*/
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);
}
}
/**
* 由傳感器記錄當前用戶運動步數,注意:該傳感器只在4.4及以後才有,並且該傳感器記錄的數據是從設備開機以後不斷累加,
* 只有當用戶關機以後,該數據才會清空,所以需要做數據保護
*
* @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) {
}
/**
* 開始倒計時,去存儲步數到數據庫中
*/
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() {
// 如果計時器正常結束,則每隔三秒存儲步數到數據庫
timeCount.cancel();
saveStepData();
startTimeCount();
}
}
/**
* 保存當天的數據到數據庫中,並去刷新通知欄
*/
private void saveStepData() {
//查詢數據庫中的數據
StepEntity entity = stepDataDao.getCurDataByDate(CURRENT_DATE);
//為空則說明還沒有該天的數據,有則說明已經開始當天的計步了
if (entity == null) {
//沒有則新建一條數據
entity = new StepEntity();
entity.setCurDate(CURRENT_DATE);
entity.setSteps(String.valueOf(CURRENT_STEP));
stepDataDao.addNewData(entity);
} else {
//有則更新當前的數據
entity.setSteps(String.valueOf(CURRENT_STEP));
stepDataDao.updateCurData(entity);
}
builder.setContentIntent(PendingIntent.getActivity(this, 0, nfIntent, 0)) // 設置PendingIntent
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(), R.mipmap.ic_launcher)) // 設置下拉列表中的圖標(大圖標)
.setContentTitle("今日步數"+CURRENT_STEP+"步") // 設置下拉列表裡的標題
.setSmallIcon(R.mipmap.ic_launcher) // 設置狀態欄內的小圖標
.setContentText("加油,要記得勤加運動"); // 設置上下文內容
// 獲取構建好的Notification
Notification stepNotification = builder.build();
//調用更新
notificationManager.notify(110,stepNotification);
}
@Override
public void onDestroy() {
super.onDestroy();
//主界面中需要手動調用stop方法service才會結束
stopForeground(true);
unregisterReceiver(mInfoReceiver);
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
}
其中關於四大組件之一的Service也有很多要去學習的,這幾天也是惡補了一下,算是彌補當年在學校沒有仔細學習這一塊的遺憾吧 - -
主要要說的就是以上了,源碼在這裡源碼點我點我
以上所述是小編給大家介紹的Android實現簡易計步器功能隔天步數清零查看歷史運動紀錄,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對本站網站的支持!
AndroidStudio Git的使用(主要是解決文件沖突)
開始:1.使用github 來測試。首先准備一個GitHub賬號吧2.在androidStudio 裡面新建一個項目 這裡我取名TestGit 然後
Android 如何修改APK的默認名稱
Android 如何修改APK的默認名稱用Android Studio 打包App時生成的名稱默認是 app-release.apk(已簽名) 或 app-debug.a
Android實戰教程--第三十八話《自定義通知NotifiCation》
上一篇小案例,完成了一個普通的通知,點擊通知啟動了一個活動。但是那裡的通知沒有加入些“靓點”,這一篇就給它加入自定義的布局,完成自定義的通知。應用
布局與控件(七)-ListView知多少(上)
第9節 ListView在應用界面當中,經常需要使用列表來展示內容。Android SDK提供了ListView控件,來實現這種效果。ListView需要和Adapter