如何使用新的FileProvider分享当前应用的APK(或其他已安装的应用)?

时间:2016-12-02 22:18:31

标签: android apk android-7.0-nougat android-fileprovider share-intent

背景

过去,使用简单的命令很容易与您想要的任何应用分享APK文件:

startActivity(new Intent(Intent.ACTION_SEND,Uri.fromFile(filePath)).setType("*/*"));

问题

如果您的应用针对的是Android API 24(Android Nougat)或更高版本,则上述代码会导致崩溃,这是由FileUriExposedException(写有关here)引起的,并且可以找到打开APK文件的示例解决方案here)。

这对我来说很合适,使用下面的代码:

    File apkFile = new File(apkFilePathFromSomewhereInExternalStorage);
    Intent intent = new Intent(Intent.ACTION_SEND);
    Uri fileUri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", apkFile);
    intent.setType("text/plain");
    intent.putExtra(Intent.EXTRA_STREAM, fileUri);
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    startActivity(intent);

然而,问题是我还希望能够分享当前应用的APK(以及其他已安装的应用)。

要获取当前应用APK的路径,我们使用:

        final PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
        File apkFile=new File(packageInfo.applicationInfo.publicSourceDir);
        ...

所有应用均可访问此APK,无需任何权限,每个已安装应用的APK也是如此。

但是当我使用上面的代码将此文件用于使用FileProvider进行共享时,我得到了这个例外:

IllegalArgumentException: Failed to find configured root that contains ...

当我使用符号链接文件到APK时也是如此:

        File apkFile=new File(packageInfo.applicationInfo.publicSourceDir);
        final String fileName = "symlink.apk";
        File symLinkFile = new File(getFilesDir(), fileName);
        if (!symLinkFile.exists())
            symLinkPath = symLinkFile.getAbsolutePath();
        createSymLink(symLinkPath, apkFile.getAbsolutePath());
        Uri fileUri= FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", symLinkFile);
        ...

public static boolean createSymLink(String symLinkFilePath, String originalFilePath) {
    try {
        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
            Os.symlink(originalFilePath, symLinkFilePath);
            return true;
        }
        final Class<?> libcore = Class.forName("libcore.io.Libcore");
        final java.lang.reflect.Field fOs = libcore.getDeclaredField("os");
        fOs.setAccessible(true);
        final Object os = fOs.get(null);
        final java.lang.reflect.Method method = os.getClass().getMethod("symlink", String.class, String.class);
        method.invoke(os, originalFilePath, symLinkFilePath);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
    }
    return false;
}

我尝试了什么

我尝试使用我认为有用的各种组合来配置provider_paths.xml文件,例如以下任何一种:

<external-path name="external_files" path="."/>
<external-path path="Android/data/lb.com.myapplication/" name="files_root" />
<external-path path="." name="external_storage_root" />
<files-path name="files" path="." />
<files-path name="files" path="" />

我还试图禁用与此机制相关的strictMode:

    StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
    StrictMode.setVmPolicy(builder.build());

问题

如何从我的应用可访问的每个可能路径共享任何APK文件,包括使用符号链接文件?

2 个答案:

答案 0 :(得分:1)

  

但是当我使用上面的代码将此文件用于使用FileProvider进行共享时,我得到了这个异常

这是因为packageInfo.applicationInfo.publicSourceDir不在FileProvider的可能根源之下。

  

当我使用符号链接文件到APK

时也是如此

也许符号链接不在这里工作。您的<files-path>个元素之一 - 或完全离开path的元素 - 可以提供getFilesDir()之外的普通文件。

  

如何从我的应用可访问的每条可能路径共享任何APK文件,包括使用符号链接文件?

由于没有官方支持符号链接,我无法帮助你。

否则,您有三个主要选项:

  1. FileProvider喜欢的地方制作APK文件的副本

  2. 尝试将my StreamProvider与一些自定义扩展程序结合使用,以教它从packageInfo.applicationInfo.publicSourceDir

  3. 进行投放
  4. ContentProvider

  5. 撰写您自己的packageInfo.applicationInfo.publicSourceDir

答案 1 :(得分:0)

我记得,apk文件位于/data/app/your.package.name-1(or-2)/base.apk,因为SDK 21(或23?如果我错了,请纠正我。)。和/data/app/your.package.name.apk之前。所以试试这个:

<root-path path="data/app/" name="your.name">

我的回答here不适用于您的情况。我在那里的评论部分解释了这一点。

PS:正如@CommonsWare所说,这个功能没有记载。它有可能在将来被删除。所以使用它有风险。