适配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 ...
随机推荐
- ptm经验总结
- HADOOP及SPARK安装步骤及问题解决
说明:主节点IP:192.168.35.134 主机名:master 从节点slave1 IP: 192.168.35.135 主机名:slave1 从节点slave2 IP: 192.168 ...
- Vue3 + Cesium + Typescript 集成搭建的快速启动模板(包含示例数据)
开门见山 项目地址:https://github.com/tanghaojie/vue3-cesium-typescript-start-up-template 好用的话给个star呗,有更新可以第一 ...
- STM32中的GPIO笔记
1.GPIO是STM32可控制的引脚,STM32的GPIO被分成很多组,每组有16个引脚.每个GPIO端口包含:2个32位配置寄存器(CRL.CRH),2个32位数据寄存器(IDR.ODR),1个32 ...
- vue(17)vue-route路由管理的安装与配置
介绍 Vue Router 是 Vue.js官方的路由管理器.它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌.包含的功能有: 嵌套的路由/视图表 模块化的.基于组件的路由配置 路由参 ...
- C语言:地址
一切都是地址 C语言用变量来存储数据,用函数来定义一段可以重复使用的代码,它们最终都要放到内存中才能供 CPU 使用.数据和代码都以二进制的形式存储在内存中,计算机无法从格式上区分某块内存到底存储的是 ...
- python_字典列表嵌套的排序问题
上一篇我们聊到python 字典和列表嵌套用法,这次我们聊聊字典和列表嵌套中的排序问题,这个在python基础中不会提到,但实际经常运用,面试中也喜欢问,我们娓娓道来. 在说组合排序之前,先来看看排序 ...
- 虚拟机安装RHEL8.0.0
在VMware Workstations 15.0.0中安装RHEL8.0.0 使用到的软件和主机基本配置 此处宿主机基本硬件配置:i3-7100U 4核,内存:12G 虚拟化软件:VMware Wo ...
- string子串匹配(用string自带函数,不涉及char数组转换)
using namespace std; #include <iostream> #include<string> //第1种,用string自带的s.subdtr()截取任意 ...
- Nexus Repository Manager 3 远程命令执行漏洞(CVE-2019-7238)
poc地址 https://github.com/magicming200/CVE-2019-7238_Nexus_RCE_Tool