4

背景

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

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

问题

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

其中之一称为Scoped Storage,它在访问设备拥有的所有文件时会破坏存储权限。它允许您处理媒体文件,或使用非常受限的存储访问框架 ( SAF ),它不允许应用程序使用 File API 和文件路径访问和使用文件。

当 Android Q Beta 2 发布时,它破坏了很多应用程序,包括谷歌。原因是它默认打开,影响所有应用程序,无论它们是否针对 Android Q。

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

然而,在 Q beta 3 上,情况发生了一些变化,因此以 Q 为目标的应用程序将具有范围存储,并且有一个标志可以禁用它,并且仍然像往常一样使用正常的存储权限和文件 API。可悲的是,旗帜只是暂时的(在这里阅读),所以它推迟了不可避免的事情。

我试过的

我已经尝试过并发现了接下来的事情:

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

  2. 使用 SAF,我可以找到 APK 文件,并通过一些解决方法来找到它的真实路径(链接在这里),我注意到 File API 可以告诉我文件确实存在,但它无法获得它的大小,并且框架未能使用其路径使用getPackageArchiveInfo. 在这里写了这个

  3. 我试图对文件建立一个符号链接(链接在这里),然后从符号链接中读取。它没有帮助。

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

问题

  1. 一般来说,有没有办法在使用 SAF 时仍然使用 File API?我不是在谈论根解决方案或只是将文件复制到其他地方。我说的是更可靠的解决方案。

  2. 对于APK解析的情况,有没有办法解决框架只提供文件路径作为参数的问题?也许有任何解决方法或使用 InputStream 的方法?

4

2 回答 2

1

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

首先,获取您需要处理的文件的 Uri(以字符串形式,类似于“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
    }          
于 2019-10-12T12:48:57.083 回答
0

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

            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
于 2019-10-15T15:29:33.313 回答