The walking step ( Đếm bước chân di chuyển)
Các điện thoại thông minh (smartphone) ở phân khúc tầm trung trở lên ngày nay đều có định vị vệ tinh (GPS), lẫn các cảm biến, con quay hồi chuyển, gia tốc kế... nên đo đạc được các vận động cơ thể, và có độ chính xác cao hơn nếu là smartphone cao cấp. Điều kiện cần đã có, điều kiện đủ là các ứng ...
Các điện thoại thông minh (smartphone) ở phân khúc tầm trung trở lên ngày nay đều có định vị vệ tinh (GPS), lẫn các cảm biến, con quay hồi chuyển, gia tốc kế... nên đo đạc được các vận động cơ thể, và có độ chính xác cao hơn nếu là smartphone cao cấp.
Điều kiện cần đã có, điều kiện đủ là các ứng dụng di động (mobile app) sao cho phù hợp theo nhu cầu sử dụng. Có người chỉ cần đo bước chân đi bộ mỗi ngày, xem tiêu tốn bao nhiêu calori, giảm mỡ được không, có người chi tiết hơn, xem mỗi buổi mình đạp xe dài ngắn ra sao, vận động các bài tập đã đủ tiêu mỡ chưa... theo đó, "app" sẽ chỉ rõ các chỉ số này.
Và trong bài viết này mình sẽ hướng dẫn các bạn cách tạo ra demo 1 ứng dụng như vậy !
1.1. Shared Preference
Mình sẽ dùng cách tối ưu Shared Preference cho android như ở bài viết này : Shared Preference
1.2. Config file AndroidManifest.xml
Các permission cần cung cấp cho ứng dụng này :
<uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.WRITE_SETTINGS"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <uses-feature android:name="android.hardware.sensor.accelerometer"/>
Full config
<?xml version="1.0" encoding="utf-8"?> <manifest package="com.tuananh.stepdetectorandcounter" xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.WRITE_SETTINGS"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> <uses-feature android:name="android.hardware.sensor.accelerometer"/> <application android:name=".App" android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".view.activity.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <service android:name=".service.StepService" android:priority="1000"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.DATE_CHANGED"/> <action android:name="android.intent.action.MEDIA_MOUNTED"/> <action android:name="android.intent.action.USER_PRESENT"/> <action android:name="android.intent.action.ACTION_TIME_TICK"/> <action android:name="android.intent.action.ACTION_POWER_CONNECTED"/> <action android:name="android.intent.action.ACTION_POWER_DISCONNECTED"/> </intent-filter> </service> </application> </manifest>
1.3. Sensor
<uses-feature android:name="android.hardware.sensor.accelerometer"/>
2.1. Callback update UI
Tạo interface UpdateUiCallBack với function void updateUi(int stepCount);
package com.tuananh.stepdetectorandcounter.step; /** * Created by FRAMGIAvu.tuan.anh on 21/08/2017. */ public interface UpdateUiCallBack { void updateUi(int stepCount); }
2.2. Constant
Key DATE_FORMAT để làm key lưu vào shared preference:
package com.tuananh.stepdetectorandcounter.model; /** * Created by FRAMGIAvu.tuan.anh on 21/08/2017. */ public class Constant { public static final String DATE_FORMAT = "yyyy_MM_dd"; }
2.3. CommonUtils
- Function getKeyToday() lấy key của ngày hôm nay
- Function getStepNumber() lấy ra số bước chân đã đi của ngày hôm nay
package com.tuananh.stepdetectorandcounter.utils; import com.tuananh.stepdetectorandcounter.model.Constant; import java.text.SimpleDateFormat; import java.util.Calendar; /** * Created by FRAMGIAvu.tuan.anh on 21/08/2017. */ public class CommonUtils { public static String getKeyToday() { Calendar calendar = Calendar.getInstance(); SimpleDateFormat simpleDateFormat = new SimpleDateFormat(Constant.DATE_FORMAT); return simpleDateFormat.format(calendar.getTime()); } public static int getStepNumber() { return SharedPreferencesUtils.getInstance().get(getKeyToday(), Integer.class); } }
3.1. Khởi tạo notification : initNotification
private void initNotification() { mBuilder = new NotificationCompat.Builder(this); mBuilder.setContentTitle(getResources().getString(R.string.app_name)) .setContentText("The number of steps today: " + mCurrentStep + " step") .setContentIntent(getDefaultIntent(Notification.FLAG_ONGOING_EVENT)) .setWhen(System.currentTimeMillis()) .setPriority(Notification.PRIORITY_DEFAULT) .setAutoCancel(false) .setOngoing(true) .setSmallIcon(R.mipmap.ic_launcher); Notification notification = mBuilder.build(); mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); startForeground(mNotifyIdStep, notification); }
3.2. Update notification : updateNotification()
private void updateNotification() { Intent hangIntent = new Intent(this, MainActivity.class); PendingIntent hangPendingIntent = PendingIntent.getActivity(this, 0, hangIntent, PendingIntent.FLAG_CANCEL_CURRENT); Notification notification = mBuilder.setContentTitle(getResources().getString(R.string.app_name)) .setContentText("The number of steps today: " + mCurrentStep + " step") .setWhen(System.currentTimeMillis()) .setContentIntent(hangPendingIntent) .build(); mNotificationManager.notify(mNotifyIdStep, notification); if (mCallback != null) { mCallback.updateUi(mCurrentStep); } }
3.3. Các action cần xử lý
private void initBroadcastReceiver() { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SHUTDOWN); filter.addAction(Intent.ACTION_SCREEN_ON); 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); mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); switch (action) { case Intent.ACTION_SCREEN_ON: Log.i(TAG, "screen_on"); break; case Intent.ACTION_SCREEN_OFF: Log.i(TAG, "screen_off"); break; case Intent.ACTION_USER_PRESENT: Log.i(TAG, "screen unlock"); break; case Intent.ACTION_CLOSE_SYSTEM_DIALOGS: Log.i(TAG, "receive ACTION_CLOSE_SYSTEM_DIALOGS"); saveData(); break; case Intent.ACTION_SHUTDOWN: Log.i(TAG, "receive ACTION_SHUTDOWN"); saveData(); break; case Intent.ACTION_DATE_CHANGED: Log.i(TAG, "receive ACTION_DATE_CHANGED"); saveData(); break; case Intent.ACTION_TIME_CHANGED: Log.i(TAG, "receive ACTION_TIME_CHANGED"); saveData(); break; case Intent.ACTION_TIME_TICK: Log.i(TAG, "receive ACTION_TIME_TICK"); saveData(); break; } } }; registerReceiver(mBroadcastReceiver, filter); }
3.4. Khởi tạo số bước của ngày hiện tại
private void initTodayData() { mCurrentStep = CommonUtils.getStepNumber(); updateNotification(); }
3.5. Check API hỗ trợ
private void startStepDetector() { if (mSensorManager != null) { mSensorManager = null; } mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); int VERSION_CODES = Build.VERSION.SDK_INT; if (VERSION_CODES >= 19) { addCountStepListener(); } else { addBasePedometerListener(); } }
- Đăng ký listener cho SensorManager:
private void addCountStepListener() { Sensor countSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER); Sensor detectorSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR); if (countSensor != null) { mStepSensorType = Sensor.TYPE_STEP_COUNTER; Log.v(TAG, "Sensor.TYPE_STEP_COUNTER"); mSensorManager .registerListener(StepService.this, countSensor, SensorManager.SENSOR_DELAY_NORMAL); } else if (detectorSensor != null) { mStepSensorType = Sensor.TYPE_STEP_DETECTOR; Log.v(TAG, "Sensor.TYPE_STEP_DETECTOR"); mSensorManager.registerListener(StepService.this, detectorSensor, SensorManager.SENSOR_DELAY_NORMAL); } else { Log.v(TAG, "Count sensor not available!"); addBasePedometerListener(); } }
3.6. SensorEventListener
public class StepService extends Service implements SensorEventListener {}
- Nếu là lần đầu tiên thì cho mHasStepCount = tempStep với : int tempStep = (int) sensorEvent.values[0];
if (!mHasRecord) { mHasRecord = true; mHasStepCount = tempStep; }
- Full code
@Override public void onSensorChanged(SensorEvent sensorEvent) { switch (mStepSensorType) { case Sensor.TYPE_STEP_COUNTER: int tempStep = (int) sensorEvent.values[0]; Log.d(TAG, "tempStep = " + tempStep); if (!mHasRecord) { mHasRecord = true; mHasStepCount = tempStep; } else { int thisStepCount = tempStep - mHasStepCount; int thisStep = thisStepCount - mPreviousStepCount; mCurrentStep += thisStep; mPreviousStepCount = thisStepCount; } break; case Sensor.TYPE_STEP_DETECTOR: if (sensorEvent.values[0] == 1.0) { mCurrentStep++; } break; } updateNotification(); } @Override public void onAccuracyChanged(Sensor sensor, int i) { }
3.7. Full code
package com.tuananh.stepdetectorandcounter.service; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Binder; import android.os.Build; import android.os.IBinder; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.util.Log; import com.tuananh.stepdetectorandcounter.R; import com.tuananh.stepdetectorandcounter.step.UpdateUiCallBack; import com.tuananh.stepdetectorandcounter.utils.CommonUtils; import com.tuananh.stepdetectorandcounter.utils.SharedPreferencesUtils; import com.tuananh.stepdetectorandcounter.view.activity.MainActivity; /** * Created by FRAMGIAvu.tuan.anh on 21/08/2017. */ public class StepService extends Service implements SensorEventListener { private static final String TAG = "TAG: " + StepService.class.getSimpleName(); private static int mStepSensorType = -1; private UpdateUiCallBack mCallback; private NotificationManager mNotificationManager; private NotificationCompat.Builder mBuilder; private BroadcastReceiver mBroadcastReceiver; private StepBinder mStepBinder = new StepBinder(); private SensorManager mSensorManager; private int mCurrentStep; private int mNotifyIdStep = 100; private int mHasStepCount = 0; private int mPreviousStepCount = 0; private boolean mHasRecord; @Override public void onCreate() { super.onCreate(); initNotification(); initTodayData(); initBroadcastReceiver(); new Thread(new Runnable() { public void run() { startStepDetector(); } }).start(); } @Nullable @Override public IBinder onBind(Intent intent) { return mStepBinder; } public void registerCallback(UpdateUiCallBack paramICallback) { mCallback = paramICallback; } private void startStepDetector() { if (mSensorManager != null) { mSensorManager = null; } mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); int VERSION_CODES = Build.VERSION.SDK_INT; if (VERSION_CODES >= 19) { addCountStepListener(); } else { addBasePedometerListener(); } } private void addCountStepListener() { Sensor countSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER); Sensor detectorSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR); if (countSensor != null) { mStepSensorType = Sensor.TYPE_STEP_COUNTER; Log.v(TAG, "Sensor.TYPE_STEP_COUNTER"); mSensorManager .registerListener(StepService.this, countSensor, SensorManager.SENSOR_DELAY_NORMAL); } else if (detectorSensor != null) { mStepSensorType = Sensor.TYPE_STEP_DETECTOR; Log.v(TAG, "Sensor.TYPE_STEP_DETECTOR"); mSensorManager.registerListener(StepService.this, detectorSensor, SensorManager.SENSOR_DELAY_NORMAL); } else { Log.v(TAG, "Count sensor not available!"); addBasePedometerListener(); } } private void addBasePedometerListener() { // TODO: 23/08/2017 } public int getStepCount() { return mCurrentStep; } public PendingIntent getDefaultIntent(int flags) { return PendingIntent.getActivity(this, 1, new Intent(), flags); } private void initTodayData() { mCurrentStep = CommonUtils.getStepNumber(); updateNotification(); } private void initBroadcastReceiver() { IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SHUTDOWN); filter.addAction(Intent.ACTION_SCREEN_ON); 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); mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); switch (action) { case Intent.ACTION_SCREEN_ON: Log.i(TAG, "screen_on"); break; case Intent.ACTION_SCREEN_OFF: Log.i(TAG, "screen_off"); break; case Intent.ACTION_USER_PRESENT: Log.i(TAG, "screen unlock"); break; case Intent.ACTION_CLOSE_SYSTEM_DIALOGS: Log.i(TAG, "receive ACTION_CLOSE_SYSTEM_DIALOGS"); saveData(); break; case Intent.ACTION_SHUTDOWN: Log.i(TAG, "receive ACTION_SHUTDOWN"); saveData(); break; case Intent.ACTION_DATE_CHANGED: Log.i(TAG, "receive ACTION_DATE_CHANGED"); saveData(); break; case Intent.ACTION_TIME_CHANGED: Log.i(TAG, "receive ACTION_TIME_CHANGED"); saveData(); break; case Intent.ACTION_TIME_TICK: Log.i(TAG, "receive ACTION_TIME_TICK"); saveData(); break; } } }; registerReceiver(mBroadcastReceiver, filter); } private void initNotification() { mBuilder = new NotificationCompat.Builder(this); mBuilder.setContentTitle(getResources().getString(R.string.app_name)) .setContentText("The number of steps today: " + mCurrentStep + " step") .setContentIntent(getDefaultIntent(Notification.FLAG_ONGOING_EVENT)) .setWhen(System.currentTimeMillis()) .setPriority(Notification.PRIORITY_DEFAULT) .setAutoCancel(false) .setOngoing(true) .setSmallIcon(R.mipmap.ic_launcher); Notification notification = mBuilder.build(); mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); startForeground(mNotifyIdStep, notification); } private void updateNotification() { Intent hangIntent = new Intent(this, MainActivity.class); PendingIntent hangPendingIntent = PendingIntent.getActivity(this, 0, hangIntent, PendingIntent.FLAG_CANCEL_CURRENT); Notification notification = mBuilder.setContentTitle(getResources().getString(R.string.app_name)) .setContentText("The number of steps today: " + mCurrentStep + " step") .setWhen(System.currentTimeMillis()) .setContentIntent(hangPendingIntent) .build(); mNotificationManager.notify(mNotifyIdStep, notification); if (mCallback != null) { mCallback.updateUi(mCurrentStep); } } @Override public void onSensorChanged(SensorEvent sensorEvent) { switch (mStepSensorType) { case Sensor.TYPE_STEP_COUNTER: int tempStep = (int) sensorEvent.values[0]; Log.d(TAG, "tempStep = " + tempStep); if (!mHasRecord) { mHasRecord = true; mHasStepCount = tempStep; } else { int thisStepCount = tempStep - mHasStepCount; int thisStep = thisStepCount - mPreviousStepCount; mCurrentStep += thisStep; mPreviousStepCount = thisStepCount; } break; case Sensor.TYPE_STEP_DETECTOR: if (sensorEvent.values[0] == 1.0) { mCurrentStep++; } break; } updateNotification(); } @Override public void onAccuracyChanged(Sensor sensor, int i) { } public void saveData() { SharedPreferencesUtils.getInstance().put(CommonUtils.getKeyToday(), mCurrentStep); } public class StepBinder extends Binder { public StepService getService() { return StepService.this; } } }
4.1 Layout activity_main
<?xml version="1.0" encoding="utf-8"?> <layout> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_awidth="match_parent" android:layout_height="match_parent" tools:context="com.tuananh.stepdetectorandcounter.view.activity.MainActivity"> <TextView android:id="@+id/text_step" android:layout_awidth="match_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:gravity="center_horizontal" android:text="0" android:textColor="@android:color/holo_red_dark" android:textSize="50sp"/> </LinearLayout> </layout>
4.2 MainActivity
- stepService.registerCallback đăng kí call back trả về số bước chân
- Function updateUi(int stepCount) trả về số bước chân đã đi từ stepService
stepService.registerCallback(new UpdateUiCallBack() { @Override public void updateUi(int stepCount) { showStepCount(CommonUtils.getStepNumber(), stepCount); } });
- Khởi tạo service connection và trong onServiceConnected sẽ cập nhật số bước chân
private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder service) { StepService stepService = ((StepService.StepBinder) service).getService(); showStepCount(CommonUtils.getStepNumber(), stepService.getStepCount()); stepService.registerCallback(new UpdateUiCallBack() { @Override public void updateUi(int stepCount) { showStepCount(CommonUtils.getStepNumber(), stepCount); } }); } @Override public void onServiceDisconnected(ComponentName componentName) { } };
- Hiển thị số bước chân đã đi lên màn hình showStepCount(int totalStepNum, int currentCounts)
public void showStepCount(int totalStepNum, int currentCounts) { if (currentCounts < totalStepNum) { currentCounts = totalStepNum; } mBinding.textStep.setText(String.valueOf(currentCounts)); }
- Khởi tạo service đếm bước chân
private void setupService() { Intent intent = new Intent(this, StepService.class); mIsBind = bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); startService(intent); }
- Hủy service
@Override protected void onDestroy() { super.onDestroy(); if (mIsBind) { unbindService(mServiceConnection); } }
- Khởi tạo data
private void initData() { showStepCount(CommonUtils.getStepNumber(), 0); setupService(); }
- Full code
package com.tuananh.stepdetectorandcounter.view.activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.databinding.DataBindingUtil; import android.os.Bundle; import android.os.IBinder; import android.support.v7.app.AppCompatActivity; import com.tuananh.stepdetectorandcounter.R; import com.tuananh.stepdetectorandcounter.databinding.ActivityMainBinding; import com.tuananh.stepdetectorandcounter.service.StepService; import com.tuananh.stepdetectorandcounter.step.UpdateUiCallBack; import com.tuananh.stepdetectorandcounter.utils.CommonUtils; public class MainActivity extends AppCompatActivity { private boolean mIsBind; private ActivityMainBinding mBinding; private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder service) { StepService stepService = ((StepService.StepBinder) service).getService(); showStepCount(CommonUtils.getStepNumber(), stepService.getStepCount()); stepService.registerCallback(new UpdateUiCallBack() { @Override public void updateUi(int stepCount) { showStepCount(CommonUtils.getStepNumber(), stepCount); } }); } @Override public void onServiceDisconnected(ComponentName componentName) { } }; public void showStepCount(int totalStepNum, int currentCounts) { if (currentCounts < totalStepNum) { currentCounts = totalStepNum; } mBinding.textStep.setText(String.valueOf(currentCounts)); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); initData(); } private void initData() { showStepCount(CommonUtils.getStepNumber(), 0); setupService(); } private void setupService() { Intent intent = new Intent(this, StepService.class); mIsBind = bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE); startService(intent); } @Override protected void onDestroy() { super.onDestroy(); if (mIsBind) { unbindService(mServiceConnection); } } }
Resource