请求协助:从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
相关产品推荐
相关产品推荐

