How to share current app's APK (or of other in

2020-02-13 01:57发布

问题:

Background

In the past, it was easy to share an APK file with any app you wanted, using a simple command:

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

The problem

If your app targets Android API 24 (Android Nougat) or above, the above code will cause a crash, caused by FileUriExposedException (written about it here, and an example solution for opening an APK file can be found here) .

This actually worked for me fine, using below code:

    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);

However, the problem is that I also wish to be able to share the current app's APK (and also other installed apps).

For getting the path of the current app's APK, we use this:

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

This APK is accessible to all apps without the need of any permission, and so does the APK of every installed app.

But when I use this file with the above code for sharing using the FileProvider, I get this exception:

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

The same goes for when I use a symlinked file to the APK, as such:

        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;
}

What I've tried

I tried to configure the provider_paths.xml file with various combinations of what I thought would help, such as any of those :

<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="" />

I also tried to disable the strictMode that's associated with this mechanism:

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

The question

How can I share any APK file, from every possible path that's accessible to my app, including using symlinked files ?

回答1:

But when I use this file with the above code for sharing using the FileProvider, I get this exception

That is because packageInfo.applicationInfo.publicSourceDir is not underneath one of the possible roots for FileProvider.

The same goes for when I use a symlinked file to the APK

Perhaps symlinks don't work here. One of your <files-path> elements — or one where you leave path off entirely — can serve ordinary files out of getFilesDir().

How can I share any APK file, from every possible path that's accessible to my app, including using symlinked files ?

Since there is no official support for symlinks, I can't help you there.

Otherwise, you have three main options:

  1. Make a copy of the APK file someplace that FileProvider likes

  2. Try using my StreamProvider with some custom extensions to teach it to serve from packageInfo.applicationInfo.publicSourceDir

  3. Write your own ContentProvider that serves from packageInfo.applicationInfo.publicSourceDir



回答2:

As I recall, the apk file lies in /data/app/your.package.name-1(or -2)/base.apk since SDK 21 (or 23? Correct me if I am wrong.). And /data/app/your.package.name.apk before that. So try this:

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

My answer here is not applicable in your case. I explained that in the comment section there.

PS: As @CommonsWare said, this feature is undocumented. There is a chance that it would be removed in the future. So there is risk using this.