在 Android 11+ 中下载/保存文件时出现问题

Problem with downloading/saving files in Android 11+

提问人:0verlord 提问时间:10/26/2023 最后编辑:0verlord 更新时间:10/26/2023 访问量:107

问:

  • 二手设备的Android版本 - 13
  • 路径 1
    给出文本错误 - 此路径
    /storage/emulated/0/Android/media/my_app/files/img.jpg
  • 路径 2
    保存,但位于用户无法访问的数据文件夹中(仅在 Android Studio 上)。
    /storage/emulated/0/Android/data/my_app/files/img.jpg

我希望你们一切都好。我目前正面临着使用我的 Android 应用程序的挑战,我真的可以使用社区的一些指导。

问题来了:我正在开发一个 Android 应用程序,我需要将文件下载/保存到设备的存储中,以便用户可以访问它们。 我正在使用 Capacitor 进行开发。但是,我在尝试将这些文件保存到正确的位置时遇到了问题。

我已经试过了

  • Mediastore.saveToDownloads from '@agorapulse/capacitor-mediastore' - 我的路径错误 -/storage/emulated/0/Android/media/my_app/files/img.jpg
  • Filesystem.writeFile 从 '@capacitor/filesystem' 返回{"uri":"file:///storage/emulated/0/Android/data/my_app/files/img.jpg"}

在这里,我可以看到我的文件,但只能在Android Studio中。

我正在尝试让用户保存文件,因为它可以在 iOS 中完成(使用 ),这样他就可以通过本机文件应用程序直接访问它Directory.ExternalStorage

我已经在我的存储中声明了读取和写入外部存储的必要权限。但是,尽管如此,我仍然收到错误或文件似乎没有按预期保存。AndroidManifest.xml

  1. 二手设备的Android版本 - 13
  2. “@capacitor/核心”: “^5.0.0”
  3. “@capacitor/文件系统”: “^5.1.2”
  4. “@capacitor/设备”: “^5.0.0”
    <!-- Permissions -->
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
    <uses-permission android:name="android.permission.RECORD_AUDIO" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
    <uses-permission android:name="android.permission.RECORD_VIDEO" />
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />
    // Test of permission for managing external storage
    <!-- <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> -->
    // For Android 10 and older
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
import { Device } from '@capacitor/device';
import type { PermissionStatus as FilePermissionStatus } from '@capacitor/filesystem';
import { Directory, Filesystem } from '@capacitor/filesystem';
  const downloadFile = async (file: FileModel): Promise<FileStatusEnum> => {
    if (isNativeMobile) {
      const media = await $api.file.media(file.apiUrl);
      if (isBlob(media)) {
        const savedFile = await saveFile(
          media as Blob,
          file.key,
          file.name,
          file.type,
          file.mimeType
        );

        if (savedFile !== undefined) {
          return FileStatusEnum.Success;
        } else {
          return FileStatusEnum.Error;
        }
      } else {
        return FileStatusEnum.Error;
      }
    } else {
      const media = await $api.file.media(file.apiUrl);
      if (isBlob(media)) {
        const link = Object.assign(document.createElement('a'), {
          href: URL.createObjectURL(media as Blob),
          download: `${file.name}.${file.type}`,
        });
        link.click();
      }
    }

    return FileStatusEnum.Success;
  };

  const saveFile = async (
    file: Blob,
    internal: string,
    name: string,
    ext: string,
    mimeType: string
  ): Promise<string | undefined> => {
    if (hasFilePermission.value === false) {
      let status: FilePermissionStatus = await Filesystem.checkPermissions();

      if (status.publicStorage !== 'granted') {
        status = await Filesystem.requestPermissions();
      }

      // если пользователь отказался давать разрешение
      if (status.publicStorage !== 'granted') {
        return undefined;
      }

      hasFilePermission.value = true;
    }

    const base64Data = await blobToString(file);
    if (name.length > 120) {
      name = name.slice(0, 120);
    }

    const deviceInfo = await Device.getInfo();
    const androidCase = deviceInfo.platform === 'android';

    // Saving the file using the Filesystem plugin
    // Note that for Android, we use the Cache directory as ExternalStorage isn't writable
    // On iOS, we use the ExternalStorage directory - Documents directory
    const savedFile = await Filesystem.writeFile({
      path: name + '.' + ext,
      data: base64Data,
      directory: androidCase ? Directory.External : Directory.ExternalStorage,
    });
    const downloadedFile = await Mediastore.saveToDownloads({
      filename: name + '.' + ext,
      path:
        '/storage/emulated/0/Android/media/my_app/files/' +
        name +
        '.' +
        ext,
    });
    // TODO - Remove this console.log
    console.log('---Saved File---', JSON.stringify(savedFile));
    console.log('---Downloaded File---', JSON.stringify(downloadedFile));

    const fileModel: FilePickModel = {
      internal: internal,
      name: name,
      path: savedFile.uri,
      mimeType: mimeType,
      size: file.size,
      payload: '',
      status: FileStatusEnum.Success,
      media: undefined,
    };

    files.value.push(fileModel);

    const fileStore = useFileStore();
    fileStore.add({
      mimeType: mimeType,
      path: savedFile.uri,
      name: name,
      size: file.size,
    } as FileCacheModel);

    return savedFile.uri;
  };

