适配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 ...
随机推荐
- AcWing 829. 模拟队列
实现一个队列,队列初始为空,支持四种操作: (1) "push x" – 向队尾插入一个数x: (2) "pop" – 从队头弹出一个数: (3) " ...
- SpringBoot | 1.3 约定编程Spring AOP
前言 前面聊过Spring的一个很重要的概念,IoC控制反转,接下来就是AOP了: 1. AOP切面编程 面向切面编程,是利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度 ...
- Spring:Spring优势——分层架构简介
Spring框架采用分层架构,根据不同的功能被划分成了多个模块,这些模块大体可分为 Data Access/Integration.Web.AOP.Aspects.Messaging.Instrume ...
- Java:Java实例化(new)过程
实例化过程(new) 1.首先去JVM 的方法区中区寻找类的class对象,如果能找到,则按照定义生成对象,找不到 >>如下2.所示 2.加载类定义:类加载器(classLoader)寻找 ...
- 使用RestTemplate发送post请求,请求头中封装参数
最近使用RestTemplate发送post请求,遇到了很多问题,如转换httpMessage失败.中文乱码等,调了好久才找到下面较为简便的方法: RestTemplate restTemplate ...
- 大数据-Hadoop安装
进入到Hadoop安装包路径下 [atguigu@hadoop101 ~]$ cd /opt/software/ 解压安装文件到/opt/module下面 [atguigu@hadoop101 sof ...
- FA转发地址
1.FA地址诞生背景和作用 FA 是Forwarding Address的简写.FA是ASBR通告的TYPE 5 LSA中的字段,它的作用是告诉OSPF域内的路由器如何能够更快捷地到达LSA 5所通告 ...
- python使用selenium,webdriver自动下载百度网盘内容
想实现一个自动下载微信公众号分享百度网盘图片链接的爬虫,使用selenium和火狐的webdriver进行完成 1.首先根据自己的浏览器下载相应的webdriver驱动器,python中导入selen ...
- W: GPG 错误:http://mirrors.aliyun.com xenial/mongodb-org/3.2 Release: 由于没有公钥,无法验证下列签名: NO_PUBKEY D68FA50FEA312927
更新错误: 正在读取软件包列表... 完成 W: GPG 错误:http://mirrors.aliyun.com xenial/mongodb-org/3.2 Release: 由于没有公钥,无法验 ...
- GDB常用命令整理
(gdb) break xxx (gdb) b xxx 在源代码指定的某一行设置断点,其中 xxx 用于指定具体打断点的位置. (gdb) run (gdb) r 执行被调试的程序,其会自动在第一个断 ...