如何打开适用于所有 Android 版本的 APK 文件 [英] How to open an APK file for all Android versions

查看:65
本文介绍了如何打开适用于所有 Android 版本的 APK 文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

So far, there was an easy way to install an APK file, using this intent:

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

但是,如果您的应用面向 Android API 24 及更高版本 (Nougat - 7.0),并且您在其上或更新版本上运行此代码,则会出现异常,如图所示 此处,例如:

But, if your app targets Android API 24 and above (Nougat - 7.0) , and you run this code on it or newer, you will get an exception, as shown here , for example:

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

问题

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

The problem

So I did what I was told: use the support library's FileProvider class, as such:

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

清单:

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

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>

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

But, now it works only on Android Nougat. On Android 5.0, it throws an exception: ActivityNotFoundException.

我可以添加对 Android 操作系统版本的检查,并使用任一方法,但正如我所读到的,应该使用一种方法:FileProvider.

I can just add a check for the version of Android OS, and use either methods, but as I've read, there should be a single method to use: FileProvider.

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

So, what I tried is to use my own ContentProvider that acts as FileProvider, but I got the same exception as of the support library's 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);

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
    }

}

清单

    <provider
        android:name=".utils.apps_utils.OpenFileProvider"
        android:authorities="open_file_provider"
        android:exported="true"
        android:grantUriPermissions="true"
        android:multiprocess="true"/>

问题

  1. 为什么会出现这种情况?

  1. Why does it occur?

我创建的自定义提供程序有什么问题吗?需要国旗吗?URI 创建正常吗?我应该将当前应用的包名称添加到其中吗?

Is there anything wrong with the custom provider I've created? Is the flag needed? Is the URI creation ok ? Should I add the current app's package name to it?

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

Should I just add a check if it's Android API 24 and above, and if so, use the provider, and if not, use a normal Uri.fromFile call ? If I use this, the support library actually loses its purpose, because it will be used for newer Android versions...

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

Will the support library FileProvider be enough for all use cases (given that I do have external storage permission, of course) ?

推荐答案

我可以添加对 Android 操作系统版本的检查,并使用任一方法,但正如我所读到的,应该使用一种方法:FileProvider.

I can just add a check for the version of Android OS, and use either methods, but as I've read, there should be a single method to use: FileProvider.

嗯,俗话说,探戈需要两个人".

Well, as the saying goes, "it takes two to tango".

要使用任何特定方案(filecontenthttp 等),您不仅需要在该方案,但接收方需要能够支持接受该方案中的数据.

To use any particular scheme (file, content, http, etc.), not only do you have to provide the data in that scheme, but the recipient needs to be able to support accepting the data in that scheme.

在包安装程序的情况下,对 content 作为方案的支持仅在 Android 7.0 中添加(然后,也许只是因为 我指出了这个问题).

In the case of the package installer, support for content as a scheme was only added in Android 7.0 (and then, perhaps only because I pointed out the problem).

为什么会发生?

因为 Google(请参阅this这个).

Because Google (see this and this).

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

Is there anything wrong with the custom provider I've created?

可能不会.

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

Should I just add a check if it's Android API 24 and above, and if so, use the provider, and if not, use a normal Uri.fromFile call ?

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

Yes. Or, if you prefer, catch the ActivityNotFoundException and react to that, or use PackageManager and resolveActivity() to see ahead of time if a given Intent (e.g., one with a content Uri) will work properly.

如果我使用这个,支持库实际上失去了它的用途,因为它将用于更新的Android版本

If I use this, the support library actually loses its purpose, because it will be used for newer Android versions

支持库"与较新与较旧的 Android 版本关系不大.在各种 Android 支持工件中,只有一小部分类是向后移植或兼容性垫片.大量的它—FileProviderViewPagerConstraintLayout 等 —只是 Google 希望提供和支持但希望在固件之外提供它们的类.

The "support library" has little to do with newer-vs.-older Android versions. Only a small percentage of the classes across the various Android Support artifacts are backports or compatibility shims. Vast quantities of it — FileProvider, ViewPager, ConstraintLayout, etc. — are simply classes that Google wanted to provide and support but wanted to make them available outside of the firmware.

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

Will the support library FileProvider be enough for all use cases

仅适用于 Android 7.0+.同样,Android 7.0 之前的 Android 软件包安装程序不支持 content 方案.

Only on Android 7.0+. Again, the stock Android package installer does not support content schemes prior to Android 7.0.

这篇关于如何打开适用于所有 Android 版本的 APK 文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