android 由apk文件安装到FileProvider的使用


零、参考资料

本文主要参考上述资料,并结合自身编码实践记录完成。

一、我为何用到FileProvider

1.需求

在项目中,需对apk文件提供外部应用打开方式,实现apk安装。
条件:能获得apk文件的绝对路径filePath。
想法:先获取该apk文件的URI,再构建一个包含该URI的Intent,并将该Intent传递给能安装apk的应用。

2.遇到的问题

已知文件的真实路径filePath,可以创建相对应的File对象,再通过Uri.fromFile()可以获得file://类型的URI,本文称之为普通URI。
在普通URI中,直接包含了文件的本地真实路径,这被认为是不安全的。
并且,控制普通URI的访问权限是通过变更文件所在的文件系统权限来实现的,授予的访问权限是针对所有应用可用的,除非手动更改,该权限会一直有效,显然,这类授权是非常不安全的。
在android7.0以下,可以通过普通URI的方式将apk文件分享给其它应用,实现安装。
从Android7.0(N)及以上开始,严格执行StrictMode模式,对安全做更严格的校验。

至此,不允许在App间使用普通URI(即file://类型)的方式共享文件的访问权限,否则会抛出FileUriExposedException的错误,直接引发crash。

3.解决措施

FileProvider可以通过getUriForFile()为文件生成关联的content://类型的URI,本文称之为内容URI。
内容URI允许对文件授予临时的读/写访问权限。

当构建一个包含内容URI的Intent,并要将该Intent传递给其他应用时,可以通过Intent.setFlags()添加访问权限。
这些权限在应用的接收 Activity 处于激活状态时有效(接收 Activity 栈销毁时授权自动失效)。
如果 Intent 是传递给 Service,在 Service 运行期间权限有效(Service 停止销毁后授权自动失效)。
因此,内容URI提供了更高级别的文件访问安全性,让FileProvider成为Android安全架构基础的一部分。
由此,apk的安装代码的实现可以如下。

    static final String FileProviderString = ".fileprovider";
    Intent intent_apk;
    Uri apkUri;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        apkUri = FileProvider.getUriForFile(context, context.getPackageName() + FileProviderString, new File(filePath));
        intent_apk = new Intent(Intent.ACTION_INSTALL_PACKAGE);
        intent_apk.setData(apkUri);
        intent_apk.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    } else {
        apkUri = Uri.fromFile(new File(filePath));
        intent_apk = new Intent(Intent.ACTION_VIEW);
        intent_apk.setDataAndType(apkUri, TypeApk);
        intent_apk.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    }
    context.startActivity(intent_apk);

二、FileProvider的使用步骤

1.声明FileProvider

在AndroidManifest文件中,增加一个条目。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">
    <application
        ...>
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.example.myapp.fileprovider"
            android:grantUriPermissions="true"
            android:exported="false">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
        ...
    </application>
</manifest>

由于FileProvider的默认功能就包含了为文件生成内容URI,因此不必再在代码中定义继承自FileProvide的子类,直接在AndroidManifest中声明FileProvider即可。

  • authorities属性的设置是为FileProvider生成内容URI的授权,授权字符串必须保证唯一,通常用包名+.fileprovider组装避免重复。
  • android:authorities="${applicationId}.provider"是更通用的写法。
  • grantUriPermissions属性值设为true,表示允许FileProvider给文件授予临时访问权限。
  • exported设为false,表示FileProvider不对外公开。
  • meta-data的设置在下一小节讲解。

2.指定允许生成内容URI的文件所属目录

FileProvider只能为事先指定目录下的文件生成内容URI。
而指定目录是通过在xml文件中定义存储空间和路径实现的。

2.1 创建XML资源配置(指定可共享的目录)

在res/xml目录下创建file_paths.xml文件。该文件以paths为根节点,在根节点下必须有至少一个表示存储空间和路径的节点。

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="files_path" path="."/>
    <external-path name="external_path" path="."/>
    <!-- ...... -->
</paths>

paths中可包含以下类型的子节点:

  • files-path:表示在应用内部存储空间中 files/ 子目录。
    这个目录路径跟 Context.getFilesDir()返回的一致。
  • cache-path:表示在应用内部存储空间中 cache/ 子目录。
    这个目录路径跟 Context.getCacheDir()返回的一致。
  • external-path:表示在应用外部存储空间中的根目录。
    这个目录路径跟Environment.getExternalStorageDirectory() 返回的一致。
  • external-files-path:表示在应用外部存储空间中 files/ 子目录。
    这个目录路径跟Context.getExternalFilesDir(String)、Context.getExternalFilesDir(null)返回的一致。
  • external-cache-path:表示在应用外部存储空间中 cache/ 子目录。
    这个目录路径跟Context.getExternalCacheDir() 返回的一致。
  • external-media-path:表示在应用外部存储空间中媒体子目录。
    这个目录路径跟Context.getExternalMediaDirs() 返回的一致(注意:这个目录只在 API 21+ 的设备上有效)。

