You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

请求协助:从Google Fit读取睡眠数据的可行示例

获取Google Fit睡眠数据的完整实现方案与示例代码

我之前帮不少开发者排查过Google Fit睡眠数据读取的问题,结合官方API的最佳实践,给你整理了一套能正常运行的实现方案和代码示例,你可以对照着调整你的现有代码:

一、先确认必要的准备工作

  • 首先在Google Cloud Console里启用Google Fit API,创建OAuth客户端ID,配置你的应用签名和包名
  • 在Android项目的AndroidManifest.xml里添加基础权限:
    <uses-permission android:name="android.permission.INTERNET" />
    
  • 添加Google Play Services依赖到build.gradle(Module级):
    implementation 'com.google.android.gms:play-services-fitness:21.0.1'
    implementation 'com.google.android.gms:play-services-auth:20.7.0'
    

二、核心代码实现

1. 初始化Fit客户端并处理权限

睡眠数据属于敏感数据,必须先获取用户授权,同时Android 10+需要动态申请ACTIVITY_RECOGNITION权限:

import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.fitness.Fitness
import com.google.android.gms.fitness.FitnessOptions
import com.google.android.gms.fitness.data.DataType
import com.google.android.gms.fitness.request.DataReadRequest
import com.google.android.gms.fitness.result.DataReadResponse
import java.util.Calendar
import java.util.concurrent.TimeUnit

class SleepDataManager(private val context: Context) {
    // 定义睡眠数据的授权选项
    private val fitnessOptions = FitnessOptions.builder()
        .addDataType(DataType.TYPE_SLEEP_SEGMENT, FitnessOptions.ACCESS_READ)
        .addDataType(DataType.TYPE_SLEEP_STAGE, FitnessOptions.ACCESS_READ)
        .build()

    // 检查是否已获取Fit睡眠数据权限
    fun hasFitPermissions(): Boolean {
        val account = GoogleSignIn.getLastSignedInAccount(context) ?: return false
        return GoogleSignIn.hasPermissions(account, fitnessOptions)
    }

    // 请求Fit睡眠数据授权
    fun requestFitPermissions(activity: AppCompatActivity, requestCode: Int) {
        val account = GoogleSignIn.getLastSignedInAccount(context) ?: run {
            Toast.makeText(context, "请先登录Google账号", Toast.LENGTH_SHORT).show()
            return
        }
        GoogleSignIn.requestPermissions(activity, requestCode, account, fitnessOptions)
    }

2. 查询睡眠数据的核心方法

实现一个异步查询最近7天睡眠数据的方法,包含完整的睡眠周期和细分阶段:

// 异步查询最近7天的睡眠数据
    fun fetchRecentSleepData(callback: (List<SleepRecord>?, Exception?) -> Unit) {
        if (!hasFitPermissions()) {
            callback(null, Exception("未获取Google Fit睡眠数据读取权限"))
            return
        }

        val account = GoogleSignIn.getLastSignedInAccount(context) ?: run {
            callback(null, Exception("用户未登录Google账号"))
            return
        }

        // 设置时间范围:最近7天
        val calendar = Calendar.getInstance()
        val endTime = calendar.timeInMillis
        calendar.add(Calendar.DAY_OF_YEAR, -7)
        val startTime = calendar.timeInMillis

        // 构建数据读取请求
        val readRequest = DataReadRequest.Builder()
            .read(DataType.TYPE_SLEEP_SEGMENT) // 完整睡眠周期
            .read(DataType.TYPE_SLEEP_STAGE)   // 细分睡眠阶段
            .setTimeRange(startTime, endTime, TimeUnit.MILLISECONDS)
            .build()

        // 执行异步查询
        Fitness.getHistoryClient(context, account)
            .readData(readRequest)
            .addOnSuccessListener { response ->
                val sleepRecords = parseSleepResponse(response)
                callback(sleepRecords, null)
            }
            .addOnFailureListener { exception ->
                callback(null, exception)
            }
    }

3. 解析睡眠数据响应

把Google Fit返回的原始数据转换成易读的自定义模型:

// 解析Fit返回的睡眠数据
    private fun parseSleepResponse(response: DataReadResponse): List<SleepRecord> {
        val sleepRecords = mutableListOf<SleepRecord>()
        // 先获取所有睡眠周期
        val sleepSegments = response.getDataSet(DataType.TYPE_SLEEP_SEGMENT)
        for (segmentPoint in sleepSegments.dataPoints) {
            val segmentStartTime = segmentPoint.getStartTime(TimeUnit.MILLISECONDS)
            val segmentEndTime = segmentPoint.getEndTime(TimeUnit.MILLISECONDS)
            
            // 匹配该周期内的所有睡眠阶段
            val sleepStages = mutableListOf<SleepStage>()
            val stageDataSet = response.getDataSet(DataType.TYPE_SLEEP_STAGE)
            for (stagePoint in stageDataSet.dataPoints) {
                val stageStartTime = stagePoint.getStartTime(TimeUnit.MILLISECONDS)
                val stageEndTime = stagePoint.getEndTime(TimeUnit.MILLISECONDS)
                if (stageStartTime >= segmentStartTime && stageEndTime <= segmentEndTime) {
                    val stageType = stagePoint.getValue(DataType.TYPE_SLEEP_STAGE.fields[0]).asInt()
                    val stageName = when (stageType) {
                        SleepStage.STAGE_AWAKE -> "清醒"
                        SleepStage.STAGE_LIGHT -> "浅睡眠"
                        SleepStage.STAGE_DEEP -> "深睡眠"
                        SleepStage.STAGE_REM -> "REM睡眠"
                        else -> "未知阶段"
                    }
                    sleepStages.add(SleepStage(stageName, stageType, stageStartTime, stageEndTime))
                }
            }
            sleepRecords.add(SleepRecord(segmentStartTime, segmentEndTime, sleepStages))
        }
        return sleepRecords
    }

