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

Flutter Health插件读取步数数据时权限始终未授予问题

Flutter Health插件权限请求失败问题排查

问题概述

使用Flutter Health插件读取步数数据时,requestAuthorization()始终返回false,已在AndroidManifest.xml中声明必要权限,且设备已安装Health Connect。

我的代码与配置

权限请求函数

Future<bool> _requestPermissions() async {
  try {
    // First check if Health Connect is available
    bool isHealthConnectAvailable = await health.isHealthConnectAvailable();
    print("Health Connect available: $isHealthConnectAvailable");

    if (!isHealthConnectAvailable) {
      print("Health Connect is not available on this device");
      return false;
    }

    // Define all the data types we want to access
    final types = [HealthDataType.STEPS];

    // Set up corresponding permissions
    final permissions =
        types.map((type) => HealthDataAccess.READ_WRITE).toList();

    print("Checking existing permissions for health data types: $types");
    // I thought if i had declared it in manifest i would get it true here. But apparently not
    // Check if we already have permissions

    bool? hasAllPermissions = await health.hasPermissions(types);

    print("Existing permissions status: $hasAllPermissions");

    // If we already have all permissions, no need to request them again
    if (hasAllPermissions == true) {
      print("Already have all required permissions!");
      return true;
    }

    // Request Activity Recognition permission
    var activityStatus = await Permission.activityRecognition.status;
    print("Activity Recognition initial status: ${activityStatus.name}");

    if (!activityStatus.isGranted) {
      final status = await Permission.activityRecognition.request();
      await Permission.locationWhenInUse.request();
      await Permission.sensors.request();
      print("Activity Recognition request result: ${status.name}");
      if (!status.isGranted) {
        print("Activity Recognition permission denied: ${status.name}");
        return false;
      }
    }

    print("Requesting authorization for health data types: $types");

    try {
      // Check if types are supported before requesting authorization
      for (HealthDataType type in types) {
        bool supported = await health.isDataTypeAvailable(type);
        print("Health data type $type supported: $supported");
        if (!supported) {
          print("Warning: $type is not supported on this device");
        }
      }

      // Request Health Connect permissions with both read and write access
      // always returns false
      bool authorized = await health.requestAuthorization(
        types,
        permissions: permissions,
      );

      print("Health authorization result: $authorized");

      if (!authorized) {
        print("Health permissions not granted!");
        return false;
      }

      // Verify if we can actually read the data types
      for (HealthDataType type in types) {
        bool isAvailable = await health.isDataTypeAvailable(type);
        print("Health data type $type available: $isAvailable");
      }

      print("Health permissions granted successfully!");
      return true;
    } catch (healthErr) {
      print("Error requesting Health API permissions: $healthErr");
      return false;
    }
  } catch (e) {
    print("General error in permission request: $e");
    return false;
  }
}

AndroidManifest.xml配置

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- Required Permissions -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACTIVITY_RECOGNITION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.BODY_SENSORS" />
    
    <!-- Health Connect Permissions -->
    <uses-permission android:name="android.permission.health.READ_STEPS" />
    <uses-permission android:name="android.permission.health.WRITE_STEPS" />
    <uses-permission android:name="android.permission.health.READ_DISTANCE" />
    <uses-permission android:name="android.permission.health.WRITE_DISTANCE" />
    <uses-permission android:name="android.permission.health.READ_TOTAL_CALORIES_BURNED" />
    <uses-permission android:name="android.permission.health.WRITE_TOTAL_CALORIES_BURNED" />
    
    <!-- Legacy Google Fit permissions (as fallback) -->
    <uses-permission android:name="com.google.android.gms.permission.ACTIVITY_RECOGNITION" />

    <!-- Queries for Health Connect -->
    <queries>
        <package android:name="com.google.android.apps.healthdata" />
        <intent>
            <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
        </intent>
    </queries>

    <application
        android:label="MyAppl"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">

        <!-- Main Activity -->
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">

            <meta-data
                android:name="io.flutter.embedding.android.NormalTheme"
                android:resource="@style/NormalTheme"
            />

            <!-- Main app intent filter -->
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>

            <!-- Health Connect intent filter -->
            <intent-filter>
                <action android:name="androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE" />
            </intent-filter>
        </activity>

        <!-- Health Connect Permission Usage Activity -->
        <activity-alias
            android:name=".ViewPermissionUsageActivity"
            android:exported="true"
            android:targetActivity=".MainActivity"
            android:permission="android.permission.START_VIEW_PERMISSION_USAGE">
            <intent-filter>
                <action android:name="android.intent.action.VIEW_PERMISSION_USAGE" />
                <category android:name="android.intent.category.HEALTH_PERMISSIONS" />
            </intent-filter>
        </activity-alias>

        <!-- Flutter embedding -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />

        <!-- Health Connect metadata -->
        <meta-data 
            android:name="health_permissions" 
            android:resource="@array/health_permissions" />
    </application>
</manifest>

requestAuthorization函数实现

