第二十讲 权限与设备能力

0 阅读10分钟

前言:

对于获取设备权限,这一章的重要性极大,一定注意!尤其是安卓/ios端的朋友,要注意了!这两端的配置很重要哦!

一、定位

本讲核心是讲解 Flutter 开发中,如何获取和管理设备核心权限(相机、相册、定位、存储),以及如何调用设备基础能力(设备信息、网络状态、电池状态)和实现本地通知与推送功能。通过本章学习,可掌握设备权限的申请、校验、管理逻辑,以及设备能力的调用方法,解决开发中“访问设备资源”的核心需求,为开发具备设备交互能力的应用(如拍照上传、位置定位、状态监控、消息提醒)奠定基础。

核心工具:permission_handler(Flutter 主流权限管理插件,统一处理各类设备权限的申请、查询、监听);辅助工具:device_info_plus(设备信息)、connectivity_plus(网络状态)、battery_plus(电池状态)、flutter_local_notifications(本地通知)、firebase_messaging(远程推送,可选)。

Flutter 中权限与设备能力调用的底层核心是“Flutter 插件层 → 原生层(Android/iOS)→ 设备系统层”的三层交互,其结构如下(清晰展示数据流转和权限管控逻辑):

image.png

原理说明:Flutter 本身无法直接访问设备底层资源,需通过第三方插件(如 permission_handler)作为“桥梁”,调用 Android/iOS 原生的权限管理接口和设备能力接口;设备系统层负责校验权限合法性,返回资源访问结果,再由插件将原生结果封装成 Flutter 可识别的对象,回调给应用层,完成整个交互流程。

二、分模块案例、属性与注意事项

模块1:权限管理(相机、相册、定位、存储)permission_handler

权限控制严格,都需要申请权限,才能够获得访问能力。

1.1 核心属性(permission_handler 关键API)

  • 权限枚举(Permission) :对应四种核心权限,无需自定义,插件直接提供

    • Permission.camera:相机权限
    • Permission.photos:相册权限(iOS)/ Permission.storage:存储权限(Android,含相册、文件访问)
    • Permission.location:定位权限(基础定位);Permission.locationAlways:后台定位权限;Permission.locationWhenInUse:前台定位权限
  • 权限状态(PermissionStatus) :判断权限当前状态,核心状态如下

    • granted:权限已允许
    • denied:权限已拒绝(可再次申请)
    • permanentlyDenied:权限永久拒绝(无法再次申请,需引导用户去系统设置开启)
    • restricted:权限受限制(系统级限制,如家长控制)
  • 核心方法

    • Permission.status:查询单个权限状态(返回 Future)
    • Permission.request():申请单个权限(返回 Future)
    • [PermissionGroup].request():批量申请多个权限(如 [Permission.camera, Permission.photos].request())
    • openAppSettings():打开当前应用的系统设置页面(用于引导用户开启永久拒绝的权限)

1.2 案例(单个权限申请+批量申请)

import 'package:permission_handler/permission_handler.dart';

// 1. 单个权限申请(以相机为例)
Future<void> requestCameraPermission() async {
  // 查询当前相机权限状态
  PermissionStatus status = await Permission.camera.status;
  
  if (status == PermissionStatus.granted) {
    // 权限已允许,执行相机相关操作(如打开相机)
    print("相机权限已允许");
  } else if (status == PermissionStatus.denied) {
    // 权限未允许,发起申请
    status = await Permission.camera.request();
    if (status == PermissionStatus.granted) {
      print("相机权限申请成功");
    } else {
      print("相机权限申请失败");
    }
  } else if (status == PermissionStatus.permanentlyDenied) {
    // 权限永久拒绝,引导用户去系统设置开启
    print("相机权限已永久拒绝,请去设置开启");
    await openAppSettings();
  }
}

