这篇文章主要介绍了适配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也没办法拿到外部文件的绝对路径了,网上给出的方法就是将共享文件复制到沙盒目录下,然后再进行文件操作。话不多说,上代码。

  demo源码

在文件清单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 拍照,相册,裁剪,上传图片的更多相关文章

  1. Android SDK4/5/6/7,相册、拍照及裁剪功能及遇见的坑

    保存照片和视频到系统相册显示- http://blog.csdn.net/chendong_/article/details/52290329 Android 7.0 之拍照与图片裁剪适配-http: ...

  2. android 拍照 相册 剪切以及显示功能

    一.概述 android的 图片拍照 ,相册选图,以及图片剪切功能可以说非常常用. 尤其是图片上传功能,必然用到此功能. 而公司最近的一个项目中正好用到该功能. 记录下来以便以后再次用到,直接拿来使用 ...

  3. [Android实例教程] 教你如何拍照+相册选择图片+剪裁图片完整实现

    [Android实例教程] 教你如何拍照+相册选择图片+剪裁图片完整实现 今天做Android项目的时候要用到图片选择,要实现拍照获取图片和从相册获取图片,并且要求在获取完之后可以裁剪,试了很多方法之 ...

  4. 手机调用系统的拍照和裁剪功能,假设界面有输入框EditText,在一些手机会出现点击EditText会弹出输入法,却不能输入的情况。

    1. 拍照裁剪后 点击EditText会弹出输入法,却不能输入.可是点击点一EdtiText就能够输入了,所以我就写了一个看不见的EdtiText,切换焦点,这样就攻克了这个奇怪的这问题,应该是and ...

  5. 真正可用的安卓webview html图片上传限制突破处理(拍照+相册都可以用)

    两篇起步使用webview参考文章,第一篇解除限制,但会调用外部浏览器打开链接,第二篇 覆盖shouldOverrideUrlLoading return true https://www.jb51. ...

  6. android 开发 实现一个进入相机拍照后裁剪图片或者进入相册选中裁剪图片的功能

    实现思维路径: 以进入相机拍照的思维路线为例子: 1.进入app 2.判断之前是否保存头像,如果有就显示历史图像 (下面代码中在getOldAvatar();方法中执行这个逻辑) 3.点击更换图像的B ...

  7. 3.PopupWindow 、拍照、裁剪

    实现这样的效果 圆角图片的自定义控件直接拷进来,和com一个等级 想要弹出内容可以使用悬浮窗 layout_pupup <LinearLayout xmlns:android="htt ...

  8. PopupWindow 以及拍照、裁剪

    实现这样的效果 圆角图片的自定义控件直接拷进来,和com一个等级 想要弹出内容可以使用悬浮窗 layout_pupup <LinearLayout xmlns:android="htt ...

  9. mono for android 获取手机照片或拍照并裁剪保存

    axml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android ...

随机推荐

  1. 【转载】CentOS-Docker安装MongoDB(单点)

    下载镜像 $ docker pull mongo 创建相关目录 $ mkdir -p /usr/mongo/data /usr/mongo/dump 运行镜像 $ docker run --resta ...

  2. MyBatis:条件构造器QueryWrapper方法详解

    QueryWrapper 说明:      继承自 AbstractWrapper ,自身的内部属性 entity 也用于生成 where 条件及 LambdaQueryWrapper, 可以通过 n ...

  3. 23 shell 进程替换

    0.shell进程替换的用法 1.使用进程替换的必要性 2.进程替换的本质 进程替换和命令替换非常相似.命令替换是把一个命令的输出结果赋值给另一个变量,例如dir_files=`ls -l`或date ...

  4. 题解 guP1948 【[USACO08JAN]电话线Telephone Lines】

    二分+dij题目 首先读一遍题目,一定要仔细读(不要问我为什么,我就是受害者qwq 你会发现最终的费用是由最长的电话线决定的,而非电话线长度和. 至此就有了一个基本思路--枚举(二分)出可能的最长电话 ...

  5. CF277E Binary Tree on Plane

    CF277E Binary Tree on Plane 题目大意 给定平面上的 \(n\) 个点,定义两个点之间的距离为两点欧几里得距离,求最小二叉生成树. 题解 妙啊. 难点在于二叉的限制. 注意到 ...

  6. 如何监控 Log4j2 异步日志遇到写入瓶颈

    如何监控 Log4j2 异步日志遇到写入瓶颈 在之前的一篇文章中(一次鞭辟入里的 Log4j2 异步日志输出阻塞问题的定位),我们详细分析了一个经典的 Log4j2 异步日志阻塞问题的定位,主要原因还 ...

  7. 前端开发入门到进阶第三集【js进行url解析】

    https://www.cnblogs.com/yuanzhiguo/p/8241644.html

  8. elasticsearch常见错误及解决方案

    1.OpenJDK 64-Bit Server VM warning: If the number of processors is expected to increase from one, th ...

  9. Leetcode6. Z 字形变换

    > 简洁易懂讲清原理,讲不清你来打我~ 输入字符串,按下右上下右上排列后输出字符串![在这里插入图片描述](https://img-blog.csdnimg.cn/4578280a7c1848c ...

  10. Kafka之--自动启动zookeeper & kafka 脚本

    1) 首先配置SSH免密登录,在这里我用kafka(151)这台机器来作为启动脚本的存放和执行机器 [root@kafaka3 .ssh]# pwd #生成SSH KEY /root/.ssh [ro ...