升级了 compileSdkVersion 与 targetSdkVersion 到25, 导致app升级安装包 无法安装,报错日志UriExposedException。
问题1.应用间共享文件 直接使用绝对路径,会报错UriExposedException(Android7.0系统 调用系统相机、系统播放器播放视频、切图兼容问题,报异常android.os.FileUriExposedException)。
问题1解决方案:FileProvider (点这里关于FileProvider的详情介绍 )
FileProvider is a special subclass of ContentProvider that facilitates secure sharing of files associated with an app by creating a content:// Uri for a file instead of a file:/// Uri.
对于面向 Android N 的应用,Android 框架执行的 StrictMode,API 禁止向您的应用外公开 file://URI。
如果一项包含文件 URI 的 Intent 离开您的应用,应用失败,并出现 FileUriExposedException异常。
若要在应用间共享文件,您应发送一项 content://URI,并授予 URI 临时访问权限。
进行此授权的最简单方式是使用 FileProvider类。
在清单文件中注册FileProvider.
接下来在 项目的主目录下src/main/res/xml 文件夹下面创建file_provider_paths.xml
...
paths中共有以下几种类型
The element must contain one or more of the following child elements:
Context.getFilesDir().
对应目录 /data/user/0/packageName/files
getCacheDir().
对应目录 /data/user/0/packageName/cache
Environment.getExternalStorageDirectory().
对应目录 /storage/emulated/0
Context.getExternalFilesDir(null)
对应目录 /storage/emulated/0/Android/data/packageName/files
Context.getExternalFilesDir("images")
对应目录 /storage/emulated/0/Android/data/packageName/files/images
Context.getExternalCacheDir().
对应目录 /storage/emulated/0/Android/data/packageName/cache
Context.getExternalMediaDirs().
对应目录 /storage/emulated/0/Android/media/packageName
这几张类型都有相同的属性 name 与 path ,
name="name"
一个URI路径段。为了保障隐私,这个值隐藏你共享的子目录的名称。这个值的子目录名称包含在路径属性上。
path="path"
你共享的子目录。虽然名称属性是一个URI路径,路径的值是一个真实的子目录的名称。注意,path的值是指一个子目录,而不是具体的单个或多个文件。你不能共享单个文件的文件名,也不可以使用通配符指定文件的一个子集。
对文件生成 Content URI
使用content URI 分享一个文件给别的app,需要在自己的app 中生成一个content URI 。生成content URI,首先创建一个新文件,然后通过getUriForFile()传递文件。可以使用intent把通过 getUriForFile() 返回的 content URI 发送给另一个app。
接收的客户端应用程序可以打开文件并通过调用ContentResolver.openFileDescriptor来获得一个ParcelFileDescriptor访问其内容。
例如,假设你的app 需要通过使用权限为 com.mydomain.fileprovider的FileProvider提供文件给三方的app。
为了获取 内部存储子目录images下的default_image.jpg的content URI需要添加以下代码:
File imagePath = new File(getFilesDir(), "images");
File newFile = new File(imagePath, "default_image.jpg");
Uri contentUri = FileProvider.getUriForFile(this, "com.mydomain.fileprovider", newFile);
作为之前片段的结果getUriForFile() 返回 content URI 为:content://com.mydomain.fileprovider/my_images/default_image.jpg.
给一个URI临时权限
为了授权一个使用权限给 通过 getUriForFile()返回的content URI,按下列步骤操作:
1.为content:// Uri调用方法 grantUriPermission(String toPackage, Uri uri, int modeFlags)使用需要的模式标记. 为content URI授予临时权限给自定的包, 相应的mode_flags 可选参数FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION or both. 这些权限会一直保持直到你调用revokeUriPermission()撤销权限或者设备重启。
2.通过调用Intent 的setData()设置content URI。
3.接下来调用Intent.setFlags()设置Flags 可以为FLAG_GRANT_READ_URI_PERMISSION or FLAG_GRANT_WRITE_URI_PERMISSION or 两个同时设置.
4.最后,发送Intent 给另一个app。大多时候,通常操作是调用setResult()。
当Activity栈中接收Intent 中的Activity存活时,权限许可会一直有效。当栈结束,权限自动移除。app程序中一个Activity钟授予的权限就自动的扩展到该app其他组件的。
为三方应用提供Content URI
有多种方式可以为一个三方应用提供文件的 content URI。一个通常的方式是 在你的app 中调用startActivityResult(),发送一个Intent 给你的app 来在你的app 中启动一个Activity。接下来,你的app可以立即返回一个content URI给三方app或者提供一个用户接口允许使用者去获取该文件。接下来,一但用户获取了该文件,并使用setResult() 发送一个Intent,你的app可以收到一个content URI的通知。
也可以把content URI放到 ClipData对象,然后将对象添加到一个Intent发送到三方的app中。要做到这一点,调用Intent.setClipData()。当你使用这种方法,您可以添加多个ClipData对象给Intent,每个都有自己的内容URI。当你调用Intent.setFlags()来设置临时访问权限,相同的权限许可同样应用到了全部的相同的content URIs。
Note: The Intent.setClipData() method is only available in platform version 16 (Android 4.1) and later.
If you want to maintain compatibility with previous versions, you should send one content URI at a time in the Intent.
Set the action to ACTION_SEND and put the URI in data by calling setData().
以打开相机为例具体如下:
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
File file = new File(filePath);
Uri contentUri = FileProvider.getUriForFile(context,"com.womai.fileProvider",file);
intent.putExtra(MediaStore.EXTRA_OUTPUT, contentUri);
}else {
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(filePath)));
}
if (null != intent.resolveActivity(context.getPackageManager())) {
context.startActivityForResult(intent, Constants.ResultCode.BLESS_TAKEPHOTO);
}
这里就通过getUriForFile方法将通过Intent 对外暴露的 Uri做了转换。
以app升级安装为例:
安装:
Intent intent = new Intent();
// 执行动作
intent.setAction(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
Uri contentUri = FileProvider.getUriForFile(context,"com.womai.fileProvider",file);
intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
}else {
// android 4.0以后需要这句话,要不然不显示安装成功
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(
Uri.fromFile(file),
"application/vnd.android.package-archive");
}
context.startActivity(intent);
欢迎爱学习的小伙伴加群一起进步:230274309