这篇文章主要介绍了适配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