Android实现计步器功能,适配Android10,隔天步数清零,查看历史运动纪录_附源码

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

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

目前已经适配到10.0系统,代码已经上传至github,觉得好用不妨给个star,谢谢了~

开始之前首先来看下我们需要解决的点有哪些:

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

2、不能查看历史数据

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

      

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

工程的目录结构如下:

其中主要的代码都在StepService.class 中了,其中注释也都非常详细,我就直接放代码了:

/**
 * Created by fySpring
 * Date: 2020/4/22
 * To do:
 */
class StepService : Service(), SensorEventListener {
    //当前日期
    private var currentDate: String? = null
    //当前步数
    private var currentStep: Int = 0
    //传感器
    private var sensorManager: SensorManager? = null
    //数据库
    private var stepDataDao: StepDataDao? = null
    //计步传感器类型 0-counter 1-detector
    private var stepSensor = -1
    //广播接收
    private var mInfoReceiver: BroadcastReceiver? = null
    //发送消息,用来和Service之间传递步数
    private val messenger = Messenger(MessengerHandler())
    //是否有当天的记录
    private var hasRecord: Boolean = false
    //未记录之前的步数
    private var hasStepCount: Int = 0
    //下次记录之前的步数
    private var previousStepCount: Int = 0
    private var builder: Notification.Builder? = null

    private var notificationManager: NotificationManager? = null
    private var nfIntent: Intent? = null


    override fun onCreate() {
        super.onCreate()
        initBroadcastReceiver()
        Thread(Runnable { getStepDetector() }).start()
        initTodayData()
    }

    @Nullable
    override fun onBind(intent: Intent): IBinder? {
        return messenger.binder
    }


    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        /**
         * 此处设将Service为前台,不然当APP结束以后很容易被GC给干掉,
         * 这也就是大多数音乐播放器会在状态栏设置一个原理大都是相通的
         */
        notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager


