热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

Android实现简易计步器功能隔天步数清零查看历史运动纪录

这篇文章主要介绍了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实现简易计步器功能隔天步数清零查看历史运动纪录,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!


推荐阅读
  • 本文探讨了如何有效地构建和优化微信公众平台账号,涵盖了用户信息管理、内容创作与发布、互动策略及数据分析等方面。通过合理设置用户信息字段,如用户名、昵称、密码、真实姓名和性别等,确保账号的安全性和用户体验。同时,文章还介绍了如何利用微信公众平台的各项功能,提升用户参与度和品牌影响力。 ... [详细]
  • 初探性能优化:入门指南与实践技巧
    在编程领域,常有“尚未精通编码便急于优化”的声音。为了从性能优化的角度提升代码质量,本文将带领读者初步探索性能优化的基本概念与实践技巧。即使程序看似运行良好,数据处理效率仍有待提高,通过系统学习性能优化,能够帮助开发者编写更加高效、稳定的代码。文章不仅介绍了性能优化的基础知识,还提供了实用的调优方法和工具,帮助读者在实际项目中应用这些技术。 ... [详细]
  • 题目探讨了在无向图中求解点连通数的问题,具体涉及UVA1660和POJ1966两个经典问题。通过最小割算法的应用,分析了如何高效地确定网络中的关键节点和路径,为电缆电视网络的优化设计提供了理论支持。该研究不仅验证了最小割算法的有效性,还为进一步探索复杂网络的连通性和鲁棒性奠定了基础。 ... [详细]
  • 在探讨P1923问题时,我们发现手写的快速排序在最后两个测试用例中出现了超时现象,这在意料之中,因为该题目实际上要求的是时间复杂度为O(n)的算法。进一步研究题解后,发现有选手使用STL中的`nth_element`函数成功通过了所有测试点。本文将详细分析这一现象,并提出相应的优化策略。 ... [详细]
  • 提升Android开发效率:Clean Code的最佳实践与应用
    在Android开发中,提高代码质量和开发效率是至关重要的。本文介绍了如何通过Clean Code的最佳实践来优化Android应用的开发流程。以SQLite数据库操作为例,详细探讨了如何编写高效、可维护的SQL查询语句,并将其结果封装为Java对象。通过遵循这些最佳实践,开发者可以显著提升代码的可读性和可维护性,从而加快开发速度并减少错误。 ... [详细]
  • 单链表的高效遍历及性能优化策略
    本文探讨了单链表的高效遍历方法及其性能优化策略。在单链表的数据结构中,插入操作的时间复杂度为O(n),而遍历操作的时间复杂度为O(n^2)。通过在 `LinkList.h` 和 `main.cpp` 文件中对单链表进行封装,我们实现了创建和销毁功能的优化,提高了单链表的使用效率。此外,文章还介绍了几种常见的优化技术,如缓存节点指针和批量处理,以进一步提升遍历性能。 ... [详细]
  • 本文探讨了在硬币找零问题中使用枚举法的具体应用。具体而言,题目要求将一定数额的零钱换成5分、2分和1分的硬币,并且每种硬币至少需要使用一枚。研究旨在找出所有可能的换法组合。输入数据为一行,包含一个在8到100之间的整数,表示待换的零钱数额。通过详细的枚举分析,本文提供了高效的解决方案,并验证了其在实际应用中的可行性和有效性。 ... [详细]
  • 小王详解:内部网络中最易理解的NAT原理剖析,挑战你的认知极限
    小王详解:内部网络中最易理解的NAT原理剖析,挑战你的认知极限 ... [详细]
  • 在洛谷 P1344 的坏牛奶追踪问题中,第一问要求计算最小割,而第二问则需要找到割边数量最少的最小割。通过为每条边附加一个单位权值,可以在求解最小割时优先选择边数较少的方案,从而同时解决两个问题。这种策略不仅简化了问题的求解过程,还确保了结果的最优性。 ... [详细]
  • 第六章:枚举类型与switch结构的应用分析
    第六章深入探讨了枚举类型与 `switch` 结构在编程中的应用。枚举类型(`enum`)是一种将一组相关常量组织在一起的数据类型,广泛存在于多种编程语言中。例如,在 Cocoa 框架中,处理文本对齐时常用 `NSTextAlignment` 枚举来表示不同的对齐方式。通过结合 `switch` 结构,可以更清晰、高效地实现基于枚举值的逻辑分支,提高代码的可读性和维护性。 ... [详细]
  • POJ3669题目解析:基于广度优先搜索的详细解答
    POJ3669(http://poj.org/problem?id=3669)是一道典型的广度优先搜索(BFS)问题。由于陨石的降落具有时间属性,导致地图状态会随时间动态变化。因此,可以利用结构体来记录每个陨石的降落时间和位置,从而有效地进行状态更新和路径搜索。 ... [详细]
  • 掌握这些技巧,轻松获取超过90%的资源信息
    在数字时代,高效获取所需资源是每个人必备的技能。本文将分享一系列实用技巧,帮助读者轻松获取超过90%的网络资源信息,无论是学术资料、技术文档还是最新资讯,都能迅速找到。通过优化搜索引擎使用、利用专业数据库和社群资源等方法,读者将能够在信息海洋中游刃有余。 ... [详细]
  • 2020年高薪专业排行榜揭晓:计算机科学之外还有哪些值得关注的选择?
    近日,《2020年中国大学生就业报告》正式发布,揭示了除计算机科学外,多个高薪专业值得关注。报告指出,金融工程、电子信息工程、软件工程等领域的毕业生薪资水平同样表现优异,这些专业的就业前景和发展潜力不容忽视。此外,随着新兴行业的崛起,如大数据分析、人工智能和生物技术,相关专业的人才需求也在持续增长,为学生提供了更多优质的职业选择。 ... [详细]
  • 本文详细解析了微信服务端示例类的功能与应用。其中,`ClientResponseHandler` 类主要用于处理微信支付所需的响应数据,而 `TenpayHttpClient` 则是对 HTTP 请求(包括 GET 和 POST 方法)进行了封装,以便在内部调用时更加便捷和高效。这些工具类在实际开发中起到了关键作用,开发者无需深入了解其底层实现细节,即可轻松集成微信支付功能。 ... [详细]
  • HTML 页面中调用 JavaScript 函数生成随机数值并自动展示
    在HTML页面中,通过调用JavaScript函数生成随机数值,并将其自动展示在页面上。具体实现包括构建HTML页面结构,定义JavaScript函数以生成随机数,以及在页面加载时自动调用该函数并将结果呈现给用户。 ... [详细]
author-avatar
程武钢2011
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有