如何为所有Android版本打开APK文件

and*_*per 5 android android-intent apk android-fileprovider android-7.0-nougat

背景

到目前为止,使用此意图有一种简单的方法来安装APK文件:

    final Intent intent=new Intent(Intent.ACTION_VIEW)
            .setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive");
Run Code Online (Sandbox Code Playgroud)

但是,如果你的应用程序中的Android API 24以上(牛轧糖- 7.0),你就可以或更新运行这段代码,你会得到一个异常,如图所示这里,例如:

android.os.FileUriExposedException: file:///storage/emulated/0/sample.apk exposed beyond app through Intent.getData()
Run Code Online (Sandbox Code Playgroud)

问题

所以我做了我被告知的事情:使用支持库的FileProvider类,如下:

    final Intent intent = new Intent(Intent.ACTION_VIEW)//
            .setDataAndType(android.support.v4.content.FileProvider.getUriForFile(context, 
            context.getPackageName() + ".provider", apkFile),
            "application/vnd.android.package-archive").addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Run Code Online (Sandbox Code Playgroud)

清单:

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"/>
    </provider>
Run Code Online (Sandbox Code Playgroud)

res/xml/provider_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <!--<external-path name="external_files" path="."/>-->
    <external-path
        name="files_root"
        path="Android/data/${applicationId}"/>
    <external-path
        name="external_storage_root"
        path="."/>
</paths>
Run Code Online (Sandbox Code Playgroud)

但是,现在它仅适用于Android Nougat.在Android 5.0上,它抛出一个异常:ActivityNotFoundException.

我试过的

我可以添加一个Android OS版本的检查,并使用任何一种方法,但正如我所读,应该使用一种方法:FileProvider.

所以,我尝试使用我自己的ContentProvider充当FileProvider,但我得到了与支持库的FileProvider相同的异常.

这是我的代码:

    final Intent intent = new Intent(Intent.ACTION_VIEW)
        .setDataAndType(OpenFileProvider.prepareSingleFileProviderFile(apkFilePath),
      "application/vnd.android.package-archive")
      .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Run Code Online (Sandbox Code Playgroud)

OpenFileProvider.java

public class OpenFileProvider extends ContentProvider {
    private static final String FILE_PROVIDER_AUTHORITY = "open_file_provider";
    private static final String[] DEFAULT_PROJECTION = new String[]{MediaColumns.DATA, MediaColumns.DISPLAY_NAME, MediaColumns.SIZE};

    public static Uri prepareSingleFileProviderFile(String filePath) {
        final String encodedFilePath = new String(Base64.encode(filePath.getBytes(), Base64.URL_SAFE));
        final Uri uri = Uri.parse("content://" + FILE_PROVIDER_AUTHORITY + "/" + encodedFilePath);
        return uri;
    }

    @Override
    public boolean onCreate() {
        return true;
    }

    @Override
    public String getType(@NonNull Uri uri) {
        String fileName = getFileName(uri);
        if (fileName == null)
            return null;
        return MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileName);
    }

    @Override
    public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
        final String fileName = getFileName(uri);
        if (fileName == null)
            return null;
        final File file = new File(fileName);
        return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
    }

    @Override
    public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        final String filePath = getFileName(uri);
        if (filePath == null)
            return null;
        final String[] columnNames = (projection == null) ? DEFAULT_PROJECTION : projection;
        final MatrixCursor ret = new MatrixCursor(columnNames);
        final Object[] values = new Object[columnNames.length];
        for (int i = 0, count = columnNames.length; i < count; ++i) {
            String column = columnNames[i];
            switch (column) {
                case MediaColumns.DATA:
                    values[i] = uri.toString();
                    break;
                case MediaColumns.DISPLAY_NAME:
                    values[i] = extractFileName(uri);
                    break;
                case MediaColumns.SIZE:
                    File file = new File(filePath);
                    values[i] = file.length();
                    break;
            }
        }
        ret.addRow(values);
        return ret;
    }

    private static String getFileName(Uri uri) {
        String path = uri.getLastPathSegment();
        return path != null ? new String(Base64.decode(path, Base64.URL_SAFE)) : null;
    }

    private static String extractFileName(Uri uri) {
        String path = getFileName(uri);
        return path;
    }

    @Override
    public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;       // not supported
    }

    @Override
    public int delete(@NonNull Uri uri, String arg1, String[] arg2) {
        return 0;       // not supported
    }

    @Override
    public Uri insert(@NonNull Uri uri, ContentValues values) {
        return null;    // not supported
    }

}
Run Code Online (Sandbox Code Playgroud)

表现

    <provider
        android:name=".utils.apps_utils.OpenFileProvider"
        android:authorities="open_file_provider"
        android:exported="true"
        android:grantUriPermissions="true"
        android:multiprocess="true"/>
Run Code Online (Sandbox Code Playgroud)

问题

  1. 它为什么会发生?

  2. 我创建的自定义提供程序有什么问题吗?需要标志吗?URI创建好吗?我应该将当前应用程序的包名称添加到它吗?

  3. 我是否应该添加一个检查,如果它是Android API 24及更高版本,如果是,请使用提供程序,如果不是,请使用正常的Uri.fromFile调用?如果我使用它,支持库实际上失去了它的目的,因为它将用于更新的Android版本......

  4. 支持库FileProvider是否足以满足所有用例(当然,我有外部存储权限)?

Com*_*are 5

我可以只检查Android OS的版本,然后使用这两种方法,但是正如我所读的,应该使用一种方法:FileProvider。

嗯,俗话说,“探戈需要两个人”。

使用任何特定方案(filecontenthttp等),你不仅需要提供在该计划中的数据,但收件人需要能够支持接受该方案中的数据。

对于软件包安装程序,content仅在Android 7.0中添加了对作为方案的支持(然后,可能只是因为我指出了问题)。

为什么会发生?

由于谷歌(见)。

我创建的自定义提供程序有什么问题吗?

可能不会。

我是否应该仅检查它是否为Android API 24及更高版本,如果是,则使用提供程序,如果不是,则使用普通的Uri.fromFile调用?

是。或者,如果您愿意,可以抓住ActivityNotFoundException并对此做出反应,或者使用PackageManagerresolveActivity()提前查看给定的Intent(例如,带有的content Uri)是否可以正常工作。

如果使用此功能,则支持库实际上会失去其用途,因为它将用于更新的Android版本

“支持库”与较旧的Android版本无关。各种Android支持工件中的类中只有一小部分是反向移植或兼容性垫片。它浩大的数量- ,,FileProvider 等-只是类,谷歌希望提供和支持,但希望他们固件外可用。ViewPagerConstraintLayout

支持库FileProvider是否足以满足所有用例

仅在Android 7.0及更高版本上。同样,现有的Android软件包安装程序不支持contentAndroid 7.0之前的方案。