// 2. 批量申请权限(相机+相册+存储)
Future<void> requestBatchPermissions() async {
  Map<Permission, PermissionStatus> statuses = await [
    Permission.camera,
    Permission.photos, // iOS用photos,Android用storage
    Permission.storage,
  ].request();
  
  // 遍历权限状态
  statuses.forEach((permission, status) {
    if (status == PermissionStatus.granted) {
      print("${permission.toString()} 权限已允许");
    } else if (status == PermissionStatus.permanentlyDenied) {
      print("${permission.toString()} 权限永久拒绝,请去设置开启");
    }
  });
}

1.3 注意事项

  • 平台适配:Android 和 iOS 权限配置不同,需在各自原生配置文件中添加权限说明(必做,否则申请权限会直接失败):

Android:在 android/app/src/main/AndroidManifest.xml 中添加权限标签(如 )

  • 标签必须放在 标签内
  • 通常放在 标签之前
  • 可以添加多个权限,每个权限一行

iOS:在 ios/Runner/Info.plist 中添加权限描述(如 NSCameraUsageDescription需要相机权限用于拍照上传,描述需明确,否则审核会被拒)

  • 权限申请时机:不要一进入应用就批量申请所有权限,应在“即将使用该功能时”申请(如点击“拍照”按钮时,再申请相机权限),提升用户体验。
  • 永久拒绝处理:当权限被永久拒绝时,无法通过代码再次申请,必须引导用户进入系统设置开启,否则相关功能无法使用。
  • Android 13+ 适配:存储权限拆分更细,需根据需求申请 READ_MEDIA_IMAGES(读取图片)、READ_MEDIA_VIDEO(读取视频)等细分权限。

模块2:设备信息、网络状态、电池状态

2.1 核心工具与属性

功能核心工具(插件)关键属性/方法
设备信息device_info_plus- AndroidDeviceInfo:获取Android设备信息(如设备型号、系统版本、厂商)
  • IosDeviceInfo:获取iOS设备信息(如设备名称、系统版本、UUID)
  • DeviceInfoPlugin().deviceInfo:获取当前设备的统一信息对象 | | 网络状态 | connectivity_plus | - ConnectivityResult:网络状态枚举(wifi、mobile、none)
  • Connectivity().checkConnectivity():查询当前网络状态
  • Connectivity().onConnectivityChanged:监听网络状态变化(实时回调) | | 电池状态 | battery_plus | - Battery().batteryLevel:获取当前电池电量(0-100)
  • Battery().isInBatterySaveMode:判断是否开启省电模式
  • Battery().onBatteryStateChanged:监听电池状态变化(充电中、放电中、充满) |

2.2 案例(获取设备信息+监听网络+监听电池)

import 'package:flutter/material.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:battery_plus/battery_plus.dart';
import 'package:permission_handler/permission_handler.dart';
import 'dart:async';

// 1. 获取设备信息
Future<void> getDeviceInfo(BuildContext context) async {
  // 首先请求权限
  if (Theme.of(context).platform == TargetPlatform.android) {
    // Android需要请求电话状态权限
    PermissionStatus status = await Permission.phone.request();
    
    if (status.isGranted) {
      // 权限已授予,获取设备信息
      DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
      AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
      print("Android设备型号:${androidInfo.model}");
      print("Android系统版本:${androidInfo.version.release}");
      print("设备厂商:${androidInfo.manufacturer}");
      
      // 显示成功消息
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text("设备信息获取成功!"))
      );
    } else {
      // 权限被拒绝
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text("需要权限才能获取设备信息"))
      );
      print("权限被拒绝,无法获取设备信息");
    }
  } else if (Theme.of(context).platform == TargetPlatform.iOS) {
    // iOS通常不需要特殊权限获取设备信息
    DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
    IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
    print("iOS设备名称:${iosInfo.name}");
    print("iOS系统版本:${iosInfo.systemVersion}");
    print("设备UUID:${iosInfo.identifierForVendor}");
    
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text("设备信息获取成功!"))
    );
  }
}