如果有人遇到类似的问题或对如何正确将文件保存到 Android 存储有任何见解,我将非常感谢您的帮助。您的专业知识将有很大帮助!

Mediastore.saveTo下载

    public void saveToDownloads(PluginCall call) {

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
            call.reject("method requires API 29+");
            return;
        }

        String fileName = call.getString("filename");
        String path = call.getString("path");
        if (path == null) {
            call.reject("path is required");
            return;
        } else if (path.startsWith("file:///")) {
            path = path.substring(8);
        }

        String uri;
        try {
            uri = this.implementation.saveToDownloads(this.getActivity().getApplicationContext(), fileName, path);
        } catch (Exception e) {
            call.reject(e.getMessage());
            return;
        }

        JSObject ret = new JSObject();
        ret.put("uri", uri);
        call.resolve(ret);
    }

Filesystem.write文件

    public void writeFile(PluginCall call) {
        String path = call.getString("path");
        String data = call.getString("data");
        Boolean recursive = call.getBoolean("recursive", false);

        if (path == null) {
            Logger.error(getLogTag(), "No path or filename retrieved from call", null);
            call.reject("NO_PATH");
            return;
        }

        if (data == null) {
            Logger.error(getLogTag(), "No data retrieved from call", null);
            call.reject("NO_DATA");
            return;
        }

        String directory = getDirectoryParameter(call);
        if (directory != null) {
            if (isPublicDirectory(directory) && !isStoragePermissionGranted()) {
                requestAllPermissions(call, "permissionCallback");
            } else {
                // create directory because it might not exist
                File androidDir = implementation.getDirectory(directory);
                if (androidDir != null) {
                    if (androidDir.exists() || androidDir.mkdirs()) {
                        // path might include directories as well
                        File fileObject = new File(androidDir, path);
                        if (fileObject.getParentFile().exists() || (recursive && fileObject.getParentFile().mkdirs())) {
                            saveFile(call, fileObject, data);
                        } else {
                            call.reject("Parent folder doesn't exist");
                        }
                    } else {
                        Logger.error(getLogTag(), "Not able to create '" + directory + "'!", null);
                        call.reject("NOT_CREATED_DIR");
                    }
                } else {
                    Logger.error(getLogTag(), "Directory ID '" + directory + "' is not supported by plugin", null);
                    call.reject("INVALID_DIR");
                }
            }
        } else {
            // check file:// or no scheme uris
            Uri u = Uri.parse(path);
            if (u.getScheme() == null || u.getScheme().equals("file")) {
                File fileObject = new File(u.getPath());
                // do not know where the file is being store so checking the permission to be secure
                // TODO to prevent permission checking we need a property from the call
                if (!isStoragePermissionGranted()) {
                    requestAllPermissions(call, "permissionCallback");
                } else {
                    if (
                        fileObject.getParentFile() == null ||
                        fileObject.getParentFile().exists() ||
                        (recursive && fileObject.getParentFile().mkdirs())
                    ) {
                        saveFile(call, fileObject, data);
                    } else {
                        call.reject("Parent folder doesn't exist");
                    }
                }
            } else {
                call.reject(u.getScheme() + " scheme not supported");
            }
        }
    }

    private void saveFile(PluginCall call, File file, String data) {
        String encoding = call.getString("encoding");
        boolean append = call.getBoolean("append", false);

        Charset charset = implementation.getEncoding(encoding);
        if (encoding != null && charset == null) {
            call.reject("Unsupported encoding provided: " + encoding);
            return;
        }

        try {
            implementation.saveFile(file, data, charset, append);
            // update mediaStore index only if file was written to external storage
            if (isPublicDirectory(getDirectoryParameter(call))) {
                MediaScannerConnection.scanFile(getContext(), new String[] { file.getAbsolutePath() }, null, null);
            }
            Logger.debug(getLogTag(), "File '" + file.getAbsolutePath() + "' saved!");
            JSObject result = new JSObject();
            result.put("uri", Uri.fromFile(file).toString());
            call.resolve(result);
        } catch (IOException ex) {
            Logger.error(
                getLogTag(),
                "Creating file '" + file.getPath() + "' with charset '" + charset + "' failed. Error: " + ex.getMessage(),
                ex
            );
            call.reject("FILE_NOTCREATED");
        } catch (IllegalArgumentException ex) {
            call.reject("The supplied data is not valid base64 content.");
        }
    }
文件 存储 android-permissions 电容器

评论

0赞 0verlord 10/26/2023
谢谢你,blackapps!我已经用其他信息更新了我的问题。如果我错过了什么,应该补充什么,请告诉我。
0赞 0verlord 10/26/2023
二手设备的Android版本 - 13
0赞 blackapps 10/26/2023
尝试在外部存储的“下载”或“文档”目录中创建文件。对于 Android 13+ 设备,您不需要任何权限即可执行此操作。
0赞 0verlord 10/26/2023
谢谢你的帮助。你是说这样?const downloadedFile = await Mediastore.saveToDownloads({ filename: , path: , });${name}.${ext}/Downloads/${name}.${ext}
0赞 blackapps 10/26/2023
对不起,我们看不到那条路。你为什么不告诉完整的路径?不要使用媒体存储,而是使用经典文件工具。

答: 暂无答案