安卓计步器

匿名 (未验证) 提交于 2019-12-03 00:28:02

针对计步器的基本需求功能如下:
1.通过传感器得到步数
2.通过数据库储存步数
3.从数据库调出相应数据显示步数

分析ios系统自带健康软件中的计步器,数据显示为:
1.步行+跑步距离
2.步数
3.已爬楼层
其中每项显示均包含日、周、月、年的数据柱形图显示和对应时长的平均值显示,并能显示所有数据的具体时间和数值。

当传感器的值发生变化时,会调用onSensorChanged方法。当传感器的精度变化时会调用onAccuracyChanged方法。onSensorChanged方法里根据stepSensor(计步传感器类型 0-counter 1-detector)的类型设置StepDcretor.CURRENT_SETP的值,然后调用updateNotification方法更新通知栏。在onCreate方法里看到,首先获取了当前的日期,然后初始化一个广播接收者,接着开启子线程执行startStepDetector方法。
先看addCountStepListener方法,API版本大于19执行这个方法,首先先拿到两种传感器,TYPE_STEP_COUNTER 计步传感器,用于记录激活后的步伐数。TYPE_STEP_DETECTOR 步行检测传感器,用户每走一步就触发一次事件。根据拿到的情况来注册监听,sensorManager.registerListener(StepService.this, countSensor, SensorManager.SENSOR_DELAY_UI);查资料后得知这里第一个参数是Listener,第二个参数是所得传感器类型,第三个参数值获取传感器信息的频率 。如果两种传感器都没拿到,就还是执行addBasePedoListener方法。在addBasePedoListener方法里获得加速度传感器,注册监听,然后调stepDetector里的回调接口更新通知栏。
总结一下就是开启一个服务,在服务里注册一个广播接收者监听屏幕和电源情况保存数据,并且新建子线程,在子线程里开启计步检测,根据不同的API版本获取传感器管理器和传感器实例并注册监听,然后更具传感器返回的数据,然后更新界面。

代码主要包括:
1.数据库的实现
2.时间戳工具
3.获取步数信息
5.计步器服务器

计步器原理
实现计步器几步一般有2种方法:
1.利用加速传感器,通过计算二次波峰来确定每一步,这种方法误差比较大,但普适性好;
2.利用Android4.4之后的Sensor.TYPE_STEP_DETECTOR来计步,缺点是这个传感器只有4.4以后的版本才能使用而且手机要有三轴陀螺仪,限制比较大,鉴于微信运动也是使用的第二种方法,这里也放弃第一种方法的使用。(备注:可以找个方法一的算法对接到方法二上,因为Android自带的计步器太简陋了,一切都需要
自己实现,这个下文有说明)

关于TYPE_STEP_DETECTOR
通过TYPE_STEP_DETECTOR传感器我们只能获得2个数据:
1.时间戳(这个在开关机面前就废了,不做考虑);
2.从上次开机到现在的总步数(总步数会在开关机时清零),对,只是总步数……这样就不能确定某一天的步数了。

说明
关于跨天会想到Date_Changed这个系统广播,然而该广播不可信任,原因:该广播只会发出一次,
如:用户往后调整过时间,并在00:00(记此时为t)发送过该天的广播,那么当用户再次将时间拨回到正常时间时,再次经过t时刻,
系统不会发送Date_Changed广播;另外,开机广播也不可信任,原因:部分手机厂商会屏蔽掉该广播。

实现步骤
1.计步器步数数据库的实现
关于数据库这里没有使用第三方框架,已接入第三方框架的,请自行修改。数据库结构如下:

date:时间戳,表明是哪一天的数据
steps:修正步数,非真实步数,下面会解释(已DES加密)
sensor:修正传感器步数(该值非负,只有off为1或插入新数据时才为0),和上一天的sensor及off的状态有关(已des加密),之所以用修正结果,是为了监听异常关机
off:手机是否正常关机,1表示正常关机,0表示未关机,请无视这么命名,这得改啊