// 2. 监听网络状态(实时)
StreamSubscription? connectivitySubscription;
void listenNetworkStatus() {
  connectivitySubscription = Connectivity().onConnectivityChanged.listen((List<ConnectivityResult> results) {
    if (results.contains(ConnectivityResult.wifi)) {
      print("当前网络:WiFi");
      // 执行WiFi环境下的操作(如高清下载)
    } else if (results.contains(ConnectivityResult.mobile)) {
      print("当前网络:移动数据");
      // 执行移动数据环境下的操作(如提醒用户节省流量)
    } else if (results.contains(ConnectivityResult.none)) {
      print("当前无网络");
      // 提示用户检查网络
    } else {
      print("网络状态未知");
    }
  });
}

// 3. 监听电池状态(实时)
StreamSubscription? batterySubscription;
void listenBatteryStatus() {
  batterySubscription = Battery().onBatteryStateChanged.listen((BatteryState state) {
    switch (state) {
      case BatteryState.charging:
        print("电池正在充电");
        break;
      case BatteryState.discharging:
        print("电池正在放电");
        break;
      case BatteryState.full:
        print("电池已充满");
        break;
      case BatteryState.connectedNotCharging:
        print("设备已连接电源但未充电");
        break;
      case BatteryState.unknown:
        print("电池状态未知");
        break;
    }
    // 获取当前电量
    Battery().batteryLevel.then((value) {
      print("当前电池电量:$value%");
    });
  });
}

// 4. 创建完整的Widget页面
class DeviceInfoPage extends StatefulWidget {
  const DeviceInfoPage({super.key});

  @override
  State<DeviceInfoPage> createState() => _DeviceInfoPageState();
}

class _DeviceInfoPageState extends State<DeviceInfoPage> {
  StreamSubscription? connectivitySubscription;
  StreamSubscription? batterySubscription;

  @override
  void initState() {
    super.initState();
    // 启动网络状态监听
    listenNetworkStatus();
    // 启动电池状态监听
    listenBatteryStatus();
  }

  // 页面销毁时,取消监听(避免内存泄漏)
  @override
  void dispose() {
    connectivitySubscription?.cancel();
    batterySubscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("设备信息与状态")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () => getDeviceInfo(context),
              child: const Text("获取设备信息"),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => listenNetworkStatus(),
              child: const Text("开始监听网络状态"),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => listenBatteryStatus(),
              child: const Text("开始监听电池状态"),
            ),
          ],
        ),
      ),
    );
  }
}

// 5. 应用入口
void main() {
  runApp(const MaterialApp(
    home: DeviceInfoPage(),
  ));
}

2.3 注意事项

  • 插件依赖:需在 pubspec.yaml 中添加对应插件依赖,并执行 pub get(如 device_info_plus: ^9.0.0)。
  • 网络监听:需在 AndroidManifest.xml(Android)和 Info.plist(iOS)中添加网络访问权限(Android默认已添加,iOS需开启网络权限)。
    <!-- 添加获取设备信息所需的权限 -->
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    
  • 内存泄漏:监听网络、电池状态时,会创建 StreamSubscription,页面销毁时必须取消监听,否则会导致内存泄漏。
  • 设备兼容性:部分老旧设备可能无法获取完整的设备信息或电池状态,需做异常捕获(try-catch)。

模块3:本地通知与推送

3.1 核心工具与属性(flutter_local_notifications)

  • 核心对象:FlutterLocalNotificationsPlugin(全局单例,统一管理通知)

  • 初始化配置

    • AndroidInitializationSettings:Android 通知初始化(设置通知图标)
    • DarwinInitializationSettings:iOS 通知初始化(设置权限)
    • InitializationSettings:统一初始化配置(整合Android和iOS配置)
  • 通知属性(NotificationDetails)

    • title:通知标题
    • body:通知内容
    • android:Android 通知细节(如渠道、优先级、图标)
    • iOS:iOS 通知细节(如声音、 badge 数量)
  • 核心方法

    • initialize():初始化通知插件
    • show():显示即时本地通知
    • schedule():显示定时本地通知(如10分钟后提醒)
    • cancel():取消单个通知;cancelAll():取消所有通知

