使用外部存储写入 Android 11 上多个应用程序使用的文件 [英] Use external storage to write file used by multiple applications on Android 11

查看:112
本文介绍了使用外部存储写入 Android 11 上多个应用程序使用的文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我以前通过使用 WRITE_EXTERNAL_STORAGE 使用外部存储来存储我想在我的应用程序之间共享的特定数据(没有任何内容提供程序主机").

I previously used external storage to store specific data that I would like to share between my applications (without having any contentprovider "host"), by using WRITE_EXTERNAL_STORAGE.

不是媒体文件,它更像是其中的编码字符串.

Not a media file, it is more like an encoded string in it.

如果不请求 MANAGE_EXTERNAL_STORAGE,在 Android 11 上似乎不再可能.

It does not seem to be possible anymore on Android 11, without requesting MANAGE_EXTERNAL_STORAGE.

但 Google 不会向所有应用程序授予此权限,并且需要填写表单,就像每个受限权限"一样.(READ_CALL_LOG、READ_SMS、ACCESS_FINE_LOCATION 等...)请参阅 support.google.com/googleplay/android-developer/answer/9888170

But this permission will not be granted by Google to all applications, and will require to fill a form, like everry "restricted permissions" (READ_CALL_LOG, READ_SMS, ACCESS_FINE_LOCATION, etc...) See support.google.com/googleplay/android-developer/answer/9888170

示例:通过有XX个应用程序,每个应用程序都可以是第一个写入文件的(基本上是用户使用的第一个应用程序),其他3个应用程序在启动时都会读取这个文件.

Exemple : By having XX applications, each one could be the first one to write a file (the first app used by the user basically), and the 3 other applications would read this file when started.

知道如何在 Android 11 上实现这一点吗?BlobManager 似乎很合适,但文档很糟糕(我尝试过但没有成功:new BlobStoreManager在 Android 11 上读写)

Any idea on how this can be achieved on Android 11? BlobManager seems to be appropriate but documentation is terrible (I tried it without success: new BlobStoreManager read write on Android 11)

private void writeFile(String data) {
    try {
        File f = new File(Environment.getExternalStorageDirectory(), FOLDER_NAME);
        if (!f.exists()) {
            boolean mkdirs = f.mkdirs();
            if (!mkdirs) {
                return;
            }
        }

        File file = new File(f, FILE_NAME);
        FileOutputStream outputStream = new FileOutputStream(file);

        String encoded = Base64.encodeToString(data.getBytes(), Base64.DEFAULT);

        outputStream.write(encoded.getBytes());
        outputStream.close();
    } catch (IOException e) {
        Logger.e(TAG, "writeFile: IOException", e);
    } catch (Exception e) {
        Logger.e(TAG, "writeFile: Basic exception", e);
    }
}

private String readFile() {
    String data;
    try {
        File file = new File(Environment.getExternalStorageDirectory(), FOLDER_NAME + "/" + FILE_NAME);
        if (!file.exists()) {
            return "";
        }
        InputStream is = new FileInputStream(file);

        int size = is.available();
        byte[] buffer = new byte[size];
        is.read(buffer);
        is.close();

        String text = new String(buffer, Charset.forName("UTF-8"));
        data = new String(Base64.decode(text, Base64.DEFAULT));
        Logger.d(TAG, "readFile: decoded = " + data);
    } catch (IOException e) {
        Logger.e(TAG, "readFile: IOException", e);
        return "";
    } catch (IllegalArgumentException e) {
        Logger.e(TAG, "readFile: Illegal Base64 import preset", e);
        return "";
    } catch (Exception e) {
        Logger.e(TAG, "readFile: Basic exception", e);
        return "";
    }
    return data;
}

我尝试了其他一些解决方案:

I tried some others solutions:

外部公共存储方式

应用程序A"可以写,然后读文件.但是另一个应用程序B"无法读取A"写入的文件

An application "A" can write, and then read the file. But an other application "B" can not read the file written by "A"

我只收到访问错误:NotificationHelper - 读取文件:IOExceptionjava.io.FileNotFoundException:/storage/emulated/0/Download/myfolder/settings.bin:打开失败:EACCES(权限被拒绝)在 libcore.io.IoBridge.open(IoBridge.java:492)在 java.io.FileInputStream.(FileInputStream.java:160)

I only get an access error: NotificationHelper - readFile: IOException java.io.FileNotFoundException: /storage/emulated/0/Download/myfolder/settings.bin: open failed: EACCES (Permission denied) at libcore.io.IoBridge.open(IoBridge.java:492) at java.io.FileInputStream.(FileInputStream.java:160)

file = new File (Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), FOLDER_NAME + "/" + FILE_NAME);

