在官方7.0的以上的系统中,尝试传递 file://URI可能会触发FileUriExposedException。
7.0以上的系统需要使用FileProvider兼容拍照
FileProvider
是ContentProvider的一个子类
作用:是将file:/// 用 Uricontent:// URI 代替
声明FileProvider:
<!-- android:grantUriPermissions="true" 允许您授予对文件的临时访问权限-->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.fanfan.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/fileprovider"/>
</provider>
exported必须为false,否则会抛出异常。详情见源码
grantUriPermissions为true,表明授予对文件的临时访问权限
编写fileprovider.xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path name="root" path="" />
<files-path name="files" path="" />
<cache-path name="cache" path="" />
<external-path name="external" path="" />
<external-files-path name="name" path="path" />
<external-cache-path name="name" path="path" />
</paths>
在paths节点内部支持以下几个子节点,分别为:
- <root-path/> 代表设备的根目录new File("/");
- <files-path/> 代表context.getFilesDir()
- <cache-path/> 代表context.getCacheDir()
- <external-path/> 代表Environment.getExternalStorageDirectory()
- <external-files-path>代表context.getExternalFilesDirs()
- <external-cache-path>代表getExternalCacheDirs()
每个节点都支持两个属性:
- name,此值会隐藏您要共享的子目录的名称。
- path,实际的子目录名称
例如
<external-path
name="external"
path="pics" />
代表的目录即为:Environment.getExternalStorageDirectory()/pics,其他同理。
通过FileProvider生成Uri
File photoFile = new File(Environment.getExternalStorageDirectory(), imageFileName+".jpg");
photoURI = FileProvider.getUriForFile(this, "com.fanfan.fileprovider", photoFile);
getUriForFile()返回内容URI:content://com.fanfan.fileprovider/external/JPEG_20190322_164318_.jpg
兼容旧系统
可以全部授权,也可以做版本检验直接获取uri
本人推荐版本检验
Uri fileUri = null;
if (Build.VERSION.SDK_INT >= 24) {
fileUri = FileProvider.getUriForFile(this, "com.fanfan.fileprovider", photoFile);
} else {
fileUri = Uri.fromFile(photoFile);
}
调用系统拍照并获取图片实例:
public class ExistingCameraActivity extends AppCompatActivity {
private static final int REQUEST_IMAGE_CAPTURE = 1122;
@BindView(R.id.iv)
ImageView mIv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_existing_camera);
ButterKnife.bind(this);
// 该条件 resolveActivity()返回可以处理intent的第一个活动组件。
// 因为如果startActivityForResult()使用无应用程序无法处理的意图进行调用,应用程序将崩溃
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = createImageFile();
if (photoFile != null) {
Uri photoURI;
//版本校验,或者grantUriPermission授权、revokeUriPermission移除权限
if (Build.VERSION.SDK_INT >= 24) {
photoURI = FileProvider.getUriForFile(this, "com.fanfan.fileprovider", photoFile);
} else {
photoURI = Uri.fromFile(photoFile);
}
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
}
}
String currentPhotoPath;
private File createImageFile() {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File image = new File(Environment.getExternalStorageDirectory(), imageFileName + ".jpg");
currentPhotoPath = image.getAbsolutePath();
return image;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
//获取拍摄到的图片缩略图
// Bundle extras = data.getExtras();
// Bitmap imageBitmap = (Bitmap) extras.get("data");
// mIv.setImageBitmap(imageBitmap);
//获取拍摄到的图片
mIv.setImageBitmap(BitmapFactory.decodeFile(currentPhotoPath));
}
}
}
安装apk实例:
使用FileProvider安装apk需要授予URI临时权限,也就是Context.grantUriPermission(package, Uri, mode_flags),这将授予该内容的URI指定的包临时访问权限,根据该值mode_flags参数,该参数可以设置为 FLAG_GRANT_READ_URI_PERMISSION,FLAG_GRANT_WRITE_URI_PERMISSION,通过调用revokeUriPermission()或设备重新启动来撤消它
也可以直接使用以下方式授予临时权限
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
安装apk实例:
public void installApk(View view) {
File file = new File(Environment.getExternalStorageDirectory(), "testandroid7-debug.apk");
Intent intent = new Intent(Intent.ACTION_VIEW);
setIntentDataAndType(this,intent,""application/vnd.android.package-archive",file,true);
"application/vnd.android.package-archive");
startActivity(intent);
}
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);
}
}
大家可能存在疑问,为什么安卓7设备拍照没有增加临时权限?
其实是因为Intent的action为ACTION_IMAGE_CAPTURE,当我们startActivity后,会辗转调用Instrumentation的execStartActivity方法,在该方法内部,会调用intent.migrateExtraStreamToClipData(),方法内部会
给我们添加了WRITE和READ权限。(注意:addFlags方式对于ACTION_IMAGE_CAPTURE在5.0以下是无效的)
快速完成适配
FileProvider的注册
<application>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.android7.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
编写file_paths
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path
name="root"
path="" />
<files-path
name="files"
path="" />
<cache-path
name="cache"
path="" />
<external-path
name="external"
path="" />
<external-files-path
name="external_file_path"
path="" />
<external-cache-path
name="external_cache_path"
path="" />
</paths>
辅助类
public class FileProvider7 {
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 fileUri = android.support.v4.content.FileProvider.getUriForFile(context,
context.getPackageName() + ".android7.fileprovider",
file);
return fileUri;
}
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);
}
}
}
参考:
https://blog.csdn.net/lmj623565791/article/details/72859156
https://developer.android.com/reference/android/support/v4/content/FileProvider.html