3.2 案例(初始化+显示即时通知+定时通知)

import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:permission_handler/permission_handler.dart';

// 全局单例
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();

// 1. 初始化本地通知
Future<void> initializeLocalNotifications() async {
  // 首先请求通知权限
  if (await Permission.notification.request().isGranted) {
    print("通知权限已授予");
  } else {
    print("通知权限被拒绝");
    return;
  }

  // Android 配置
  const AndroidInitializationSettings androidSettings =
      AndroidInitializationSettings('@mipmap/ic_launcher');

  // iOS 配置
  const DarwinInitializationSettings iosSettings = DarwinInitializationSettings(
    requestAlertPermission: true,
    requestBadgePermission: true,
    requestSoundPermission: true,
  );

  // 统一初始化配置
  const InitializationSettings initializationSettings =
      InitializationSettings(android: androidSettings, iOS: iosSettings);

  // 初始化插件
  await flutterLocalNotificationsPlugin.initialize(
    settings:initializationSettings,
    onDidReceiveNotificationResponse: (NotificationResponse response) {
      if (response.payload != null) {
        print("点击通知,payload:${response.payload}");
      }
    },
  );
}

// 2. 显示即时本地通知
Future<void> showInstantNotification(BuildContext context) async {
  try {
    const AndroidNotificationDetails androidNotificationDetails =
        AndroidNotificationDetails(
      'channel_id_1',
      '即时通知渠道',
      importance: Importance.max,
      priority: Priority.high,
    );

  const DarwinNotificationDetails iosNotificationDetails = DarwinNotificationDetails(
    badgeNumber: 1,
  );

    const NotificationDetails notificationDetails = NotificationDetails(
      android: androidNotificationDetails,
      iOS: iosNotificationDetails,
    );

  await flutterLocalNotificationsPlugin.show(
    id:1,
    title:'新消息提醒',
    body:'您有一条新的系统通知,请查收',
    notificationDetails:notificationDetails,
    payload: 'notification_payload_1',
  );
      // 显示成功消息
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(content: Text('通知发送成功!'))
    );
    print('通知发送成功');
  } catch (e) {
    // 显示错误消息
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text('通知发送失败: $e'))
    );
    print('通知发送失败: $e');
  }
}

// 3. 创建通知测试页面
class NotificationTestPage extends StatefulWidget {
  const NotificationTestPage({super.key});

  @override
  State<NotificationTestPage> createState() => _NotificationTestPageState();
}

class _NotificationTestPageState extends State<NotificationTestPage> {
  @override
  void initState() {
    super.initState();
    initializeLocalNotifications();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("本地通知测试")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () => showInstantNotification(context),
              child: const Text("发送即时通知"),
            ),
          ],
        ),
      ),
    );
  }
}

// 4. 应用入口
void main() {
  runApp(const MaterialApp(
    home: NotificationTestPage(),
  ));
}

3.3 注意事项

安卓

    
    <!-- 添加本地通知权限 -->
    <uses-permission android:name="android.permission.VIBRATE" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
  • Android 8.0+ 适配:必须创建通知渠道(AndroidNotificationDetails 中的 channelId),否则通知无法显示,且渠道名称、描述需清晰。
  • 通知图标:Android 通知图标需为透明背景的矢量图(mipmap 目录下),否则会显示默认图标;iOS 通知图标需配置正确的尺寸。
  • 定时通知:需初始化时区(tz.initializeTimeZones()),否则定时时间会异常;Android 休眠时,需设置 androidAllowWhileIdle: true,确保通知能正常触发。
  • 权限申请:iOS 需通过代码申请通知权限,Android 需在 AndroidManifest.xml 中添加通知权限(,Android 13+ 必须)。
  • 远程推送:若需实现远程推送(如服务器推送消息),可搭配 firebase_messaging 插件,需额外配置 Firebase 项目,流程与本地通知类似,多了“接收远程消息”的回调。