Future<bool> requestAuthorization(
    List<HealthDataType> types, {
    List<HealthDataAccess>? permissions,
  }) async {
    await _checkIfHealthConnectAvailableOnAndroid();
    if (permissions != null && permissions.length != types.length) {
      throw ArgumentError(
          'The length of [types] must be same as that of [permissions].');
    }

    if (permissions != null) {
      for (int i = 0; i < types.length; i++) {
        final type = types[i];
        final permission = permissions[i];
        if ((type == HealthDataType.ELECTROCARDIOGRAM ||
                type == HealthDataType.HIGH_HEART_RATE_EVENT ||
                type == HealthDataType.LOW_HEART_RATE_EVENT ||
                type == HealthDataType.IRREGULAR_HEART_RATE_EVENT ||
                type == HealthDataType.WALKING_HEART_RATE ||
                type == HealthDataType.ATRIAL_FIBRILLATION_BURDEN) &&
            permission != HealthDataAccess.READ) {
          throw ArgumentError(
              'Requesting WRITE permission on ELECTROCARDIOGRAM / HIGH_HEART_RATE_EVENT / LOW_HEART_RATE_EVENT / IRREGULAR_HEART_RATE_EVENT / WALKING_HEART_RATE / ATRIAL_FIBRILLATION_BURDEN is not allowed.');
        }
      }
    }

    final mTypes = List<HealthDataType>.from(types, growable: true);
    final mPermissions = permissions == null
        ? List<int>.filled(types.length, HealthDataAccess.READ.index,
            growable: true)
        : permissions.map((permission) => permission.index).toList();

    // on Android, if BMI is requested, then also ask for weight and height
    if (Platform.isAndroid) _handleBMI(mTypes, mPermissions);

    List<String> keys = mTypes.map((e) => e.name).toList();
    final bool? isAuthorized = await _channel.invokeMethod(
        'requestAuthorization', {'types': keys, "permissions": mPermissions});
    return isAuthorized ?? false;
  }

已确认事项

  • 设备已安装Health Connect
  • AndroidManifest.xml包含正确的权限声明
  • 请求前检查了权限状态
  • 使用health.hasPermissions()验证现有权限
  • 手动在设备设置中授予权限,但问题仍存在

疑问

  1. 为什么requestAuthorization()始终返回false?
  2. 是否遗漏了Health Connect权限的额外配置?
  3. 使用的三星Galaxy 14(Android 14/Upside Down Cake)是否有特定设备设置导致该问题?

解答

1. requestAuthorization()返回false的核心原因

  • 权限数组未定义:你的AndroidManifest中引用了@array/health_permissions,但未在资源文件中声明该数组,Health Connect无法识别你申请的权限,导致授权流程失败。
  • 插件权限验证逻辑缺陷:hasPermissions()方法可能无法正确识别手动授予的Health Connect权限,因为这类权限是独立于系统权限的,插件自身的判断逻辑存在误差。
  • 异步权限请求顺序问题:你同时请求了ActivityRecognition、LocationWhenInUse、Sensors权限,可能存在权限状态未及时同步的情况,导致后续Health Connect权限请求时系统权限状态未更新完成。
  • 插件版本兼容性:Android 14对Health Connect的权限模型有更新,旧版本的Health插件可能未适配,导致授权流程失效。

2. 遗漏的关键配置

必须补充以下配置:

  • 添加健康权限数组:在res/values/arrays.xml中创建health_permissions数组,列出所有申请的Health Connect权限:
<resources>
    <string-array name="health_permissions">
        <item>android.permission.health.READ_STEPS</item>
        <item>android.permission.health.WRITE_STEPS</item>
        <item>android.permission.health.READ_DISTANCE</item>
        <item>android.permission.health.WRITE_DISTANCE</item>
        <item>android.permission.health.READ_TOTAL_CALORIES_BURNED</item>
        <item>android.permission.health.WRITE_TOTAL_CALORIES_BURNED</item>
    </string-array>
</resources>
  • 设置targetSdkVersion为34:Android 14的健康权限要求应用目标SDK版本至少为34,否则会触发权限兼容问题。
  • 确保插件初始化正确:确认Health对象初始化时使用正确的工厂类,比如HealthFactory()(根据你使用的插件版本调整)。

3. 三星Galaxy 14的特定设置影响

三星设备存在以下可能的干扰点:

  • 电池优化拦截:三星的电池优化功能可能会阻止Health Connect的权限请求弹窗弹出,需将你的应用和Health Connect加入电池优化白名单(路径:设置 → 电池和设备维护 → 电池 → 后台使用限制 → 未监视的应用 → 添加你的应用和Health Connect)。
  • 隐私守护者拦截:三星自带的隐私守护者可能会拦截应用访问健康数据,需在设置中找到隐私守护者,允许你的应用访问健康权限。
  • Samsung Health同步设置:若用户主要使用Samsung Health,需确保Samsung Health的数据已同步到Health Connect(路径:Samsung Health → 设置 → 数据同步 → 同步到Health Connect),不过这不会导致requestAuthorization()返回false,但会影响后续数据读取。

额外排查步骤

  1. 直接尝试读取步数数据,即使requestAuthorization()返回false,部分情况下插件的授权判断逻辑存在bug,但实际权限已授予。
  2. 更新Health插件到最新版本,确保适配Android 14。
  3. 调整权限请求顺序:先等待ActivityRecognition权限完全授予后,再请求其他系统权限,最后调用requestAuthorization()。
  4. 打开Health Connect应用,检查你的应用是否已被授予对应的步数权限,若已授予但插件仍返回false,说明插件的权限验证逻辑存在问题。

内容的提问来源于stack exchange,提问作者Clover99

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 21:34:57