Как поделиться APK текущего приложения (или других установленных приложений), используя новый FileProvider?

Фон

В прошлом было легко поделиться файлом APK с любым приложением, которое вы хотели, используя простую команду:

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

Эта проблема

Если ваше приложение предназначено для Android API 24 (Android Nougat) или выше, приведенный выше код вызовет сбой, вызванный FileUriExposedException (об этом написано здесь, а пример решения для открытия файла APK можно найти здесь) .

Это на самом деле работает для меня нормально, используя следующий код:

    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 ответа

Решение

Но когда я использую этот файл с вышеуказанным кодом для совместного использования с помощью FileProvider, я получаю это исключение

Это потому packageInfo.applicationInfo.publicSourceDir не под одним из возможных корней для FileProvider,

То же самое касается, когда я использую символическую ссылку на APK

Возможно, символические ссылки не работают здесь. Один из ваших <files-path> элементы - или тот, где вы оставляете path полностью - может обслуживать обычные файлы из getFilesDir(),

Как я могу поделиться любым файлом APK из всех возможных путей, которые доступны для моего приложения, в том числе с использованием файлов с символическими ссылками?

Официальная поддержка символических ссылок отсутствует, поэтому я не могу вам помочь.

В противном случае у вас есть три основных варианта:

  1. Сделайте копию файла APK где-нибудь FileProvider нравится

  2. Попробуйте использовать мойStreamProvider с некоторыми пользовательскими расширениями, чтобы научить его служить от packageInfo.applicationInfo.publicSourceDir

  3. Написать свой ContentProvider который служит от packageInfo.applicationInfo.publicSourceDir

Насколько я помню, файл 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">

Мой ответ здесь не применим в вашем случае. Я объяснил это в разделе комментариев там.

PS: как сказал @CommonsWare, эта функция недокументирована. Есть вероятность, что это будет удалено в будущем. Так что есть риск, используя это.

Другие вопросы по тегам