四、综合应用案例

案例需求

开发一个“设备状态监控+拍照上传+通知提醒”的综合功能:

  1. 进入页面后,批量申请相机、相册、存储权限;
  2. 权限允许后,显示当前设备信息、网络状态、电池状态;
  3. 点击“拍照”按钮,调用相机拍照,保存到相册(需存储权限);
  4. 拍照成功后,显示本地通知提醒“拍照成功,已保存到相册”;
  5. 实时监听网络状态和电池状态,状态变化时在控制台打印日志;
  6. 若权限被拒绝,提示用户去设置开启;若无网络,提示用户检查网络。

完整代码实现

import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:battery_plus/battery_plus.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
import 'dart:async';
import 'package:timezone/timezone.dart' as tz;
import 'package:timezone/data/latest.dart' as tz;

// 全局单例
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();
final ImagePicker picker = ImagePicker();

// 初始化本地通知(全局方法)
Future<void> _initializeLocalNotifications() async {
  // 创建通知渠道(Android 8.0+ 需要)
  const AndroidNotificationChannel channel = AndroidNotificationChannel(
    'channel_id_1',
    '拍照通知渠道',
    description: '用于拍照成功通知',
    importance: Importance.max,
  );

  // 创建通知渠道
  await flutterLocalNotificationsPlugin
      .resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()
      ?.createNotificationChannel(channel);

  // 初始化设置
  const AndroidInitializationSettings androidSettings =
      AndroidInitializationSettings('@mipmap/ic_launcher');
  
  const DarwinInitializationSettings darwinSettings = DarwinInitializationSettings(
    requestAlertPermission: true,
    requestBadgePermission: true,
    requestSoundPermission: true,
  );
  
  const InitializationSettings initializationSettings = InitializationSettings(
    android: androidSettings,
    iOS: darwinSettings,
    macOS: darwinSettings,
  );

  // 初始化插件
  await flutterLocalNotificationsPlugin.initialize(
    settings: initializationSettings,
    onDidReceiveNotificationResponse: (NotificationResponse response) {
      print("通知被点击: ${response.payload}");
    },
  );

  // 请求通知权限
  await flutterLocalNotificationsPlugin
      .resolvePlatformSpecificImplementation<IOSFlutterLocalNotificationsPlugin>()
      ?.requestPermissions(
        alert: true,
        badge: true,
        sound: true,
      );
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // 初始化本地通知
  await _initializeLocalNotifications();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '权限与设备能力综合案例',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const DeviceCapabilityPage(),
    );
  }
}

class DeviceCapabilityPage extends StatefulWidget {
  const DeviceCapabilityPage({super.key});

  @override
  State<DeviceCapabilityPage> createState() => _DeviceCapabilityPageState();
}

class _DeviceCapabilityPageState extends State<DeviceCapabilityPage> {
  // 状态管理
  String deviceInfoText = "正在获取设备信息...";
  String networkStatusText = "正在获取网络状态...";
  String batteryStatusText = "正在获取电池状态...";
  String permissionStatusText = "正在检查权限状态...";
  File? takenImage; // 拍照后的图片
  Map<String, PermissionStatus> permissionStatuses = {};

  // 监听订阅
  StreamSubscription? connectivitySubscription;
  StreamSubscription? batterySubscription;