媒体商店方式

但仅使用一个应用程序,我就有问题:该应用程序无法覆盖之前编写的文件,它会创建多个实例my_file"、myfile(1)、..."我在尝试阅读时出错:

But just with one app, I have issues: The app can not override a file writtend earlier, it creates multiple instance "my_file", "myfile(1), ..." And I have error when trying to read it:

java.io.FileNotFoundException:打开失败:ENOENT(没有那个文件或目录)在 android.database.DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(DatabaseUtils.java:151)在 android.content.ContentProviderProxy.openTypedAssetFile(ContentProviderNative.java:781)在 android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:1986)在 android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:1801)在 android.content.ContentResolver.openInputStream(ContentResolver.java:1478)在 fr.gg.frameworkmobile.utils.NotificationHelper.readFile(NotificationHelper.java:388)

java.io.FileNotFoundException: open failed: ENOENT (No such file or directory) at android.database.DatabaseUtils.readExceptionWithFileNotFoundExceptionFromParcel(DatabaseUtils.java:151) at android.content.ContentProviderProxy.openTypedAssetFile(ContentProviderNative.java:781) at android.content.ContentResolver.openTypedAssetFileDescriptor(ContentResolver.java:1986) at android.content.ContentResolver.openAssetFileDescriptor(ContentResolver.java:1801) at android.content.ContentResolver.openInputStream(ContentResolver.java:1478) at fr.gg.frameworkmobile.utils.NotificationHelper.readFile(NotificationHelper.java:388)