        //----------------  针对8.0 新增代码 --------------------------------------
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            builder = Notification.Builder(this.applicationContext, ConstantData.CHANNEL_ID)
            val notificationChannel =
                NotificationChannel(
                    ConstantData.CHANNEL_ID,
                    ConstantData.CHANNEL_NAME,
                    NotificationManager.IMPORTANCE_MIN
                )
            notificationChannel.enableLights(false)//如果使用中的设备支持通知灯,则说明此通知通道是否应显示灯
            notificationChannel.setShowBadge(false)//是否显示角标
            notificationChannel.lockscreenVisibility = Notification.VISIBILITY_SECRET
            notificationManager?.createNotificationChannel(notificationChannel)
            builder?.setChannelId(ConstantData.CHANNEL_ID)
        } else {
            builder = Notification.Builder(this.applicationContext)
        }

        /**
         * 设置点击通知栏打开的界面,此处需要注意了,
         * 如果你的计步界面不在主界面,则需要判断app是否已经启动,
         * 再来确定跳转页面,这里面太多坑
         */
        nfIntent = Intent(this, MainActivity::class.java)
        setStepBuilder()
        // 参数一:唯一的通知标识;参数二:通知消息。
        startForeground(ConstantData.NOTIFY_ID, builder?.build())// 开始前台服务
        return START_STICKY
    }

    /**
     * 自定义handler
     */
    private inner class MessengerHandler : Handler() {
        override fun handleMessage(msg: Message) = when (msg.what) {
            ConstantData.MSG_FROM_CLIENT -> try {
                //这里负责将当前的步数发送出去,可以在界面或者其他地方获取,我这里是在MainActivity中获取来更新界面
                val messenger = msg.replyTo
                val replyMsg = Message.obtain(null, ConstantData.MSG_FROM_SERVER)
                val bundle = Bundle()
                bundle.putInt("steps", currentStep)
                replyMsg.data = bundle
                messenger.send(replyMsg)
            } catch (e: RemoteException) {
                e.printStackTrace()
            }
            else -> super.handleMessage(msg)
        }
    }

    /**
     * 初始化广播
     */
    private fun initBroadcastReceiver() {
        val filter = 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 = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                when (intent.action) {
                    // 屏幕灭屏广播
                    Intent.ACTION_SCREEN_OFF -> saveStepData()
                    //关机广播,保存好当前数据
                    Intent.ACTION_SHUTDOWN -> saveStepData()
                    // 屏幕解锁广播
                    Intent.ACTION_USER_PRESENT -> saveStepData()
                    // 当长按电源键弹出“关机”对话或者锁屏时系统会发出这个广播
                    // example:有时候会用到系统对话框,权限可能很高,会覆盖在锁屏界面或者“关机”对话框之上,
                    // 所以监听这个广播,当收到时就隐藏自己的对话,如点击pad右下角部分弹出的对话框
                    Intent.ACTION_CLOSE_SYSTEM_DIALOGS -> saveStepData()
                    //监听日期变化
                    Intent.ACTION_DATE_CHANGED, Intent.ACTION_TIME_CHANGED, Intent.ACTION_TIME_TICK -> {
                        saveStepData()
                        isNewDay()
                    }
                }
            }
        }
        //注册广播
        registerReceiver(mInfoReceiver, filter)
    }

    /**
     * 初始化当天数据
     */
    private fun initTodayData() {
        //获取当前时间
        currentDate = TimeUtil.getCurrentDate()
        //获取数据库
        stepDataDao = StepDataDao(applicationContext)
        //获取当天的数据,用于展示
        val entity = stepDataDao!!.getCurDataByDate(currentDate!!)
        //为空则说明还没有该天的数据,有则说明已经开始当天的计步了
        currentStep = if (entity == null) 0 else Integer.parseInt(entity.steps!!)
    }

    /**
     * 监听晚上0点变化初始化数据
     */
    private fun isNewDay() {
        val time = "00:00"
        if (time == SimpleDateFormat("HH:mm").format(Date()) || currentDate != TimeUtil.getCurrentDate()) {
            initTodayData()
        }
    }

    /**
     * 获取传感器实例
     */
    private fun getStepDetector() {
        if (sensorManager != null) {
            sensorManager = null
        }
        // 获取传感器管理器的实例
        sensorManager = this
            .getSystemService(Context.SENSOR_SERVICE) as SensorManager
        //android4.4以后可以使用计步传感器
        if (Build.VERSION.SDK_INT >= 19) {
            addCountStepListener()
        }
    }

    /**
     * 添加传感器监听
     */
    private fun addCountStepListener() {
        val countSensor = sensorManager!!.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
        val detectorSensor = sensorManager!!.getDefaultSensor(Sensor.TYPE_STEP_DETECTOR)
        if (countSensor != null) {
            stepSensor = 0
            sensorManager!!.registerListener(
                this@StepService,
                countSensor,
                SensorManager.SENSOR_DELAY_NORMAL
            )
        } else if (detectorSensor != null) {
            stepSensor = 1
            sensorManager!!.registerListener(
                this@StepService,
                detectorSensor,
                SensorManager.SENSOR_DELAY_NORMAL
            )
        }
    }

    /**
     * 由传感器记录当前用户运动步数,注意:该传感器只在4.4及以后才有,并且该传感器记录的数据是从设备开机以后不断累加,
     * 只有当用户关机以后,该数据才会清空,所以需要做数据保护
     */
    override fun onSensorChanged(event: SensorEvent) {
        if (stepSensor == 0) {
            val tempStep = event.values[0].toInt()
            if (!hasRecord) {
                hasRecord = true
                hasStepCount = tempStep
            } else {
                val thisStepCount = tempStep - hasStepCount
                currentStep += thisStepCount - previousStepCount
                previousStepCount = thisStepCount
            }
            saveStepData()
        } else if (stepSensor == 1) {
            if (event.values[0].toDouble() == 1.0) {
                currentStep++
                saveStepData()
            }
        }
    }

    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {

    }


    /**
     * 保存当天的数据到数据库中,并去刷新通知栏
     */
    private fun saveStepData() {
        //查询数据库中的数据
        var entity = stepDataDao?.getCurDataByDate(currentDate!!)
        //为空则说明还没有该天的数据,有则说明已经开始当天的计步了
        if (entity == null) {
            //没有则新建一条数据
            entity = StepEntity()
            entity.curDate = currentDate
            entity.steps = currentStep.toString()

            stepDataDao?.addNewData(entity)
        } else {
            //有则更新当前的数据
            entity.steps = currentStep.toString()

            stepDataDao?.updateCurData(entity)
        }
        setStepBuilder()
    }

    private fun setStepBuilder() {
        builder?.setContentIntent(
            PendingIntent.getActivity(this,0,nfIntent,0)
        ) // 设置PendingIntent
            ?.setLargeIcon(
                BitmapFactory.decodeResource(
                    this.resources,
                    R.mipmap.ic_launcher
                )
            )
            ?.setContentTitle("今日步数" + currentStep + "步")
            ?.setSmallIcon(R.mipmap.ic_launcher)
            ?.setContentText("加油,要记得勤加运动哦")
        // 获取构建好的Notification
        val stepNotification = builder?.build()
        //调用更新
        notificationManager?.notify(ConstantData.NOTIFY_ID, stepNotification)
    }

    override fun onDestroy() {
        super.onDestroy()
        //主界面中需要手动调用stop方法service才会结束
        stopForeground(true)
        unregisterReceiver(mInfoReceiver)
    }

    override fun onUnbind(intent: Intent): Boolean {
        return super.onUnbind(intent)
    }
}

其中关于四大组件之一的Service也有很多要去学习的,这几天也是恶补了一下,算是弥补当年在学校没有仔细学习这一块的遗憾吧 - -
主要要说的就是以上了,源码在这里 源码点我点我(各位大佬下载了觉得不错的话帮忙给个好评,谢谢啦~)

2017/4/18更新:

使用了一段时间后我发现我这种方法计算的步数和微信计步的记录数据一样呢~简直不要太棒

2018/03/01更新

一个非常关键的权限我居然忘了申请。。。在AndroidManifest添加以下权限:
<uses-permission android:name="android.permission.BODY_SENSORS" />

另外6.0系统以上的手机要在代码中动态申请该权限,切记,我是说我这么强大的MIX2怎么不计步呢,o(╯□╰)o哎,改代码去了

2020/04/22更新:

由于android8.0系统不允许后台应用创建后台服务,需要通过startForegroundService来启动,导致8.0以上机型会闪退。近日正在准备找工作,于是将代码全部换成了kotlin,并且优化部分代码,算是加深记忆。顺便说一句(kotlin用起来真香,我已经回不去了)

2020/10/11更新:

android10.0中把这一块权限改了,所以会记录不了运动步数。需要申请以下权限,然后在代码中动态申请该权限。

<uses-permission android:name="android.permission.ACTIVITY_RECOGNITION"/>

  • 41
    点赞
  • 151
    收藏
    觉得还不错? 一键收藏
  • 103
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 103
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值