你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

Android开发笔记(一百七十七)借助FileProvider安装应用

2021/11/2 1:11:17

除了发送彩信需要文件提供器,安装应用也需要FileProvider。不单单彩信的附件图片能到媒体库中查询,应用的APK安装包也可在媒体库找到。查找安装包依然借助于内容解析器,具体的实现过程和查询图片类似,比如事先声明如下的对象变量:

private List<ApkInfo> mApkList = new ArrayList<ApkInfo>(); // 安装包列表
private Uri mFilesUri = MediaStore.Files.getContentUri("external"); // 存储卡的Uri
private String[] mFilesColumn = new String[]{ // 媒体库的字段名称数组
        MediaStore.Files.FileColumns._ID, // 编号
        MediaStore.Files.FileColumns.TITLE, // 标题
        MediaStore.Files.FileColumns.SIZE, // 文件大小
        MediaStore.Files.FileColumns.DATA, // 文件路径
        MediaStore.Files.FileColumns.MIME_TYPE}; // 媒体类型

再如通过内容解析器到媒体库查找安装包列表,具体的加载代码示例如下:

// 加载安装包列表
private void loadApkList() {
    mApkList.clear(); // 清空安装包列表
    // 查找存储卡上所有的apk文件,其中mime_type指定了APK的文件类型,或者判断文件路径是否以.apk结尾
    Cursor cursor = getContentResolver().query(mFilesUri, mFilesColumn,
            "mime_type='application/vnd.android.package-archive' or _data like '%.apk'", null, null);
    if (cursor != null) {
        // 下面遍历结果集,并逐个添加到安装包列表。简单起见只挑选前十个文件
        for (int i=0; i<10 && cursor.moveToNext(); i++) {
            ApkInfo apk = new ApkInfo(); // 创建一个安装包信息对象
            apk.setId(cursor.getLong(0)); // 设置安装包编号
            apk.setName(cursor.getString(1)); // 设置安装包名称
            apk.setSize(cursor.getLong(2)); // 设置安装包的文件大小
            apk.setPath(cursor.getString(3)); // 设置安装包的文件路径
            mApkList.add(apk); // 添加至安装包列表
        }
        cursor.close(); // 关闭数据库游标
    }
}

找到安装包之后,通常还要获取它的包名、版本名称、版本号等信息,此时可调用应用包管理器的getPackageArchiveInfo方法,从安装包文件中提取PackageInfo包信息。包信息对象的packageName属性值为应用包名,versionName属性值为版本名称,versionCode属性值为版本号。下面是利用弹窗展示包信息的代码例子:

// 显示安装apk的提示对话框
private void showAlert(final ApkInfo apkInfo) {
    PackageManager pm = getPackageManager(); // 获取应用包管理器
    // 获取apk文件的包信息
    PackageInfo pi = pm.getPackageArchiveInfo(apkInfo.getPath(), PackageManager.GET_ACTIVITIES);
    if (pi != null) { // 能找到包信息
        String desc = String.format("应用包名:%s\n版本名称:%s\n版本编码:%s\n文件路径:%s",
                pi.packageName, pi.versionName, pi.versionCode, apkInfo.getPath());
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("是否安装该应用?"); // 设置提醒对话框的标题
        builder.setMessage(desc); // 设置提醒对话框的消息内容
        builder.setPositiveButton("是", new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int which) {
                installApk(apkInfo.getPath()); // 安装指定路径的APK
            }
        });
        builder.setNegativeButton("否", null);
        builder.create().show(); // 显示提醒对话框
    } else { // 未找到包信息
        ToastUtil.show(this, "该安装包已经损坏,请选择其他安装包");
    }
}

有了安装包的文件路径,就能打开系统的安装程序执行安装操作了,此时一样要把安装包的Uri对象传过去。应用安装的详细调用代码如下所示:

// 安装指定路径的APK
private void installApk(String path) {
    Uri uri = Uri.parse(path); // 根据指定路径创建一个Uri对象
    // 兼容Android7.0,把访问文件的Uri方式改为FileProvider
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        // 通过FileProvider获得安装包文件的Uri访问方式
        uri = FileProvider.getUriForFile(this,
                BuildConfig.APPLICATION_ID + ".fileProvider", new File(path));
    }
    Intent intent = new Intent(Intent.ACTION_VIEW); // 创建一个浏览动作的意图
    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 另外开启新页面
    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 需要读权限
    // 设置Uri的数据类型为APK文件
    intent.setDataAndType(uri, "application/vnd.android.package-archive");
    startActivity(intent); // 启动系统自带的应用安装程序
}

注意,Android8.0开始安装应用需要申请权限REQUEST_INSTALL_PACKAGES,于是打开AndroidManifest.xml,补充下面的权限申请配置:

<!-- 安装应用请求,Android8.0需要 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

这下大功告成,编译运行App,打开测试页面自动加载安装包列表的界面如下图所示。

点击某项安装包,弹出如下图所示的确认对话框。

 

点击确认对话框的“是”按钮,便跳到了如下图所示的应用安装页面,剩下的安装操作就交给系统了。

 


点此查看Android开发笔记的完整目录