如何兼容所有Android版本选择照片或拍照然后裁剪图片--基于FileProvider和动态权限的实现
我们知道, Android操作系统一直在进化. 虽然说系统是越来越安全, 可靠, 但是对于开发者而言, 开发难度是越来越大的, 需要注意的兼容性问题, 也越来越多. 就比如在Android平台上拍照或者选择照片之后裁剪图片, 原本不需要考虑权限是否授予, 存储空间的访问安全性等问题. 比如, 在Android 6.0之后, 一些危险的权限诸如相机, 电话, 短信, 定位等, 都需要开发者主动向用户申请该权限才可以使用, 不像以前那样, 在AndroidManifest.xml里面配置一下即可. 再比如, 在Android 7.0之后, FileProvider的出现, 要求开发者需要手动授予访问本应用内部File, Uri等涉及到存储空间的Intent读取的权限, 这样外部的应用(比如相机, 文件选择器, 下载管理器等)才允许访问.
最近在公司的项目中, 又遇到了要求拍照或者选择图片, 裁减后上传到服务器的需求. 所以就怀着使后来人少踩坑的美好想象, 把这部分工程中大家可能都会遇到的共同问题, 给出一个比较合理通用的解决方案.
好, 下面我们就正式开始吧!
1, 在AndroidManifest.xml配置文件中, 添加对相机权限的使用:
<uses-permission android:name="android.permission.CAMERA" />
2, 声明本应用对FileProvider使用, 在<application>里面添加元素<provider>:
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
android:theme="@style/QxfActionBarTheme"
tools:replace="android:icon">
//.... <provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.file_provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_path" />
</provider>
3, 在res目录下创建xml文件夹, 然后在其内容创建文件file_path.xml文件, 这里对你的文件的位置, 进行了定义:
<?xml version="1.0" encoding="utf-8"?>
<paths>
<external-path
name="Download"
path="." />
</paths>
4, 创建IntentUtil.java文件, 用于获取调用相机, 选择图片, 裁减图片的Intent:
public static Intent getIntentOfTakingPhoto(@NonNull Context context, @NonNull Uri photoUri) {
Intent takingPhotoIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
takingPhotoIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
grantIntentAccessUriPermission(context, takingPhotoIntent, photoUri);
return takingPhotoIntent;
}
public static Intent getIntentOfPickingPicture() {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
return intent;
}
public static Intent getIntentOfCroppingImage(@NonNull Context context, @NonNull Uri imageUri) {
Intent croppingImageIntent = new Intent("com.android.camera.action.CROP");
croppingImageIntent.setDataAndType(imageUri, "image/*");
croppingImageIntent.putExtra("crop", "true");
//crop into circle image
// croppingImageIntent.putExtra("circleCrop", "true");
//The proportion of the crop box is 1:1
croppingImageIntent.putExtra("aspectX", 1);
croppingImageIntent.putExtra("aspectY", 1);
//Crop the output image size
croppingImageIntent.putExtra("outputX", 256);//输出的最终图片文件的尺寸, 单位是pixel
croppingImageIntent.putExtra("outputY", 256);
//scale selected content
croppingImageIntent.putExtra("scale", true);
//image type
croppingImageIntent.putExtra("outputFormat", "JPEG");
croppingImageIntent.putExtra("noFaceDetection", true);
//false - don't return uri | true - return uri
croppingImageIntent.putExtra("return-data", true);//
croppingImageIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
grantIntentAccessUriPermission(context, croppingImageIntent, imageUri);
return croppingImageIntent;
}
5, 在IntentUtil.java文件定义grantIntentAccessUriPermission(...)方法, 用于向访问相机, 裁减图片的Intent授予对本应用内容File和Uri读取的权限:
private static void grantIntentAccessUriPermission(@NonNull Context context, @NonNull Intent intent, @NonNull Uri uri) {
if (!Util.requireSDKInt(Build.VERSION_CODES.N)) {//in pre-N devices, manually grant uri permission.
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
} else {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
}
当然, 需要指出的是: 通过向Intent添加flag的方法, 即intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)和intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)适用于所有的Android版本, 除了Android 4.4. 在Android 4.4中需要手动的添加两个权限, 即
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
当然, 手动地添加读写权限同样适用于所有版本, 只不过通过向Intent添加flag的方法更加轻松, 更加简单而已. 如果你的应用最低版本高于Android 4.4, 则只使用添加flag的方法就行了.
6, 在需要使用相机, 选择图片, 裁减图片的Activity里面, 定义变量File(输出文件的具体位置)和Uri(包含文件的相关信息并供Intent使用):
private File avatarFile; private Uri avatarUri;
7, 在使用相机的时候, 有如下逻辑: 开启相机前, 判断一下是否已经取得了相机的权限: a, 如果用户已经授予应用访问相机的权限, 则直接去开启相机. b, 如果用户没有授予相机的权限, 则主动向用户去请求. 在接收到授予权限的结果时, 如果用户授予了相机权限, 则直接打开相机. 如果用户拒绝了, 则给予相应的提示或者操作. c, 如果用户连续向该权限申请拒绝了两次, 即, 系统已经对相机权限的申请直接进行了拒绝, 不再向用户弹出授予权限的对话框, 则直接提示用户该权限已经被系统拒绝, 需要手动开启, 并直接跳转到相应的权限管理系统页面. 当然, 动态权限仅限于Android 6.0+使用.
8, 使用相机:
@Override
public void onCameraSelected() {
if (Util.checkPermissionGranted(this, Manifest.permission.CAMERA)) {//如果已经授予相机相关权限
openCamera();
} else {//如果相机权限并未被授予, 主动向用户请求该权限
if (Util.requireSDKInt(Build.VERSION_CODES.M)) {//Android 6.0+时, 动态申请权限
requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION);
} else {
IntentUtil.openAppPermissionPage(this);
}
}
}
9, 对动态申请权限的结果进行处理, 具体代码如下:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case REQUEST_PERMISSION:
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openCamera();
} else {
// permission denied, boo! Disable the
// functionality that depends on this permission.
showPermissionDeniedDialog();
}
break;
}
}
上述代码的逻辑是: 如果用户授予了权限, 则直接打开相机, 如果没有, 则显示一个权限被拒绝的对话框.
10, 选择图片: 这个Intent不需要授予读写权限, 注意一下:
@Override
public void onGallerySelected() {
Intent pickingPictureIntent = IntentUtil.getIntentOfPickingPicture();
if (pickingPictureIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(pickingPictureIntent, REQUEST_PICK_PICTURE);
}
}
11, 覆盖onActivityResult(...)方法, 对拍照和选择图片的结果进行处理, 然后进行裁减.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == Activity.RESULT_OK) {
if (requestCode == REQUEST_TAKE_PHOTO) {
avatarUri = UriUtil.getUriFromFileProvider(avatarFile);
cropImage(avatarUri);
} else if (requestCode == REQUEST_PICK_PICTURE) {
if (data != null && data.getData() != null) {
avatarFile = FileFactory.createTempImageFile(this);
/*
* Uri(data.getData()) from Intent(data) is not provided by our own FileProvider,
* so we can't grant it the permission of read and write programmatically
* through {@link IntentUtil#grantIntentAccessUriPermission(Context, Intent, Uri)},
* So we have to copy to our own Uri with permission of write and read granted
*/
avatarUri = UriUtil.copy(this, data.getData(), avatarFile);
cropImage(avatarUri);
}
} else if (requestCode == REQUEST_CROP_IMAGE) {
mAvatar.setImageURI(avatarUri);
uploadAvatar();
}
} else {
// nothing to do here
}
}
12, 对图片进行裁减.
private void cropImage(Uri uri) {
Intent croppingImageIntent = IntentUtil.getIntentOfCroppingImage(this, uri);
if (croppingImageIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(croppingImageIntent, REQUEST_CROP_IMAGE);
}
}
对裁减的结果进行处理, 代码在(11)里面.
13, 最后再补充一下上述代码使用到的FileFactory.java和UriUtil.java两个文件里面的一些方法.
FileFactory.java:
public class FileFactory {
private FileFactory() {
}
private static String createImageFileName() {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
return imageFileName;
}
public static File createTempImageFile(@NonNull Context context) {
try {
File storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
FileFactory.createImageFileName(), /* prefix */
".jpeg", /* suffix */
storageDir /* directory */
);
return image;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static File createImageFile(@NonNull Context context, @NonNull String fileName) {
try {
if (TextUtils.isEmpty(fileName)) {
fileName = "0000";
}
File storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = new File(storageDir, fileName + ".jpeg");
if (!image.exists()) {
image.createNewFile();
} else {
image.delete();
image.createNewFile();
}
return image;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public static byte[] readBytesFromFile(@NonNull File file) {
try (InputStream inputStream = new FileInputStream(file)) {
byte[] bytes = new byte[inputStream.available()];
inputStream.read(bytes);
return bytes;
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
UriUtil.java:
public class UriUtil {
private UriUtil() {
}
public static Uri getUriFromFileProvider(@NonNull File file) {
return FileProvider.getUriForFile(QxfApplication.getInstance(),
BuildConfig.APPLICATION_ID + ".file_provider",
file);
}
public static Uri copy(@NonNull Context context, @NonNull Uri fromUri, @NonNull File toFile) {
try (FileChannel source = ((FileInputStream) context.getContentResolver().openInputStream(fromUri)).getChannel();
FileChannel destination = new FileOutputStream(toFile).getChannel()) {
if (source != null && destination != null) {
destination.transferFrom(source, 0, source.size());
return UriUtil.getUriFromFileProvider(toFile);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
最后再添加两个方法, requireSDKInt(int)和checkPermissionGranted(context, permission), 分别用于判断是否要求最低Android版本是多少和检测某个权限是否已经被用户授予.
public static boolean requireSDKInt(int sdkInt) {
return Build.VERSION.SDK_INT >= sdkInt;
}
public static boolean checkPermissionGranted(Context context, String permission) {
return PermissionChecker.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
}
最后, 先拍照或者选择图片, 然后对结果进行图片的裁减, 兼容了所有Android版本的方式已经介绍完了, 无论是Android 6.0中动态权限的申请和Android 7.0中对存储空间的限制, 都已经进行了处理, 而且测试通过.
大家有什么问题, 可以在评论里面问我. 谢谢~
如何兼容所有Android版本选择照片或拍照然后裁剪图片--基于FileProvider和动态权限的实现的更多相关文章
- mono for android 获取手机照片或拍照并裁剪保存
axml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android ...
- Android 从本地图库或拍照后裁剪图片并设置头像
在QQ和微信等应用都会有设置头像,一般都是从本地图库选取或相机拍照,然后再截图自己喜欢的部分,然后设置.最后一步把截取好的图片再保存到本地,来保存头像.为了大家使用方便,我把自己完整的代码贴出来,大家 ...
- android 开发 实现一个进入相机拍照后裁剪图片或者进入相册选中裁剪图片的功能
实现思维路径: 以进入相机拍照的思维路线为例子: 1.进入app 2.判断之前是否保存头像,如果有就显示历史图像 (下面代码中在getOldAvatar();方法中执行这个逻辑) 3.点击更换图像的B ...
- Android调用相机实现拍照并裁剪图片,调用手机中的相冊图片并裁剪图片
在 Android应用中,非常多时候我们须要实现上传图片,或者直接调用手机上的拍照功能拍照处理然后直接显示并上传功能,以下将讲述调用相机拍照处理图片然后显示和调用手机相冊中的图片处理然后显示的功能,要 ...
- [转]Android使用WebView从相册/拍照中添加图片
原地址:http://blog.csdn.net/djcken/article/details/46379929 解决这个问题花了很长时间搜索了解,网上大部分使用openFileChooser但都没解 ...
- android: 从相册中选择照片
虽然调用摄像头拍照既方便又快捷,但并不是每一次我们都需要去当场拍一张照片的. 因为每个人的手机相册里应该都会存有许许多多张照片,直接从相册里选取一张现有的照 片会比打开相机拍一张照片更加常用.一个优秀 ...
- android 开发 实现多个动态权限的方法(并且兼容6.0以下的版本权限授权)
android开发权限授权因为版本的不同有不同的授权方式,6.0以下的版本使用的是在注册表中添加权限的静态授权(这种授权权限提示只会出现在app安装的时候),而6.0以上(包含6.0)就需要动态授权的 ...
- Android开发模板代码(一)——简单打开图库选择照片
首先,先贴上样本代码 //检查权限 public void checkPermission() { if (ContextCompat.checkSelfPermission(this, Manife ...
- 关于前端本地压缩图片,兼容IOS/Android/PC且自动按需加载文件之lrz.bundle.js
一.介绍说明主要特点: ①在前端压缩好要上传的图片可以更快的发送给后端,因此也特别适合在移动设备上使用. ②兼容IOS/Android,修复了IOS/Android某些版本已知的BUG. ③按需加载文 ...
随机推荐
- GCD SUM 强大的数论,容斥定理
GCD SUM Time Limit: 8000/4000MS (Java/Others) Memory Limit: 128000/64000KB (Java/Others) SubmitStatu ...
- Tensorflow学习教程------创建图启动图
Tensorflow作为目前最热门的机器学习框架之一,受到了工业界和学界的热门追捧.以下几章教程将记录本人学习tensorflow的一些过程. 在tensorflow这个框架里,可以讲是若数据类型,也 ...
- django ajax练习
这几天遇到了django ajax请求出错的问题,总结一下 前端js:我这里创建的是一个字典格式的数据,前端js收到字典之后也是要用字典的形式去解包后台传送过来的数据,比如我下面的写法:data['s ...
- python爬取煎蛋网图片
``` py2版本: #-*- coding:utf-8 -*-#from __future__ import unicode_literimport urllib,urllib2,timeimpor ...
- Linux vi 退出&保存/不保存
无论是否退出 vi,均可保存所做的工作.按 ESC 键,确定 vi 是否处于命令模式. 操作 键入 保存,但不退出vi :w 保存并退出vi ...
- 替代PhotoShop:GIMP图形编辑器的使用
GIMP最早是linux环境下用于图形编辑的一款开源软件,目前的功能很已经很丰富,如果使用得当,在很多的图形编辑操作上完全可以替代收费的Photoshop(PS).目前GIMP已经发展成了多平台的开源 ...
- python apschedule安装使用与源码分析
我们的项目中用apschedule作为核心定时调度模块.所以对apschedule进行了一些调查和源码级的分析. 1.为什么选择apschedule? 听信了一句话,apschedule之于pytho ...
- 奥利奥好吃吗?Android 8.0新特性适配测试报告来啦!
WeTest 导读 谷歌2017 I/O开发者大会上发布了Android 8.0的正式版, 其官方代号为Oreo(奥利奥).网上关于Android8.0新功能特性的介绍已铺天盖地,新功能特性会对程序应 ...
- sqlite3基本相关使用
闲来无事,复习和总结了一下之前学习到的关于sqlite3数据库的相关知识: [1] sqlite3的安装:1.离线安装:sudo dpkg -i *.deb2.在线安装:sudo apt-get in ...
- win10 uwp 使用 Geometry resources 在 xaml
经常会遇到在 xaml 使用矢量图,对于 svg 的矢量图,一般都可以拿出来写在 Path 的 Data ,所以可以写为资源,但是写出来的是字符串,如何绑定 Geometry 到字符串资源? 假如在资 ...