当我只能处理文件或文件路径时,如何处理SAF?

时间:2019-05-18 13:49:48

标签: android file storage-access-framework androidq scoped-storage

背景

直到Android Q,如果我们想获取有关APK文件的信息,我们可以使用WRITE_EXTERNAL_STORAGEREAD_EXTERNAL_STORAGE来访问存储,然后使用PackageManager.getPackageArchiveInfo函数文件路径。

存在类似情况,例如在压缩文件上使用ZipFile class,可能还有无数框架API和第三方库。

问题

Google最近宣布了对Android Q的大量限制。

其中之一称为Scoped Storage,当访问设备上的所有文件时,该存储权限将被破坏。它使您可以处理媒体文件,也可以使用受限制的Storage-Access-Framework(SAF),后者不允许应用程序使用File API和文件路径访问和使用文件。

发布Android Q Beta 2时,它因此破坏了很多应用程序,包括Google。原因是默认情况下已将其打开,从而影响所有应用程序,无论它们是否针对Android Q。

原因是许多应用程序,SDK和Android框架本身-都经常使用File API。在许多情况下,它们也不支持InputStream或SAF相关的解决方案。我编写的APK解析示例(PackageManager.getPackageArchiveInfo)就是一个示例。

但是,在Q beta 3上,情况有所变化,因此目标Q的应用将具有作用域存储,并且有一个标志将其禁用,并且仍照常使用常规存储权限和File API。遗憾的是,该标志只是临时的(读here),因此它延迟了不可避免的时间。

我尝试过的

我已经尝试并发现了以下内容:

  1. 使用存储权限确实并没有让我读取不是媒体文件的任何文件(我想找到APK文件)。好像文件不存在。

  2. 使用SAF,我可以找到APK文件,并通过一些变通办法来找到其真实路径(链接here),我注意到File API可以告诉我该文件确实存在,但它无法达到其大小,并且框架无法通过getPackageArchiveInfo使用其路径。写了这个here

  3. 我试图建立到文件的符号链接(链接here),然后从符号链接中读取。它没有帮助。

  4. 对于解析APK文件的情况,我尝试搜索其他解决方案。我发现有2个使用File类(herehere)处理APK的github存储库,还有一个使用InputStream替代(here)的github存储库。遗憾的是,使用InputStream的那个版本很旧,缺少各种功能(例如获取应用程序的名称和图标),并且不会很快更新。此外,拥有一个库需要维护以跟上未来版本的Android,否则将来可能会出现问题,甚至崩溃。

问题

  1. 通常,使用SAF时是否仍可以使用File API?我不是在谈论根解决方案,也不是在将文件复制到其他地方。我说的是更可靠的解决方案。

  2. 对于APK解析,是否有一种方法可以解决此问题,即框架仅提供文件路径作为参数?有什么解决方法或使用InputStream的方法吗?

2 个答案:

答案 0 :(得分:1)

当我只能处理文件或文件路径时,如何处理SAF?即使您只能将Java File对象或路径字符串发送到无法修改的库函数,也有可能:

首先,获取需要处理的文件的Uri(以String形式,类似于“ content:// ...”),然后:

    try {
        ParcelFileDescriptor parcelFileDescriptor =
                getContentResolver().openFileDescriptor(uri, "r"); // may get FileNotFoundException here
        // Obtain file descriptor:
        int fd = parcelFileDescriptor.getFd(); // or detachFd() if we want to close file in native code
        String linkFileName = "/proc/self/fd/" + fd;
        // Call library function with path/file string:
        someFunc(/*file name*/ linkFileName);
        // or with File parameter
        otherFunc(new File(linkFileName));
        // Finally, if you did not call detachFd() to obtain the file descriptor, call:
        parcelFileDescriptor.close();
        // Otherwise your library function should close file/stream...
    } catch (FileNotFoundException fnf) {
        fnf.printStackTrace(); // or whatever
    }          

答案 1 :(得分:0)

发布另一个答案只是为了有更多空间,让我插入代码片段。给定我之前回答中解释的文件描述符,我尝试使用@androiddeveloper在原始问题中提到的net.dongliu:apk-parser软件包,如下所示(Lt.d是我使用Log.d(SOME_TAG,字符串的简写。 ..)):

            String linkFileName = "/proc/self/fd/" + fd;
            try (ApkFile apkFile = new ApkFile(new File(linkFileName))) {
                ApkMeta apkMeta = apkFile.getApkMeta();
                Lt.d("ApkFile Label: ", apkMeta.getLabel());
                Lt.d("ApkFile pkg name: ", apkMeta.getPackageName());
                Lt.d("ApkFile version code: ", apkMeta.getVersionCode());
                String iconStr = apkMeta.getIcon();
                Lt.d("ApkFile icon str: ", iconStr);
                for (UseFeature feature : apkMeta.getUsesFeatures()) {
                    Lt.d(feature.getName());
                }
            }
            catch (Exception ex) {
                Lt.e("Exception in ApkFile code: ", ex);
                ex.printStackTrace();
            }
        }

它为我提供了正确的应用标签,对于图标它仅给我一个资源目录的字符串(例如“ res / drawable-mdpi-v4 / fex.png”),因此原始的ZIP阅读功能必须用于读取实际的图标位。具体来说,我正在测试ES File Explorer Pro APK(购买了此产品,并将APK保存为自己的备份,得到以下输出:

 I/StorageTest: ApkFile Label: ES File Explorer Pro
 I/StorageTest: ApkFile pkg name: com.estrongs.android.pop.pro
 I/StorageTest: ApkFile version code: 1010
 I/StorageTest: ApkFile icon str: res/drawable-mdpi-v4/fex.png
 I/StorageTest: android.hardware.bluetooth
 I/StorageTest: android.hardware.touchscreen
 I/StorageTest: android.hardware.wifi
 I/StorageTest: android.software.leanback
 I/StorageTest: android.hardware.screen.portrait