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

Flutter开发Instagram克隆:Android14存储权限请求失效问题

Android 14 下 Flutter 图片选择权限置灰问题解决

问题描述

我用Flutter开发Instagram克隆应用,需要访问设备存储让用户选择图片发布,但应用设置中的存储权限呈灰色不可用状态。跟着YouTube教程写的代码,仅在Android 11模拟器上能收到权限请求,Android 14真机既不弹出权限请求,权限还置灰,已经在AndroidManifest中添加了uses-permission,代码如下:

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:photo_manager/photo_manager.dart';


class NewPostScreen extends StatefulWidget {
  const NewPostScreen({Key? key}) : super(key: key);

  @override
  State<NewPostScreen> createState() => _NewPostScreenState();
}

class _NewPostScreenState extends State<NewPostScreen> {
  final List<Widget> _mediaList = [];
  final List<File> path = [];
  File? _file;
  int currentPage = 0;
  int? lastPage;

  _fetchNewMedia() async {
    lastPage = currentPage;
    final PermissionState ps = await PhotoManager.requestPermissionExtend();
    if (ps.isAuth) {
      List<AssetPathEntity> album =
          await PhotoManager.getAssetPathList(onlyAll: true);
      List<AssetEntity> media =
          await album[0].getAssetListPaged(page: currentPage, size: 60);

      for (var asset in media) {
        if (asset.type == AssetType.image) {
          final file = await asset.file;
          if (file != null) {
            path.add(File(file.path));
            _file = path[0];
          }
        }
      }
      List<Widget> temp = [];
      for (var asset in media) {
        temp.add(FutureBuilder(
            future: asset.thumbnailDataWithSize(const ThumbnailSize(
              200,
              200,
            )),
            builder: (context, snapshot) {
              print('Thumbnail data: ${snapshot.data}');
              if (snapshot.connectionState == ConnectionState.done) {
                return Container(
                  child: Stack(
                    children: [
                      Positioned.fill(
                          child: Image.memory(
                        snapshot.data!,
                        fit: BoxFit.cover,
                      ))
                    ],
                  ),
                );
              }
              return Container();
            }));
      }
      setState(() {
        _mediaList.addAll(temp);
        currentPage++;
      });
    }
  }

  @override
  void initState() {
    super.initState();
    _fetchNewMedia();
  }

  int indexx = 0;

  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).brightness == Brightness.light
            ? Colors.white
            : Colors.black,
        title: const Text(
          'New Post',
          style: TextStyle(
            fontSize: 16,
            fontWeight: FontWeight.w500,
          ),
        ),
        toolbarHeight: 40,
        centerTitle: true,
        automaticallyImplyLeading: false,
      ),
      
      body: SafeArea(
        child: SingleChildScrollView(
          child: Column(
            children: [
              SizedBox(
                height: 410,
                child: GridView.builder(
                  gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                      crossAxisCount: 1,
                      mainAxisSpacing: 1,
                      crossAxisSpacing: 1),
                  itemBuilder: (context, index) {
                    return _mediaList[indexx];
                  },
                ),
              ),
              Container(
                width: double.infinity,
                height: 40,
                child: Row(
                  children: [
                    SizedBox(
                      width: 10,
                    ),
                    Text(
                      'Recent',
                      style: TextStyle(fontWeight: FontWeight.w500),
                    )
                  ],
                ),
              ),
              GridView.builder(
                shrinkWrap: true,
                itemCount: _mediaList.length,
                gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 3, mainAxisSpacing: 1, crossAxisSpacing: 2),
                itemBuilder: (context, index) {
                  return GestureDetector(onTap: () {
                    setState(() {
                      indexx = index;
                    });
                  }, child: _mediaList[index],
                  );
                },
              )
            ],
          ),
        ),
      ),
    );
  }
}

问题原因

Android 13(API 33)开始彻底重构存储权限机制,废弃了旧的READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE,改用细分的媒体权限:READ_MEDIA_IMAGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO。Android 14进一步收紧权限策略,旧的存储权限会被系统直接忽略,导致权限选项置灰且无法发起请求。

解决步骤

  1. 更新AndroidManifest权限配置
    替换旧的存储权限,添加适配Android 13+的媒体权限,同时兼容低版本:

    <!-- Android 13+ 读取图片权限 -->
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
    <!-- 兼容Android 12及以下版本 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
    

    不需要添加WRITE_EXTERNAL_STORAGE,现在选择图片仅需读权限;若需发布后保存到相册,使用MediaStoreAPI即可,无需申请写存储权限。

  2. 升级photo_manager包版本
    确保使用的photo_manager是适配Android 13/14的最新稳定版,旧版本没有处理新权限逻辑:

    photo_manager: ^2.7.2 # 替换为pub上的最新版本
    
  3. 优化权限请求逻辑
    在请求权限前先检查状态,确保用户明确授权,拒绝时给出提示:

    _fetchNewMedia() async {
      // 先检查当前权限状态
      PermissionState ps = await PhotoManager.checkPermissionExtend();
      if (!ps.isAuth) {
        // 发起权限请求
        ps = await PhotoManager.requestPermissionExtend();
        if (!ps.isAuth) {
          if (mounted) {
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(content: Text('需要开启图片访问权限才能发布内容')),
            );
          }
          // 权限永久拒绝时,引导用户去设置页开启
          if (ps == PermissionState.permanentlyDenied) {
            await PhotoManager.openSetting();
          }
          return;
        }
      }
      // 后续媒体加载逻辑...
    }
    
  4. Android 14精细权限适配(可选)
    Android 14新增READ_MEDIA_VISUAL_USER_SELECTED权限,允许用户仅授权特定相册访问。如需支持该特性,可在请求时指定:

    final PermissionState ps = await PhotoManager.requestPermissionExtend(
      permission: PermissionState.accessMediaLocation, // 可选,如需获取媒体位置信息
    );
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 22:27:03