private void writeFile(String data) {
        String outputFilename = "my_file";
        String outputDirectory = "my_sub_directory"; // The folder within the Downloads folder, because we use `DIRECTORY_DOWNLOADS`

        ContentResolver resolver = AbstractMobileApplication.getInstance().getApplicationContext().getContentResolver();
        ContentValues values = new ContentValues();
        // save to a folder
        values.put(MediaStore.Files.FileColumns.DISPLAY_NAME, outputFilename);
        values.put(MediaStore.Files.FileColumns.MIME_TYPE, "application/my-custom-type");
        values.put(MediaStore.Files.FileColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/" + outputDirectory);
        values.put(MediaStore.Files.FileColumns.IS_PENDING, 1);

        Uri uri = resolver.insert(MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), values);
        // You can use this outputStream to write whatever file you want:
        OutputStream outputStream = null;
        Log.d(TAG, "writeFile: >>>>>>>>" + uri.getPath());
        try {
            outputStream = resolver.openOutputStream(uri);

            String encoded = Base64.encodeToString(data.getBytes(), Base64.DEFAULT);

            outputStream.write(encoded.getBytes());
            outputStream.close();

            values.clear();
            values.put(MediaStore.Files.FileColumns.IS_PENDING, 0);
            resolver.update(uri, values, null, null);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
}

private String readFile() {
    String data;
        String outputFilename = "my_file";
        String outputDirectory = "my_sub_directory"; // The folder within the Downloads folder, because we use `DIRECTORY_DOWNLOADS`

        ContentResolver resolver = AbstractMobileApplication.getInstance().getApplicationContext().getContentResolver();
        ContentValues values = new ContentValues();
        // save to a folder
        values.put(MediaStore.Files.FileColumns.DISPLAY_NAME, outputFilename);
        values.put(MediaStore.Files.FileColumns.MIME_TYPE, "application/my-custom-type");
        values.put(MediaStore.Files.FileColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/" + outputDirectory);

        Uri uri = resolver.insert(MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL), values);
        // You can use this outputStream to write whatever file you want:
        Log.d(TAG, "readFile: >>>>>>>>" + uri.getPath());
        try {
            InputStream is = resolver.openInputStream(uri);

            int size = is.available();
            byte[] buffer = new byte[size];
            is.read(buffer);
            is.close();

            String text = new String(buffer, Charset.forName("UTF-8"));
            data = new String(Base64.decode(text, Base64.DEFAULT));
            Logger.d(TAG, "readFile: decoded = " + data);

        } catch (FileNotFoundException e) {
            data = "";
            e.printStackTrace();
        } catch (IOException e) {
            data = "";
            e.printStackTrace();
        }
    return data;
}

推荐答案

Content Providers 也不是解决方案,因为没有一个应用程序是主机"

Content Providers is not a solution either because none of the apps is a "host"

他们都是主持人.您将需要维护 N 个数据副本,每个应用一个副本,并在它们之间使用某种协调机制来处理对该数据的修改.如果多个应用程序可能会修改旧解决方案中的公共文件,则您已经需要一种协调机制.

They all are the host. You will need to maintain N copies of the data, one per app, with some sort of coordination mechanism between them for handling modifications to that data. You already needed a coordination mechanism, if multiple of the apps might modify your common file in your old solution.

例如,如果数据不经常更改:

For example, if the data changes infrequently:

  • 您套件中的第一个应用程序在首次运行时会发送一个安全的我可以获取数据的副本吗?";广播,没有人响应,因为它是您套件中的第一个应用

  • The first app in your suite, when first run, sends a secured "can I get a copy of the data?" broadcast, which nobody responds to, since it is the first app in your suite

第一个应用设置数据

随后的应用程序,在第一次运行时,发送相同的我可以获取数据的副本吗?";广播

Subsequent apps, when first run, send a the same "can I get a copy of the data?" broadcast

每个应用程序都有一个广播接收器,如果它们有数据,就会发送这里是数据的副本";广播回复,它要么有数据本身(如果数据很小),要么有一个 Uri 到一个可以提供数据的 ContentProvider.理想情况下,数据中包含时间戳或其他一些版本信息.

Each app has a receiver for the broadcast, and if they have the data, sends a "here is a copy of the data" broadcast in reply, which either has the data itself (if it is small) or has a Uri to a ContentProvider that can supply the data. Ideally, the data has a timestamp or some other versioning information in it.

每个应用程序,如果它修改了数据,就会发送这里是数据的副本";广播.

Each app, if it modifies the data, sends that "here is a copy of the data" broadcast.

每个应用程序都有一个接收器,用于接收这里是数据的副本".如果数据比他们拥有的数据新,则广播并使用它来抓取数据(如果他们还没有数据,则第一次抓取数据).

Each app has a receiver for the "here is a copy of the data" broadcast and uses that to grab the data if it is newer than what they have (or grabs it for the first time if they do not already have the data).

这很复杂,如果两个应用同时尝试修改数据,就会有发生冲突的风险.

This is complex, with risks of collisions if two apps try modifying the data around the same time.

您可以考虑一个选举协议,并将一个应用作为所有者"数据,其他应用程序只有备份副本,如果当前所有者应用程序被卸载,则进行新的选举.如果处理得当,这可以降低碰撞风险,但代价是更加复杂.

You could consider an election protocol and have a single app be the "owner" of the data, with the other apps just having backup copies, and with a new election if the current owner app is uninstalled. Done properly, this could reduce the risks of collisions, at the cost of even more complexity.

简单的解决方案是允许用户通过ACTION_CREATE_DOCUMENT(对于您的第一个应用程序)和ACTION_OPEN_DOCUMENT(对于后续应用程序)来指定此共享内容所在的位置.但是,您拒绝了这一点(并且它要求对用户来说是不可见的":没有文件选择器").我的建议是让你放宽这个要求.而且,您仍然需要一些协调机制,如果多个应用程序可能会修改您的公共内容,就像您最初采用的公共文件方法一样.

The simple solution is to allow the user to specify where this shared content resides, via ACTION_CREATE_DOCUMENT (for your first app) and ACTION_OPEN_DOCUMENT (for subsequent apps). However, you rejected this ("And it requires to be "invisible" for the users: no file picker"). My recommendation would be for you to relax this requirement. And, you still need some coordination mechanism, if multiple of the apps might modify your common content, just as you did with the common file approach you took originally.

而且,您始终可以考虑取消套件,将功能合并到一个应用程序中.或者,采用更多的主机和插件"套件的模型,这样每个插件应用程序不需要独立访问数据.

And, you could always consider eliminating the suite, merging the functionality into a single app. Or, adopt more of a "host-and-plugins" model for the suite, such that each plugin app does not need independent access to the data.

这篇关于使用外部存储写入 Android 11 上多个应用程序使用的文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