适配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 1127. 香甜的黄油
农夫John发现了做出全威斯康辛州最甜的黄油的方法:糖. 把糖放在一片牧场上,他知道 N 只奶牛会过来舔它,这样就能做出能卖好价钱的超甜黄油. 当然,他将付出额外的费用在奶牛上. 农夫John很狡猾, ...
- Java运算中的类型转换
类型转换 运算中,不同类型的数据先转化为同一类型,然后进行运算 public class Dome04 { public static void main(String[] args) { //int ...
- ROS2学习之旅(20)——创建一个动作消息
本文用来自定义一个动作消息类型. 以下命令用来创建一个工作空间并建立一个功能包: mkdir -p action_ws/src cd action_ws/src ros2 pkg create act ...
- 懂得分享 Linux 配置NFS共享服务
部署YUM仓库及NFS共享服务一.YUM概述 YUM (Yellow dog Updater Modified)二.准备安装源 ① 软件仓库的提供方式 ② RPM软件包的来源 ...
- 【LeetCode】137. 只出现一次的数字 II(剑指offer 56-II)
137. 只出现一次的数字 II(剑指offer 56-II) 知识点:哈希表:位运算 题目描述 给你一个整数数组 nums ,除某个元素仅出现 一次 外,其余每个元素都恰出现 三次 .请你找出并返回 ...
- Scala学习——基础入门
基础语法 1)注意点 1)区分大小写 - Scala是大小写敏感的,这意味着标识Hello 和 hello在Scala中会有不同的含义. 2)类名 - 对于所有的类名的第一个字母要大写. 3)方法名 ...
- Innodb 锁的介绍
如下博文是参考如下博文内容,再加整理. http://blog.chinaunix.net/uid-24111901-id-2627857.html http://blog.csdn.net/wang ...
- Mac OS系统安装pymssql 报错
Mac OS系统安装pymssql 一开始报错:缺少Cython,于是pip install Cython 然后还是报错:解决办法如下操作: 首先安装freetds 在 FreeTDS stable ...
- Linux符号描述
& 表示任务在后台执行,如要在后台运行redis-server,则有 redis-server &&& 表示前一条命令执行成功时,才执行后一条命令 ,如 echo ...
- macOS下将可执行文件索引位置增添到PATH中
一.shell中可执行文件的两种执行方式 (1)绝对路径 比如,打开电脑上安装的python3,使用绝对路径方式打开为: /usr/local/bin/python3 (2)使用PATH 将pytho ...