    // 自定义睡眠数据模型
    data class SleepRecord(
        val startTime: Long, // 睡眠开始时间戳(毫秒)
        val endTime: Long,   // 睡眠结束时间戳(毫秒)
        val stages: List<SleepStage> // 该周期内的睡眠阶段列表
    )

    data class SleepStage(
        val name: String,
        val type: Int,
        val startTime: Long,
        val endTime: Long
    ) {
        companion object {
            const val STAGE_AWAKE = 0
            const val STAGE_LIGHT = 1
            const val STAGE_DEEP = 2
            const val STAGE_REM = 3
        }
    }
}

三、在Activity中使用的完整示例

import android.Manifest
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat

class MainActivity : AppCompatActivity() {
    private val REQUEST_ACTIVITY_RECOGNITION = 1001
    private val REQUEST_FIT_PERMISSIONS = 1002
    private lateinit var sleepDataManager: SleepDataManager

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        sleepDataManager = SleepDataManager(this)

        // 处理Android 10+的运动识别权限
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACTIVITY_RECOGNITION)
                != PackageManager.PERMISSION_GRANTED
            ) {
                ActivityCompat.requestPermissions(
                    this,
                    arrayOf(Manifest.permission.ACTIVITY_RECOGNITION),
                    REQUEST_ACTIVITY_RECOGNITION
                )
                return
            }
        }

        // 检查并请求Fit权限
        checkAndRequestFitPermissions()
    }

    private fun checkAndRequestFitPermissions() {
        if (!sleepDataManager.hasFitPermissions()) {
            sleepDataManager.requestFitPermissions(this, REQUEST_FIT_PERMISSIONS)
        } else {
            fetchAndDisplaySleepData()
        }
    }

    private fun fetchAndDisplaySleepData() {
        sleepDataManager.fetchRecentSleepData { records, exception ->
            runOnUiThread {
                if (exception != null) {
                    Toast.makeText(this, "获取睡眠数据失败:${exception.message}", Toast.LENGTH_LONG).show()
                    return@runOnUiThread
                }
                records?.forEachIndexed { index, record ->
                    Log.d("SleepData", "第${index+1}次睡眠:${record.startTime} 至 ${record.endTime}")
                    record.stages.forEach { stage ->
                        Log.d("SleepData", "  - ${stage.name}:${stage.startTime} 至 ${stage.endTime}")
                    }
                }
                Toast.makeText(this, "成功获取${records?.size ?: 0}条睡眠记录", Toast.LENGTH_SHORT).show()
            }
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when (requestCode) {
            REQUEST_ACTIVITY_RECOGNITION -> {
                if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    checkAndRequestFitPermissions()
                } else {
                    Toast.makeText(this, "需要运动识别权限才能读取睡眠数据", Toast.LENGTH_LONG).show()
                }
            }
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == REQUEST_FIT_PERMISSIONS) {
            if (resultCode == RESULT_OK) {
                fetchAndDisplaySleepData()
            } else {
                Toast.makeText(this, "授权失败,无法读取睡眠数据", Toast.LENGTH_LONG).show()
            }
        }
    }
}

四、关键注意事项

  • 确保用户已登录Google账号,且授权流程完成后再执行数据查询
  • 如果查询不到数据,可能是用户的Google Fit账户中没有记录睡眠数据,或者权限未正确授予
  • 睡眠阶段数据依赖Google Fit的自动监测或用户手动录入,部分设备可能不支持完整的阶段识别

内容的提问来源于stack exchange,提问作者Eugenio Cesar Velazquez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 10:20:09