数据库操作代码:
/**
* 这里为了方便查看数据创建了2个表格一个DES加密,一个未加密,根据需要 自行修改
*/
public class Database extends SQLiteOpenHelper {

private final static String DB_NAME = "steps"; private final static String DB_NAME_ENCRYPT = "steps_encrypt"; private final static int DB_VERSION = 1;  private static Database instance; private static final AtomicInteger openCounter = new AtomicInteger();  private Database(final Context context) {     super(context, DB_NAME, null, DB_VERSION); }  public static synchronized Database getInstance(final Context c) {     if (instance == null) {         instance = new Database(c.getApplicationContext());     }     openCounter.incrementAndGet();     return instance; }  @Override public void close() {     if (openCounter.decrementAndGet() == 0) {         super.close();     } }  /**  * date : 计步的日期  * steps : 开关机后存储的步数  * sensor : 虚拟传感器的步数(非真实值,和上一天是否关机有关),计步器传感器每次变化都会触发  * off : 是否关机过,默认false(0);关机时置为true(1),待计步服务开启后将当天的off置为false(0) ;  *  * @param db  */ @Override public void onCreate(final SQLiteDatabase db) {     db.execSQL("CREATE TABLE " + DB_NAME + " (date INTEGER, steps INTEGER , sensor INTEGER , off INTEGER)");     db.execSQL("CREATE TABLE " + DB_NAME_ENCRYPT + " (date INTEGER, steps TEXT , sensor TEXT , off INTEGER)"); }  @Override public void onUpgrade(final SQLiteDatabase db, int oldVersion, int newVersion) {     // 关于升级数据库,暂时不处理 }  /**  * Query the 'steps' table. Remember to close the cursor!  *  * @param columns       the colums  * @param selection     the selection  * @param selectionArgs the selction arguments  * @param groupBy       the group by statement  * @param having        the having statement  * @param orderBy       the order by statement  * @return the cursor  */ public Cursor query(final String[] columns, final String selection,                     final String[] selectionArgs, final String groupBy, final String having,                     final String orderBy, final String limit) {     return getReadableDatabase()             .query(DB_NAME, columns, selection, selectionArgs, groupBy, having, orderBy, limit); }  /**  * 插入新的数据  *  * @param date  * @param steps  * @param correctSensorSteps 需要根据上一天的开关机判断  * @param off  */ public void insertNewDay(long date, int steps, int correctSensorSteps, int off) {     getWritableDatabase().beginTransaction();     try {         ContentValues values = new ContentValues();         values.put("date", date);         values.put("steps", steps);         values.put("sensor", correctSensorSteps);         values.put("off", off);         getWritableDatabase().insert(DB_NAME, null, values);         getWritableDatabase().setTransactionSuccessful();     } finally {         getWritableDatabase().endTransaction();     }     insertNewDayEncrypt(date , steps , correctSensorSteps , off); } public void insertNewDayEncrypt(long date, int steps, int correctSensorSteps, int off) {     getWritableDatabase().beginTransaction();     try {         ContentValues values = new ContentValues();         values.put("date", date);         values.put("steps", DesTool.encrypt(steps + "" ));         values.put("sensor", DesTool.encrypt(correctSensorSteps + "" ));         values.put("off", off);         getWritableDatabase().insert(DB_NAME_ENCRYPT , null, values);         getWritableDatabase().setTransactionSuccessful();     } finally {         getWritableDatabase().endTransaction();     } }  /**  * 更新steps(关机、插入新数据、异常关机、数据异常时才会调用)  *  * @param date  * @param correctSensorSteps 应该传的传感器数值,不一定为读取到的传感器数值  */ public void updateSteps(long date, int correctSensorSteps) {     Cursor c = getReadableDatabase().query(DB_NAME, new String[]{"date"}, "date = ?",             new String[]{String.valueOf(date)}, null, null, null);     c.moveToFirst();     if (c.getCount() == 0) {         // 没有记录,说明之前传感器没有动,所以一概为0 (sensor在计步传感器发生改变时会改变,         // 此时为0,是为了区别计步器是否监听到了运动,为0显然传感器没有改变);off传1是因为:         // 当天的会在启动监听计步的服务时置为0,如果前一天没数据置为1视为关机状态,便于逻辑的实现         insertNewDay(date, 0, 0, 1);     } else {         getWritableDatabase().execSQL(                 "UPDATE " + DB_NAME + " SET steps = steps + " + correctSensorSteps + " WHERE date = " + date);     }     c.close();     updateStepsEncrypt(date , correctSensorSteps); } public void updateStepsEncrypt(long date, int correctSensorSteps) {     Cursor c = getReadableDatabase().query(DB_NAME_ENCRYPT , new String[]{"date"}, "date = ?",             new String[]{String.valueOf(date)}, null, null, null);     c.moveToFirst();     if (c.getCount() == 0) {         // 同上         insertNewDayEncrypt(date, 0, 0, 1);     } else {         int steps = 0 ;         try{             steps = Integer.parseInt(DesTool.decrypt(getStepsEncrypt(date))) ;         }catch (Exception e){          }         getWritableDatabase().execSQL(                 "UPDATE " + DB_NAME_ENCRYPT + " SET steps = \"" + DesTool.encrypt((steps + correctSensorSteps) + "") + "\" WHERE date = " + date);     }     c.close(); }  /**  * @param date the date in millis since 1970  * @return steps的值  */ public int getSteps(long date) {     Cursor c = getReadableDatabase().query(DB_NAME, new String[]{"steps"}, "date = ?",             new String[]{String.valueOf(date)}, null, null, null);     c.moveToFirst();     int re = 0;     if (c.getCount() == 0) {     } else {         re = c.getInt(0);     }     return re; } public String getStepsEncrypt(long date) {     Cursor c = getReadableDatabase().query(DB_NAME_ENCRYPT , new String[]{"steps"}, "date = ?",             new String[]{String.valueOf(date)}, null, null, null);     c.moveToFirst();     String re = null ;     if (c.getCount() == 0) {         re = "" ;     } else {         re = c.getString(0);         if(null == re){             re = "" ;         }     }     c.close();     return re; }  /**  * 更新某一天(date)的传感器步数(sensor)  *  * @param date               所属的时间,in millis since 1970  * @param correctSensorSteps  */ public void updateSensorSteps(long date, int correctSensorSteps) {     Cursor c = getReadableDatabase().query(DB_NAME, new String[]{"date"}, "date = ?",             new String[]{String.valueOf(date)}, null, null, null);     c.moveToFirst();     if (c.getCount() == 0) {         // 没有记录,这一行执行不到,因为更新sensor的操作总在其他getXXX之后执行         insertNewDay(date, 0, correctSensorSteps, 0);     } else {         getWritableDatabase().execSQL(                 "UPDATE " + DB_NAME + " SET sensor = " + correctSensorSteps + " WHERE date = " + date);     }     c.close();     updateSensorStepsEncrypt(date , correctSensorSteps); } public void updateSensorStepsEncrypt(long date, int correctSensorSteps) {     Cursor c = getReadableDatabase().query(DB_NAME_ENCRYPT, new String[]{"date"}, "date = ?",             new String[]{String.valueOf(date)}, null, null, null);     c.moveToFirst();     if (c.getCount() == 0) {         // 没有记录         insertNewDayEncrypt(date, 0, correctSensorSteps, 0);     } else {         getWritableDatabase().execSQL(                 "UPDATE " + DB_NAME_ENCRYPT + " SET sensor = \"" + DesTool.encrypt(correctSensorSteps + "") + "\" WHERE date = " + date);     }     c.close(); }  /**  * @param date the date in millis since 1970  * @return 存储的传感器的步数  */ public int getSensorSteps(long date) {     Cursor c = getReadableDatabase().query(DB_NAME, new String[]{"sensor"}, "date = ?",             new String[]{String.valueOf(date)}, null, null, null);     c.moveToFirst();     int re;     if (c.getCount() == 0) {         re = 0;     } else {         re = c.getInt(0);     }     c.close();     return re; } public String getSensorStepsEncrypt(long date) {     Cursor c = getReadableDatabase().query(DB_NAME_ENCRYPT, new String[]{"sensor"}, "date = ?",             new String[]{String.valueOf(date)}, null, null, null);     c.moveToFirst();     String re = null ;     if (c.getCount() == 0) {         re = "" ;     } else {         re = c.getString(0);         if(null == re){             re = "" ;         }     }     c.close();     return re; }  /**  * 更新off的状态  *  * @param date  * @param offStatus  */ public void updateOffStatus(long date, int offStatus) {     Cursor c = getReadableDatabase().query(DB_NAME, new String[]{"date"}, "date = ?",             new String[]{String.valueOf(date)}, null, null, null);     c.moveToFirst();     if (c.getCount() == 0) {         // 同上         insertNewDay(date, 0 , 0 , offStatus);     }else {         getWritableDatabase().execSQL(                 "UPDATE " + DB_NAME + " SET off = " + offStatus + " WHERE date = " + date);     }     c.close();     updateOffStatusEncrypt(date , offStatus) ; } public void updateOffStatusEncrypt(long date, int offStatus) {     Cursor c = getReadableDatabase().query(DB_NAME_ENCRYPT , new String[]{"date"}, "date = ?",             new String[]{String.valueOf(date)}, null, null, null);     c.moveToFirst();     if (c.getCount() == 0) {         // 没有记录,同上         insertNewDay(date, 0 , 0 , offStatus);     }else {         getWritableDatabase().execSQL(                 "UPDATE " + DB_NAME_ENCRYPT + " SET off = " + offStatus + " WHERE date = " + date);     }     c.close(); }  /**  * 得到off的值 0 = false ; 1 = true ;  *  * @param date  * @return  */ public int getOffStatus(long date) {     Cursor c = getReadableDatabase().query(DB_NAME, new String[]{"off"}, "date = ?",             new String[]{String.valueOf(date)}, null, null, null);     c.moveToFirst();     int hasOffed = 0;     if (c.getCount() == 0) {     } else {         hasOffed = c.getInt(0);     }     c.close();     return hasOffed; } public int getOffStatusEncypt(long date) {     Cursor c = getReadableDatabase().query(DB_NAME_ENCRYPT, new String[]{"off"}, "date = ?",             new String[]{String.valueOf(date)}, null, null, null);     c.moveToFirst();     int hasOffed = 0;     if (c.getCount() == 0) {     } else {         hasOffed = c.getInt(0);     }     c.close();     return hasOffed; } /**  * 用于关机时修改整个数据库(所以数据库中内容不宜过多,要定期清理,清理方法暂时略过)  */ public void updateAll() {     getWritableDatabase().execSQL(             "UPDATE " + DB_NAME + " SET off = 1,steps = steps + sensor,sensor = 0");     updateAllEncrypt() ; }  public void updateAllEncrypt() {     Cursor c = getReadableDatabase().query(DB_NAME_ENCRYPT , new String[]{"date"}, "date > ?",             new String[]{String.valueOf(1)}, null, null, null);     if (c.getCount() == 0) {         // 没有记录,说明之前传感器没有动,不进行任何操作     } else {         while (c.moveToNext()){             long date = c.getLong(0) ;             int steps = getSteps(date) ;             int sensor = getSensorSteps(date) ;             getWritableDatabase().execSQL(                     "UPDATE " + DB_NAME_ENCRYPT + " SET off = 1,steps = \"" + DesTool.encrypt((steps + sensor) + "")                             + "\",sensor = \"" + DesTool.encrypt("0") + "\" WHERE date = " + date);         }     }     c.close(); } 

}

2.时间戳工具
public abstract class Util4Pedometer {

/**  * @return milliseconds since 1.1.1970 for today 0:00:00  */ public static long getToday() {     Calendar c = Calendar.getInstance();     c.setTimeInMillis(System.currentTimeMillis());     c.set(Calendar.HOUR_OF_DAY, 0);     c.set(Calendar.MINUTE, 0);     c.set(Calendar.SECOND, 0);     c.set(Calendar.MILLISECOND, 0);     return c.getTimeInMillis(); } 

}

3.获取当天的步数
以往的步数建议服务器获取。

获取当天步数代码:

/**
* 使用该方法前必须对context进行初始化
*/
public class StepsCountTool {

public static Context context;  /**  * 获得当天的步数,若步数异常重置步数  * 这里返回String是因为方便往服务器提交数据时加密(如:RSA)  */ public static String getTodaySteps() {     if (null == context) {         return "0";     }     int result = 0;     Database db = Database.getInstance(context);     long today = Util4Pedometer.getToday();     // 当天步数为当天的steps数据与sensor数据之和     result = db.getSteps(today) + db.getSensorSteps(today);     if (0 > result) {         // 说明数据异常(只有在跨天且计步器逻辑没有被触发才有可能异常,此处触发几率不大,保险起见,         // 看计步器服务调用该方法的位置可理解),应该重置当前的步数         db.updateSteps(today, -db.getSteps(today));         db.updateSensorSteps(today, 0);         result = 0;     }     return result + ""; } 

}

4.计步器服务的实现
计步器要一直在后台运行就使用到了Service,如果不能一直运行则某些情况会使计数偏小,关于如何保活,这是个问题,这里只尽可能的使服务唤醒
(请自行解决,最后会给出源码,仅供参考)。

计步服务代码:

@TargetApi(Build.VERSION_CODES.KITKAT)
public class SensorListener extends Service implements SensorEventListener {

public static boolean isShutdowning = false;// 只有接收到关机广播才会置为true private Database db; public static int steps = 0; private final static int MICROSECONDS_IN_ONE_MINUTE = 60000000;  @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { }  @Override public void onSensorChanged(final SensorEvent event) {     // 关机时不往下执行     if (isShutdowning) {         return;     }     // 若步数异常(用户随意调整时间后,开关机等等),修复步数     StepsCountTool.getTodaySteps();     Sensor sensor = event.sensor;     if (sensor.getType() != Sensor.TYPE_STEP_COUNTER) {         return;     }     steps = (int) event.values[0];     db = Database.getInstance(this);     long today = Util4Pedometer.getToday();     long yesterDay = today - 24 * 60 * 60 * 1000;     int correctSensorSteps;     int off = db.getOffStatus(yesterDay);     if (off == 0) {         // 说明昨天没有关机,得到修正值:当前计步器数据 - 上一天sensor修正值,手机异常关机,会使steps的值从0开始         // 正常情况correctSensorSteps 为正         correctSensorSteps = steps - db.getSensorSteps(yesterDay);         if (correctSensorSteps < 0) {             // 说明手机异常关机:扣电池或使用脚本关机导致关机广播没有发出,正常关机时会将所有off置为1,             db.updateOffStatus(yesterDay, 1);             // 异常关机后重启先保存关机前的数据             db.updateSteps(today, db.getSensorSteps(today));             // 再重新计算步数,这样能最大可能的接近真实值,此时steps从1开始             db.updateSensorSteps(today, steps);         } else {             // 如果没有关机,跨天时一定会执行if中的代码,因为insertNewDay的sensor和steps的参数都是0,             // 在第一次监听到计步器改变时,senor的值还未改变             if (db.getSensorSteps(today) <= 0) {                 // 这里每天只更新一次sensor,更新后和sensor的和为0,跨天问题解决                 // steps此时为0,将sensor偏移量取反放入steps中,                 db.updateSteps(today, -correctSensorSteps);             }             db.updateSensorSteps(today, correctSensorSteps);         }     } else if (off == 1) {         // 说明昨天关机,correctSensorSteps 就是传感器的真实值         correctSensorSteps = steps;         db.updateSensorSteps(today, correctSensorSteps);     } else {         // 计步器异常     } }  @Override public IBinder onBind(final Intent intent) {     return null; }  @Override public int onStartCommand(final Intent intent, int flags, int startId) {     if (isShutdowning) {         return START_STICKY;     }     db = Database.getInstance(this);     // 每次启动服务都将今天的状态置为0     db.updateOffStatus(Util4Pedometer.getToday(), 0);     // restart service every hour to get the current step count     ((AlarmManager) getApplicationContext().getSystemService(Context.ALARM_SERVICE))             .set(AlarmManager.RTC, System.currentTimeMillis() + AlarmManager.INTERVAL_HOUR,                     PendingIntent.getService(getApplicationContext(), 2,                             new Intent(this, SensorListener.class),                             PendingIntent.FLAG_UPDATE_CURRENT));     return START_STICKY; }  @Override public void onCreate() {     super.onCreate();     reRegisterSensor(); }  @Override public void onTaskRemoved(final Intent rootIntent) {     super.onTaskRemoved(rootIntent);     // Restart service in 500 ms     ((AlarmManager) getSystemService(Context.ALARM_SERVICE))             .set(AlarmManager.RTC, System.currentTimeMillis() + 500, PendingIntent                     .getService(this, 3, new Intent(this, SensorListener.class), 0)); }  @Override public void onDestroy() {     super.onDestroy();     if (null != db) {         db.close();     }     try {         SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);         sm.unregisterListener(this);     } catch (Exception e) {         e.printStackTrace();     }     reRegisterSensor(); }  @TargetApi(19) private void reRegisterSensor() {     if (Build.VERSION.SDK_INT < 19) {         return;     }     SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);     if (null == sm) {         // 无法获得传感器管理组件     } else {         // 获得传感器管理组件     }     try {         sm.unregisterListener(this);     } catch (Exception e) {         e.printStackTrace();     }     if (null == sm.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)) {         // 没有计步器     } else {         // 有计步器     }     sm.registerListener(this, sm.getDefaultSensor(Sensor.TYPE_STEP_COUNTER),             SensorManager.SENSOR_DELAY_NORMAL, 5 * MICROSECONDS_IN_ONE_MINUTE); } 

}

参考:https://blog.csdn.net/niuzhucedenglu/article/details/53378265

作者:陈绮婧
原文链接:https://blog.csdn.net/forget323/article/details/80703598

文章来源: 安卓计步器
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!