适配Android10 拍照,相册,裁剪,上传图片
这篇文章主要介绍了适配Android 10(Q)后,调用系统拍照,系统相册,系统裁剪和上传问题,这是一个很常用的功能,但是在Android 6.0,Android 7.0和Android 10.0以上版本的实现都有所不同,这篇文章从Android 4适配到Android 10。
之前写毕设的时候,在写上传头像的功能时,参考网上的方法写了一大堆,在我的手机(Android 9)上可以正常运行,当时没多想,以为高版本可以向下兼容,后来我把程序发给同学去试验,结果都告诉我上传头像用不了,一问才知道他们用的是Android 10的手机,于是只能上网查找原因,然后发现Android 10的存储方式发生了变化,Android 10的文件系统采用了沙盒文件系统,最显著的变化就是文件系统变安全了,于是app也没办法拿到外部文件的绝对路径了,网上给出的方法就是将共享文件复制到沙盒目录下,然后再进行文件操作。话不多说,上代码。
在文件清单AndroidManifest.xml中添加权限:
1 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><!-- 储存卡的读权限 -->
2 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><!-- 储存卡的写权限 -->
3 <uses-permission android:name="android.permission.CAMERA" /><!-- 调用相机权限 -->
在官方7.0的以上的系统中,尝试传递 file://URI可能会触发FileUriExposedException,使用FileProvider来共享文件,AndroidManifest.xml:
<application
...
<!-- 兼容Android7.0拍照闪退 -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.camera.test"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
在主界面放一个ImageView和两个按钮,activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity"> <ImageView
android:id="@+id/image"
android:layout_width="250dp"
android:layout_height="250dp"
android:layout_marginTop="20dp"
android:layout_gravity="center_horizontal"/> <TextView
android:id="@+id/tv_camera"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:text="相机"
android:textSize="18sp"
android:textColor="#FFF"
android:padding="10dp"
android:background="#1878FF"
android:layout_marginHorizontal="20dp"
android:gravity="center_horizontal"/> <TextView
android:id="@+id/tv_album"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:text="相册"
android:textSize="18sp"
android:textColor="#FFF"
android:padding="10dp"
android:background="#1878FF"
android:layout_marginHorizontal="20dp"
android:gravity="center_horizontal"/> </LinearLayout>
接下来是主页面的代码:
获取控件,对两个按钮添加点击监听,判断权限:
private ImageView image;
private TextView tvCamera, tvAlbum;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
init();
} private void init(){
image = findViewById(R.id.image);
tvCamera = findViewById(R.id.tv_camera);
tvAlbum = findViewById(R.id.tv_album); tvCamera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//相机
if ((ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED)
&& (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED)
&& (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED)) {
//权限都齐的情况下,跳转相机
openCamera();
} else {
if (activity != null) {
//请求权限
ActivityCompat.requestPermissions(activity, new String[]{
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
}, PHOTO_REQUEST_CAMERA);
}
}
}
}); tvAlbum.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//相册
if ((ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED)
&& (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE)
== PackageManager.PERMISSION_GRANTED)) {
//权限都齐的情况下,跳转相册
openAlbum();
} else {
if (activity != null) {
//请求权限
ActivityCompat.requestPermissions(activity, new String[]{
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
}, PHOTO_REQUEST_ALBUM);
}
}
}
});
}
权限申请回调:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case PHOTO_REQUEST_CAMERA:
//相机权限请求回调
if (grantResults.length > 0) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED
&& grantResults[2] == PackageManager.PERMISSION_GRANTED) {
//跳转相机
openCamera();
} else {
//无权限提示
Toast.makeText(context, "权限未通过", Toast.LENGTH_SHORT).show();
}
}
break;
case PHOTO_REQUEST_ALBUM:
if (grantResults.length > 0) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED
&& grantResults[1] == PackageManager.PERMISSION_GRANTED) {
//跳转相册
openAlbum();
} else {
//无权限提示
Toast.makeText(context, "权限未通过", Toast.LENGTH_SHORT).show();
}
}
break;
}
}
跳转相机:
private void openCamera(){
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//判断是否有相机
if (activity != null && context != null && intent.resolveActivity(activity.getPackageManager()) != null){
File file;
Uri uri = null;
if (isAndroidQ){
//适配Android10
uri = createImageUri(context);
} else {
//Android10以下
file = createImageFile(context);
if (file != null){
//Android10以下
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
//适配Android7.0文件权限
uri = FileProvider.getUriForFile(context, "com.example.camera.test", file);
} else {
uri = Uri.fromFile(file);
}
}
}
imageUri = uri;
Log.e(TAG, "相机保存的图片Uri:" + imageUri);
if (uri != null){
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(intent, CAMERA_REQUEST_CODE);
}
}
}
Android 10以上的创建Uri,Uri创建在沙盒内:
contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/0/");
以上设置的保存路径为:".../包名/files/Pictures/0",可按需更改
用于保存拍照之后的照片:
private Uri createImageUri(@NonNull Context context){
String status = Environment.getExternalStorageState();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, SAVE_AVATAR_NAME);
contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/*");
contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/0/");
//判断是否有SD卡
if (status.equals(Environment.MEDIA_MOUNTED)){
return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
} else {
return context.getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, contentValues);
}
}
Android 10以下的返回一个file来保存拍照后的图片:
private File createImageFile(@NonNull Context context){
File file = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
if (file != null && !file.exists()){
if (file.mkdir()){
Log.e(TAG, "文件夹创建成功");
} else {
Log.e(TAG, "file为空或者文件夹创建失败");
}
}
File tempFile = new File(file, SAVE_AVATAR_NAME);
Log.e(TAG, "临时文件路径:" + tempFile.getAbsolutePath());
if (!Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(tempFile))){
return null;
}
return tempFile;
}
跳转相册:
private void openAlbum(){
Intent intent = new Intent(Intent.ACTION_PICK, null);
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
startActivityForResult(intent, ALBUM_REQUEST_CODE);
}
跳转裁剪,裁剪在相机拍照后跳转,用一个file来加载:
private void openCrop(Uri uri){
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) && context != null){
file = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES + "/0"), SAVE_AVATAR_NAME);
}
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
intent.setDataAndType(uri, "image/*");
// 设置裁剪
intent.putExtra("crop", "true");
// aspectX aspectY 是宽高的比例
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
// 裁剪后输出图片的尺寸大小
intent.putExtra("outputX", 250);
intent.putExtra("outputY", 250);
//适配Android10,存放图片路径
intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(file));
// 图片格式
intent.putExtra("outputFormat", "PNG");
intent.putExtra("noFaceDetection", true);// 取消人脸识别
intent.putExtra("return-data", true);// true:不返回uri,false:返回uri
startActivityForResult(intent, TAILOR_REQUEST_CODE);
}
跳转相机、相册和裁剪的回调,如果有上传需求的,直接上传代码中的file即可:
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == -1){
//回调成功
switch (requestCode) {
case CAMERA_REQUEST_CODE:
//相机回调
Log.e(TAG, "相机回调");
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
//照片裁剪
openCrop(imageUri);
} else {
Toast.makeText(context, "未找到存储卡", Toast.LENGTH_SHORT).show();
}
break;
case ALBUM_REQUEST_CODE:
//相册回调
Log.e(TAG, "相册回调");
if (data != null && data.getData() != null) {
image.setImageURI(data.getData());
//如果需要上传操作的可以使用这个方法
File file = FileUtils.uriToFile(data.getData(), context);
//这里的file就是需要上传的图片了
}
break;
case TAILOR_REQUEST_CODE:
//图片剪裁回调
Log.e(TAG, "图片剪裁回调");
// Glide.with(context).load(file).into(image);
Uri uri = Uri.fromFile(file);
image.setImageURI(uri);
//如果需要上传全局的这个file就是需要上传的图片了
File file = this.file;
break;
}
} else {
Toast.makeText(context, "取消", Toast.LENGTH_SHORT).show();
}
}
以上,如果有改进的建议的,欢迎骚扰
QQ:1336140321
适配Android10 拍照,相册,裁剪,上传图片的更多相关文章
- Android SDK4/5/6/7,相册、拍照及裁剪功能及遇见的坑
保存照片和视频到系统相册显示- http://blog.csdn.net/chendong_/article/details/52290329 Android 7.0 之拍照与图片裁剪适配-http: ...
- android 拍照 相册 剪切以及显示功能
一.概述 android的 图片拍照 ,相册选图,以及图片剪切功能可以说非常常用. 尤其是图片上传功能,必然用到此功能. 而公司最近的一个项目中正好用到该功能. 记录下来以便以后再次用到,直接拿来使用 ...
- [Android实例教程] 教你如何拍照+相册选择图片+剪裁图片完整实现
[Android实例教程] 教你如何拍照+相册选择图片+剪裁图片完整实现 今天做Android项目的时候要用到图片选择,要实现拍照获取图片和从相册获取图片,并且要求在获取完之后可以裁剪,试了很多方法之 ...
- 手机调用系统的拍照和裁剪功能,假设界面有输入框EditText,在一些手机会出现点击EditText会弹出输入法,却不能输入的情况。
1. 拍照裁剪后 点击EditText会弹出输入法,却不能输入.可是点击点一EdtiText就能够输入了,所以我就写了一个看不见的EdtiText,切换焦点,这样就攻克了这个奇怪的这问题,应该是and ...
- 真正可用的安卓webview html图片上传限制突破处理(拍照+相册都可以用)
两篇起步使用webview参考文章,第一篇解除限制,但会调用外部浏览器打开链接,第二篇 覆盖shouldOverrideUrlLoading return true https://www.jb51. ...
- android 开发 实现一个进入相机拍照后裁剪图片或者进入相册选中裁剪图片的功能
实现思维路径: 以进入相机拍照的思维路线为例子: 1.进入app 2.判断之前是否保存头像,如果有就显示历史图像 (下面代码中在getOldAvatar();方法中执行这个逻辑) 3.点击更换图像的B ...
- 3.PopupWindow 、拍照、裁剪
实现这样的效果 圆角图片的自定义控件直接拷进来,和com一个等级 想要弹出内容可以使用悬浮窗 layout_pupup <LinearLayout xmlns:android="htt ...
- PopupWindow 以及拍照、裁剪
实现这样的效果 圆角图片的自定义控件直接拷进来,和com一个等级 想要弹出内容可以使用悬浮窗 layout_pupup <LinearLayout xmlns:android="htt ...
- mono for android 获取手机照片或拍照并裁剪保存
axml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android ...
随机推荐
- Centos中安装Node.Js
NodeJs安装有好几种方式: 第一种: 最简单的是用yum命令,可惜我现在用的时候 发现 镜像中没有nodejs:所以这种方式放弃: 第二种:去官网下载源码,然后自己编译:编译过程中可能会出现问题, ...
- Acunetix敏感的数据泄露–泄露如何发生
术语"敏感数据暴露"是指允许未授权方访问存储或传输的敏感信息,例如信用卡号或密码.全球范围内大多数重大安全漏洞都会导致某种敏感的数据泄露. Acunetix利用攻击漏洞(例如Web ...
- centos搭建dns服务器
前言:搭建dns服务器,dns服务器我就不多说什么了,大家都懂,就是域名解析,就将ip装换为域名,域名就可以理解为类似这样的www.baidu.com网址,接下来我就直接上图了.这里面最重要的是修改u ...
- 题解 SP3591 PATHEADS - Patting Heads
类似桶排 先看有多少头奶牛抽出这个数 再看这个数的奶牛能拍多少人的头(别忘了-1,自己不能拍自己) 最后根据输入输出 110ms #include<bits/stdc++.h> using ...
- Linux相关网络命令大全 网络接口 域名分析
Linux网络设置一.查看网络接口信息ifconfig① 查看所有活动的网络接口信息② 查看指定网络接口信息补充二.查看主机名称hostname① hostname命令② 永久设置主机名三.查看路由表 ...
- M1卡分类
M1卡复制前文说到,每一张M1卡的0扇区0块都是出厂时厂商赋予的绝对地址块,我们无法在M1卡内直接修改它. 说到这不得不提一下M1卡的复制子卡--UID卡,FUID卡,CUID卡. UID卡UID卡是 ...
- 浅析vue-cli脚手架命令的执行过程
上一篇文章,已经大致了解脚手架是什么以及脚手架是如何工作的.接下来,稍微深入一下脚手架的工作过程(以vue-cli为例).首先抛出3个问题: 1.明明全局安装的是@vue/cli,最后执行的命令却是v ...
- 解决远程连接服务器数据库报错:Host ‘XXXXXX’ is blocked because of many connection errors
参考:https://blog.csdn.net/li_li_lin/article/details/72764683和 https://blog.csdn.net/zaishijizhidian/a ...
- ThinkPHP3.2.3使用PHPExcel类操作excel导出excel
如何导入excel请看:ThinkPHP3.2.3使用PHPExcel类操作excel导入读取excel // 引入PHPExcel类 import("Org.Util.PHPExccel& ...
- 计算机基础-Socket
计算机基础-Socket 当时明月在,曾照彩云归. 简介:计算机基础-Socket 一.I/O 模型 一个输入操作通常包括两个阶段: 等待数据准备好 从内核向进程复制数据 对于一个套接字上的输入操作, ...