欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2023-12-04 19:21:34
最近需要用到计步功能,这可难坏我了,ios端倒好,有自带的计步功能,让我惊讶的是连已爬楼层都给做好了,只需要调接口便可获得数据,我有一句mmp,我很想讲。 但是抱怨归抱怨...

最近需要用到计步功能,这可难坏我了,ios端倒好,有自带的计步功能,让我惊讶的是连已爬楼层都给做好了,只需要调接口便可获得数据,我有一句mmp,我很想讲。

但是抱怨归抱怨,功能还是得事先的去实现,微信运动,乐动力,都还不错,尤其是乐动力的计步功能真的非常的强大,在ui域用户与用户交互也做得非常棒,党来内需当连续运动十步后开始计步。本想着去找他们实现的算法然后拿来用,但很明显这是不可能的。后来我搜了很多资料发现,在android4.4 kitkat 新增的step detector 以及 step counter传感器。但是!android的这个传感器虽然可以计步,但是所记录的步数是从你开机之时开始计算,不断累加,隔天也不会清零,并且,一旦关机后,传感器记录的数据也就清空了!这就很尴尬了,不过既然直接使用传感器数据不行,那我们就自己动手,将数据按天来保存~接下来进入正题,皮皮猿,我们走起~

先来看下我们需要解决的点有:

1、步数从开机之后不断累加,关机之后便清零,步数不能隔天清零

2、不能查看历史数据

这就好办了。我们只需将当前传感器记录的步数以每天为单位存进数据库,如果更新的步数为当天的则去更新数据库!先来看下我的界面(demo在文章最后):

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

第一二张图为界面效果图,数据均是从数据取出绘制在界面上,第三张图为设置前台进程时所设置的notification样式,当然了这个可以去自定义样式,再此我就不详细解释了。

工程的目录结构如下:

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

其中主要的代码都在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实现简易计步器功能隔天步数清零查看历史运动纪录,希望对大家有所帮助