子节点对应的路径

并且,子节点可以设置name和path属性:

  • name属性设置了内容URI的路径片段。
    为了增强安全性,这个值用来隐藏文件子目录的详细路径信息,也就是在内容URI中,用这个属性值替代子目录的路径信息。name的值可随意,但不能重复。
  • path属性设置共享文件所在的子目录详细路径
    这个值是真实存在的路径。这里可以设定子目录或某个特定的文件,但不能用通配符指定多个文件。

以<files-path name=“database” path=“internal/db/”/>为例,这表明FileProvider可以为files/internal/db下的文件生成内容URI。
假如一个文件存储在files/internal/db 目录下,在生成的内容 URI 中并不会包含 internal/db 片段,而是使用 name 属性的 值 database 隐藏真实的路径信息。
例如为 files/internal/db/init_data.db 生成的内容URI 为 content://com.owen.demo.android.owen.fileprovider/database/init_data.db。

2.2 在FileProvider中引用file_paths.xml

在应用清单文件中的<provider> 标签内部,使用 <meta-data> 子标签引用目录配置 XML 资源.
其中 android:name 属性值必须是 android.support.FILE_PROVIDER_PATHS, android:resources 引用定义好的 XML 资源文件。

            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />

3.为文件生成内容URI

先为文件定义一个 File 实例,然后调用 FileProvider.getUriForFile() API ,传入<provider> 标签 android:authorities 属性声明的授权以及文件 File 实例,即可生成内容 URI。

File file = new File(filePath);
Uri contentUri = FileProvider.getUriForFile(MainActivity.this, appContext.packageName + ".fileprovider", file);

注意,appContext.packageName + ".fileprovider"必须和FileProvider声明中的authorities保持一致。

4.在Intent中授予访问权限

将内容 URI 传递给请求方应用,并且赋予对内容 URI 的访问权限,按照以下步骤配置:

  1. 构建一个 Intent 实例对象,通过 Intent.setData() 将内容 URI 添加到 Intent 中;
  2. 调用 Intent.setFlags() 或者 Intent.addFlags() 接口添加 Intent.FLAG_GRANT_READ_URI_PERMISSIONIntent.FLAG_GRANT_WRITE_URI_PERMISSION 标志(或者同时添加两个);
  3. 可以用startActivity等方式启动intent

通过 Intent 授予的内容 URI 的访问权限,在接收的 Activity 栈处于激活状态时保持有效,当栈销毁之后,授权将自动失效。授予客户端应用一个 Activity 的访问权限,也将会自动扩展到应用的其他组件。

三、总结

  • 通过FileProvider.getUriForFile()能够为指定目录下的文件生成关联的content://类型的内容URI。
  • 内容URI相比普通URI提供了更高级别的文件访问安全性,让FileProvider成为Android安全架构基础的一部分。
  • 自从Android7.0(N)及以上开始,不允许在App间使用普通URI(即file://类型)的方式共享文件的访问权限,必须使用内容URI。

在app项目开发的实际过程中,需要用到FileProvider的主要有:

  1. 调用摄像头拍照以及图片裁剪
  2. 调用系统应用安装器安装apk(或应用升级)

本文主要介绍了apk的安装及FileProvider的使用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
简单的android文件管理器源码,从书上的例子改来的。 @打开没有权限的文件夹死机 @二级目录前面有"/" @修改图标 @修改排序,先目录,后文件,不区分大小写 @单击直接打开,长按弹出选项 ├── AndroidManifest.xml ├── assets ├── bin │ ├── classes.dex │ ├── com │ ├── FileManager.apk │ └── resources.ap_ ├── default.properties ├── FileManager.launch ├── gen │ └── com │ └── ckl │ └── android │ └── FileManager │ └── R.java ├── res │ ├── drawable │ │ ├── addfolderr.png │ │ ├── audio.png │ │ ├── back.png │ │ ├── delete.png │ │ ├── flash3.png │ │ ├── flash.png │ │ ├── folder.png │ │ ├── goroot.png │ │ ├── icon.png │ │ ├── image.png │ │ ├── packed.png │ │ ├── paste.png │ │ ├── reload.png │ │ ├── text.png │ │ ├── unkown.png │ │ ├── uponelevel.png │ │ ├── video2.png │ │ ├── video.png │ │ ├── webtext2.png │ │ └── webtext.png │ ├── layout │ │ ├── dialog.xml │ │ └── rename.xml │ └── values │ ├── fileendings.xml │ └── strings.xml └── src └── com └── ckl └── android └── FileManager ├── CONST.java ├── FileManager.java ├── IconifiedText.java ├── IconifiedTextListAdapter.java └── IconifiedTextView.java

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值