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

Flutter Android13如何打开非音视频图片类附件文件?

问题:Android 13 打开PDF附件时权限报错
  • 需求:需打开应用内的各类附件文件,涵盖图片、视频、PDF、Excel等格式
  • 权限处理:针对Android 13,使用READ_MEDIA_IMAGES、READ_MEDIA_VIDEO、READ_MEDIA_AUDIO权限,通过permission_handler包请求:
    [Permission.photos, Permission.videos, Permission.audio].request()
    
  • 文件打开:通过open_file包从通知中打开已下载文件,调用方法:
    OpenFile.open(path)
    
  • 异常情况:打开PDF文件时失败,报错信息:Permission denied: android.permission.MANAGE_EXTERNAL_STORAGE flutter。该权限在设置中已被永久拒绝,引导用户跳转设置操作不直观,且添加该权限会导致应用被Play Store拒绝。

相关代码

Flutter 代码

import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:open_file/open_file.dart';
import 'package:pagedesk/data/model/ticket/attachemnt_item.dart';
import 'package:pagedesk/data/network/firebase_api.dart';
import 'package:pagedesk/utils/Utils.dart';
import 'package:pagedesk/view_model/ticket_attachemnts_view_model.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';

class Notifications {
  int maxProgress = 5;
  bool isCompleted = false;

  Future getDownloadNotification(
      AttachemntItem item,
      String downloadingText,
      String downloadCompletedText,
      String downoadFaildMessage,
      String cantOpenFailMessage,
      TicketAttachemntsViewModel viewModel) async {
    final AndroidNotificationDetails androidNotificationDetails =
        AndroidNotificationDetails(
      'name',
      'name',
      channelDescription: 'progress channel description',
      channelShowBadge: false,
      importance: Importance.max,
      priority: Priority.high,
      onlyAlertOnce: true,
      showProgress: false,
    );
    final NotificationDetails notificationDetails =
        NotificationDetails(android: androidNotificationDetails);

    String newPath = "";

    Directory directory;
    try {
      if (Platform.isAndroid) {
        if (await requestStoragePermissions()) {
          directory = (await getExternalStorageDirectory())!;
          print(directory);
          List<String> paths = directory.path.split("/");
          for (int x = 1; x < paths.length; x++) {
            String folder = paths[x];
            if (folder != "Android") {
              newPath += "/$folder";
            } else {
              break;
            }
          }
          newPath = "$newPath/Download";
          directory = Directory(newPath);
        } else {
          return;
        }
      } else {
        if (await _requestPermission(Permission.storage)) {
          directory = await getApplicationDocumentsDirectory();
        } else {
          return;
        }
      }

      if (!await directory.exists()) {
        await directory.create(recursive: true);
      }

      String filename = item.name;
      String path = directory.path;
      print('FILENAME: $filename');
      print('OATH: $path');

      File file = File('$path/$filename');
      if (await file.exists()) {
        int suffix = 1;
        String newFileName;
        while (await file.exists()) {
          newFileName =
              '${filename.replaceAll(RegExp(r'\..+'), '')}($suffix)${filename.substring(filename.lastIndexOf('.'))}';

          file = File('$path/$newFileName');
          suffix++;
        }
      } else {
        print("File doesn't exist");
      }

      FirebaseApi.localNotifications.show(
          item.id,
          item.name,
          isCompleted ? downloadCompletedText : downloadingText,
          notificationDetails,
          payload: newPath);
      viewModel.getAttachment(item).then((value) => {
            if (value != null)
              {
                file.writeAsBytes(value),
                isCompleted = true,
                FirebaseApi.localNotifications.cancel(item.id),
                FirebaseApi.localNotifications.show(item.id, item.name,
                    downloadCompletedText, notificationDetails,
                    payload: file.path)
              }
            else
              {
                isCompleted = false,
                FirebaseApi.localNotifications.cancel(item.id),
                FirebaseApi.localNotifications.show(item.id, item.name,
                    downoadFaildMessage, notificationDetails,
                    payload: null)
              }
          });
    } catch (e) {
      print('ERROR');
    }
  }
}

void openFile(String path, String cantOpenFileMessage) async {
  try {
    final result = await OpenFile.open(path);
    if (result.type == ResultType.done) {
      print('File opened successfully');
    } else if (result.type == ResultType.noAppToOpen) {
      Utils.toastMessage(cantOpenFileMessage);
    } else {
      Utils.toastMessage("Can't open file throgh this app.");
    }
  } catch (e) {
    print('Error opening file: $e');
  }
}

Future<bool> _requestPermission(Permission permission) async {
  if (await permission.isGranted) {
    return true;
  } else if (await permission.isPermanentlyDenied) {
    openAppSettings();
    return false;
  } else {
    var result = await permission.request();
    if (result == PermissionStatus.granted) {
      return true;
    }
  }

  return false;
}

Future<bool> requestStoragePermissions() async {
  List<Permission> permissions = [];
  final deviceInfo = await DeviceInfoPlugin().androidInfo;

  if (deviceInfo.version.sdkInt > 32) {
    permissions = [Permission.photos, Permission.videos, Permission.audio];
  } else {
    permissions = [Permission.storage];
  }
  Map<Permission, PermissionStatus> statuses = await permissions.request();
  return !statuses.containsKey(false);
}

AndroidManifest.xml 权限配置

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission. READ_MEDIA_AUDIO" />

内容的提问来源于stack exchange,提问作者Dušan Perić

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 00:01:02