  @override
  void initState() {
    super.initState();
    // 初始化时区(用于本地通知)
    tz.initializeTimeZones();
    // 延迟执行权限申请,确保context可用
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (mounted) {
        // 批量申请权限
        requestBatchPermissions();
        // 监听网络和电池状态
        listenNetworkAndBattery();
      }
    });
  }

  // 1. 批量申请权限(相机、相册、存储、通知等)
  Future<void> requestBatchPermissions() async {
    try {
      // 根据平台申请不同的权限
      List<Permission> permissions = [
        Permission.camera,
        Permission.photos,
        Permission.storage,
        Permission.notification,
      ];

      // 如果是Android,添加更多权限
      if (Theme.of(context).platform == TargetPlatform.android) {
        permissions.addAll([
          Permission.accessMediaLocation,
          Permission.manageExternalStorage,
        ]);
      }

      Map<Permission, PermissionStatus> statuses = await permissions.request();

      // 检查权限状态,获取设备信息
      bool allGranted = true;
      List<String> deniedPermissions = [];
      
      statuses.forEach((permission, status) {
        if (status != PermissionStatus.granted) {
          allGranted = false;
          deniedPermissions.add(_getPermissionName(permission));
          
          if (status == PermissionStatus.permanentlyDenied) {
            _showPermissionDialog(
              "权限被永久拒绝",
              "${_getPermissionName(permission)} 权限已永久拒绝,请去系统设置开启",
            );
          } else if (status == PermissionStatus.denied) {
            _showPermissionDialog(
              "权限被拒绝",
              "${_getPermissionName(permission)} 权限被拒绝,某些功能将无法使用",
            );
          }
        }
      });

      // 所有权限都允许,获取设备信息
      if (allGranted) {
        await getDeviceInfo();
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(content: Text("所有权限已授权,应用功能已启用")),
        );
      } else {
        setState(() {
          deviceInfoText = "部分权限未授权,某些功能可能受限\n被拒绝的权限:${deniedPermissions.join(', ')}";
        });
      }
    } catch (e) {
      setState(() {
        deviceInfoText = "权限申请失败:$e";
      });
      print("权限申请错误:$e");
    }
  }

  // 获取权限的中文名称
  String _getPermissionName(Permission permission) {
    switch (permission) {
      case Permission.camera:
        return "相机";
      case Permission.photos:
        return "相册";
      case Permission.storage:
        return "存储";
      case Permission.notification:
        return "通知";
      case Permission.accessMediaLocation:
        return "媒体位置";
      case Permission.manageExternalStorage:
        return "外部存储管理";
      default:
        return permission.toString();
    }
  }

  // 显示权限对话框
   void _showPermissionDialog(String title, String content) {
     showDialog(
       context: context,
       builder: (context) => AlertDialog(
         title: Text(title),
         content: Text(content),
         actions: [
           TextButton(
             onPressed: () => Navigator.pop(context),
             child: const Text("取消"),
           ),
           TextButton(
             onPressed: () async {
               Navigator.pop(context);
               await openAppSettings();
             },
             child: const Text("去设置"),
           ),
         ],
       ),
     );
   }

   // 检查当前权限状态
   Future<void> checkCurrentPermissions() async {
     try {
       List<Permission> permissions = [
         Permission.camera,
         Permission.photos,
         Permission.storage,
         Permission.notification,
       ];

       if (Theme.of(context).platform == TargetPlatform.android) {
         permissions.addAll([
           Permission.accessMediaLocation,
           Permission.manageExternalStorage,
         ]);
       }

       Map<String, PermissionStatus> statusMap = {};
       
       for (var permission in permissions) {
         PermissionStatus status = await permission.status;
         statusMap[_getPermissionName(permission)] = status;
       }

       setState(() {
         permissionStatuses = statusMap;
         permissionStatusText = "权限状态检查完成";
       });
     } catch (e) {
       setState(() {
         permissionStatusText = "权限状态检查失败:$e";
       });
     }
   }

   // 获取权限状态的中文描述
   String _getPermissionStatusText(PermissionStatus status) {
     switch (status) {
       case PermissionStatus.granted:
         return "已授权";
       case PermissionStatus.denied:
         return "已拒绝";
       case PermissionStatus.permanentlyDenied:
         return "永久拒绝";
       case PermissionStatus.restricted:
         return "受限";
       case PermissionStatus.limited:
         return "有限授权";
       case PermissionStatus.provisional:
         return "临时授权";
       default:
         return "未知";
     }
   }

   // 获取权限状态的颜色
   Color _getPermissionStatusColor(PermissionStatus status) {
     switch (status) {
       case PermissionStatus.granted:
         return Colors.green;
       case PermissionStatus.denied:
         return Colors.orange;
       case PermissionStatus.permanentlyDenied:
         return Colors.red;
       case PermissionStatus.restricted:
         return Colors.purple;
       case PermissionStatus.limited:
         return Colors.blue;
       case PermissionStatus.provisional:
         return Colors.cyan;
       default:
         return Colors.grey;
     }
   }

  // 2. 获取设备信息
  Future<void> getDeviceInfo() async {
    DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
    String info = "";
    if (Theme.of(context).platform == TargetPlatform.android) {
      AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
      info = "设备型号:${androidInfo.model}\n系统版本:${androidInfo.version.release}\n厂商:${androidInfo.manufacturer}";
    } else if (Theme.of(context).platform == TargetPlatform.iOS) {
      IosDeviceInfo iosInfo = await deviceInfo.iosInfo;
      info = "设备名称:${iosInfo.name}\n系统版本:${iosInfo.systemVersion}\nUUID:${iosInfo.identifierForVendor}";
    }
    setState(() {
      deviceInfoText = info;
    });
  }

  // 3. 监听网络和电池状态
  void listenNetworkAndBattery() {
    // 监听网络状态
    connectivitySubscription = Connectivity().onConnectivityChanged.listen((result) {
      String status = "";
      if (result.contains(ConnectivityResult.wifi)) {
        status = "当前网络:WiFi";
      } else if (result.contains(ConnectivityResult.mobile)) {
        status = "当前网络:移动数据";
      } else if (result.contains(ConnectivityResult.none)) {
        status = "当前无网络";
        // 提示无网络
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text("无网络连接,请检查网络设置")),
          );
        }
      } else {
        status = "网络状态未知";
      }
      if (mounted) {
        setState(() {
          networkStatusText = status;
        });
      }
      print(networkStatusText);
    });

    // 监听电池状态
    batterySubscription = Battery().onBatteryStateChanged.listen((state) {
      String status = "";
      switch (state) {
        case BatteryState.charging:
          status = "电池状态:正在充电";
          break;
        case BatteryState.discharging:
          status = "电池状态:正在放电";
          break;
        case BatteryState.full:
          status = "电池状态:已充满";
          break;
        case BatteryState.unknown:
          status = "电池状态:未知";
          break;
        case BatteryState.connectedNotCharging:
          status = "电池状态:已连接但未充电";
          break;
        default:
          status = "电池状态:未知状态";
      }
      // 获取电池电量
      Battery().batteryLevel.then((level) {
        if (mounted) {
          setState(() {
            batteryStatusText = "$status\n当前电量:$level%";
          });
        }
        print(batteryStatusText);
      });
    });
  }

  // 4. 拍照功能
  Future<void> takePhoto() async {
    // 再次检查相机权限(防止用户中途关闭权限)
    PermissionStatus cameraStatus = await Permission.camera.status;
    if (cameraStatus != PermissionStatus.granted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text("相机权限未开启,无法拍照")),
      );
      return;
    }

    // 调用相机拍照
    final XFile? photo = await picker.pickImage(source: ImageSource.camera);
    if (photo != null) {
      setState(() {
        takenImage = File(photo.path);
      });
      // 拍照成功,显示本地通知
      await showInstantNotification("拍照成功", "照片已保存到相册");
    }
  }

  // 5. 显示本地通知
  Future<void> showInstantNotification(String title, String body) async {
    const AndroidNotificationDetails androidNotificationDetails = AndroidNotificationDetails(
      'channel_id_1',
      '拍照通知渠道',
      importance: Importance.max,
      priority: Priority.high,
    );
    
    const NotificationDetails notificationDetails = NotificationDetails(
      android: androidNotificationDetails,
    );

    await flutterLocalNotificationsPlugin.show(
      id:DateTime.now().millisecondsSinceEpoch, // 用当前时间戳作为通知ID,确保唯一
      title: title ,
      body: body ,
      notificationDetails: notificationDetails,
    );
  }

  @override
  void dispose() {
    // 取消监听,避免内存泄漏
    connectivitySubscription?.cancel();
    batterySubscription?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("设备能力综合演示"),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: () {
              requestBatchPermissions();
              checkCurrentPermissions();
            },
            tooltip: "刷新权限状态",
          ),
        ],
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 权限管理
            const Text("一、权限管理", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            Text(permissionStatusText),
            const SizedBox(height: 8),
            Row(
              children: [
                ElevatedButton(
                  onPressed: requestBatchPermissions,
                  child: const Text("申请权限"),
                ),
                const SizedBox(width: 8),
                ElevatedButton(
                  onPressed: checkCurrentPermissions,
                  child: const Text("检查权限"),
                ),
              ],
            ),
            const SizedBox(height: 12),
            // 显示权限状态列表
            if (permissionStatuses.isNotEmpty)
              Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: permissionStatuses.entries.map((entry) {
                  return Padding(
                    padding: const EdgeInsets.symmetric(vertical: 4),
                    child: Row(
                      children: [
                        Container(
                          width: 12,
                          height: 12,
                          decoration: BoxDecoration(
                            color: _getPermissionStatusColor(entry.value),
                            shape: BoxShape.circle,
                          ),
                        ),
                        const SizedBox(width: 8),
                        Expanded(
                          child: Text("${entry.key}: ${_getPermissionStatusText(entry.value)}"),
                        ),
                      ],
                    ),
                  );
                }).toList(),
              ),
            const SizedBox(height: 16),

            // 设备信息
            const Text("二、设备信息", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            Text(deviceInfoText),
            const SizedBox(height: 16),

            // 网络状态
            const Text("三、网络状态", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            Text(networkStatusText),
            const SizedBox(height: 16),

            // 电池状态
            const Text("四、电池状态", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            Text(batteryStatusText),
            const SizedBox(height: 16),

            // 拍照功能
            const Text("五、拍照上传", style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
            const SizedBox(height: 8),
            ElevatedButton(
              onPressed: takePhoto,
              child: const Text("点击拍照"),
            ),
            const SizedBox(height: 12),
            // 显示拍照后的图片
            if (takenImage != null)
              Image.file(
                takenImage!,
                width: double.infinity,
                height: 200,
                fit: BoxFit.cover,
              ),
          ],
        ),
      ),
    );
  }
}

