概述
FileProvider继承自ContentProvider 从英语翻译过来就是文件提供 也就是文件共享 当我们需要使用本地的文件时(这样说好像不太对)
- 例如调用相机拍照 需要将照片显示在屏幕上 照片是需要缓存在sdcard的缓存文件目录中的 这时就会用到FileProvider。
- 还有一个用处就是在自动安装apk上 我们经常会遇到app更新 此时系统会自动弹出一个框让我们更新并安装。
FileProvider使用时需要注意的一个地方就是Android7.0版本以后和Android4.4之前 对uri(通用资源标识符 Android上可用的每种资源-图像、视频片段都可以用uri来表示)的操作是不一样的。
相机获取本地图片
最近写了一个demo 主要是想实现从相册 本地获取的照片通过画布展现在屏幕上 并实现图片的保存。
下面就贴下我写的开启照相机拍照的代码:
public void takePhoto(){
//判断设备中是否存在sdcard
String SDState = Environment.getExternalStorageState();
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if(SDState.equals(Environment.MEDIA_MOUNTED)){
File outputImage = new File(getExternalCacheDir(),"output_image.jpg");
if(!outputImage.exists()){
outputImage.mkdir();
}
if(Build.VERSION.SDK_INT>=24){
imageUri = FileProvider.getUriForFile(this,
this.getPackageName()+".android7.fileprovider",outputImage);
Log.e("tag",imageUri.toString());
}else {
imageUri = Uri.fromFile(outputImage);
}
}
intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
startActivityForResult(intent,SELECT_PIC_BY_TAKE_PHOTO);
}
解决FileUriExposedException
在没有用FileProvider 只用Uri.fromFile()是对file://的访问 对于Android7.0来说是禁止的 要在应用间共享文件 应发送一项content://URL 并授予URL临时访问权限 这时就需要用到FileProvider。
使用FileProvider兼容拍照需要以下几个步骤:
声明provider
声明provider的原因我是这样理解的:FileProvider是ContentProvider的一个子类 在文件共享时需要声明来告诉系统进行了文件共享操作。
meta-data,里面指向了一个xml文件。
编写resource xml file
此文件的创建过程:res-->xml-->file_paths.xml。
在paths节点内部支持以下几个子节点,分别为:
相当于new File("/"),指向了整个存储的根目录。
相当于context.getFileDir()
返回文件系统上文件所在目录的绝对路径
相当于context.getCacheDir()
返回特定用于程序的缓存目录的绝对路径
相当于Environment.getExternalStorageDirectory()
返回主共享/外部存储目录。这个目录可能用户将其安装在其上 目前无法访问电脑 从设备中删除或一些问题发生了。使用此方法确定当前状态。
相当于context.getExternalFileDirs()
返回主数据库上特定于应用程序的目录的绝对路径共享外部存储设备,应用程序可以放置缓存它拥有的文件。这些程序是应用程序的内部文件,而不是通常作为媒体对用户可见。
相当于getExternalCacheDirs()
返回主数据库上特定于应用程序的目录的绝对路径共享外部存储设备,应用程序可以放置缓存它拥有的文件。这些程序是应用程序的内部文件,而不是通常作为媒体对用户可见。
这六个结点相对应的方法的作用 是我根据源码中对方法作用的注释 具体的区别我目前还不是很懂 我大概的理解是在拍照后照片需要存储在缓存文件中 我们需要给照片一个具体完整的路径 根据上面的六种基本路径存储方式会对应的得到一个文件路径头 后面的需要我们对应的补充。我看了很多博客大家都基本上选用
使用FileProvider API
主要代码就是:
FileProvider.getUriForFile(this,this.getPackageName()+".android7.fileprovider",outputImage);
getUriForFile()方法中有三个参数 第二个参数很重要,可以随便命名但是必须和前面的provider中的authorities属性相对应,第三个参数是自定义的图片文件路径。
Android4.4 7.0的区别
版本兼容的方法就是用版本校对
if(Build.VERSION.SDK_INT>=24){
imageUri = FileProvider.getUriForFile(this,
this.getPackageName()+".android7.fileprovider",outputImage);
Log.e("tag",imageUri.toString());
}else {
imageUri = Uri.fromFile(outputImage);
}
不用版本校对就需要为grantUriPermission进行授权。
主要思想就是通过获得应用中的包名加入到一个集合中,再通过for循环依次为当前符合条件的授权。
代码请参考鸿洋大神的博客Android 7.0 行为变更 通过FileProvider在应用间共享文件吧
使用FileProvider兼容安装apk
自动安装apk在考虑到版本不同的情况下 和拍照获取图片又是不一样的。主要问题在于ACTION_IMAGE_CAPTURE 在startActivity后 会逐层调用一个方法intent.migrateExtraStreamToClipData();此方法中有setClipData()方法 将EXTRA_OUTPUT转化 还添加了FLAG_GRANT_WRITE_URI_PERMISSION|FLAG_GRANT_READ_URI_PERMISSION两种权限(对uri的读和写) 这些方法在自动安装中并不会调用所以需要我们自己申请。
下面是一个通用的类:
public class FileProviderUtil {
public static Uri getUriForFile(Context context, File file){
Uri fileUri = null;
if(Build.VERSION.SDK_INT > 24){
fileUri = getUriForFile24(context,file);
}
else {
fileUri = Uri.fromFile(file);
}
return fileUri;
}
public static Uri getUriForFile24(Context context,File file){
Uri fileUrl = android.support.v4.content.FileProvider.
getUriForFile(context,context.getPackageName()+".android7.fileprovider",file);
return fileUrl;
}
//为自动安装apk设置权限
public static void setIntentDataAndType(Context context, Intent intent,
String type,File file,boolean writeAble){
if(Build.VERSION.SDK_INT>=24){
intent.setDataAndType(getUriForFile(context,file),type);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
if(writeAble){
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
}else {
intent.setDataAndType(Uri.fromFile(file),type);
}
}
}