案例说明与注意事项

1. 依赖添加

2. 原生配置(必做)

需要什么权限,增加什么权限

Android(android/app/src/main/AndroidManifest.xml):
        <!-- 权限配置 -->
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

<!-- 相机功能声明 -->
<uses-feature android:name="android.hardware.camera" android:required="false"/>
iOS(ios/Runner/Info.plist):<!-- 权限描述 -->
<key>NSCameraUsageDescription</key>
<string>需要相机权限用于拍照上传</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要相册权限用于保存照片</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>需要相册权限用于保存照片</string>
<key>UIBackgroundModes</key>
<array>
  <string>remote-notification</string>
</array>

3. 核心亮点

  • 整合了本章所有核心技术:权限管理、设备信息、网络状态、电池状态、本地通知、相机拍照。
  • 权限处理完善:批量申请、状态判断、永久拒绝引导、二次校验(拍照前再次检查权限)。
  • 用户体验优化:无网络提示、权限不足提示、拍照成功通知、图片预览。
  • 内存管理:页面销毁时取消监听,避免内存泄漏。

4. 测试说明

测试时需使用真实设备(模拟器部分功能无法正常使用,如相机、电池状态),分别测试以下场景:

  • 权限申请:拒绝权限、允许权限、永久拒绝权限,观察提示是否正常。
  • 拍照功能:点击拍照,确认照片能正常预览、通知能正常显示。
  • 状态监听:切换网络(WiFi/移动数据/飞行模式)、插拔充电器,观察状态显示是否实时更新。