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

  demo源码

在文件清单AndroidManifest.xml中添加权限:

1 <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><!-- 储存卡的读权限 -->
2 ="android.permission.WRITE_EXTERNAL_STORAGE"  储存卡的写权限 3 ="android.permission.CAMERA"  调用相机权限 -->

在官方7.0的以上的系统中,尝试传递 file://URI可能会触发FileUriExposedException,使用FileProvider来共享文件,AndroidManifest.xml:

application
        ...
        <!-- 兼容Android7.0拍照闪退 -->
        provider
            ="androidx.core.content.FileProvider"
            android:authorities="com.example.camera.test"
            android:exported="false"
            android:grantUriPermissions="true">
            meta-data
                ="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
    android:orientation="vertical"
    tools:context=".MainActivity">

    ImageView
        android:id="@+id/image"
        android:layout_width="250dp"
        android:layout_height
        android:layout_marginTop="20dp"
        android:layout_gravity="center_horizontal"/>

    TextView
        ="@+id/tv_camera"="wrap_content"="50dp"
        android:text="相机"
        android:textSize="18sp"
        android:textColor="#FFF"
        android:padding="10dp"
        android:background="#1878FF"
        android:layout_marginHorizontal
        android:gravity="@+id/tv_album"="相册"/>

LinearLayout>

接下来是主页面的代码:

获取控件,对两个按钮添加点击监听,判断权限:

    
    private ImageView image;
private TextView tvCamera,tvAlbum;
    @Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); } private 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 onClick(View v) { //相机 if ((ContextCompat.checkSelfPermission(context,Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) && (ContextCompat.checkSelfPermission(context,Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED)) { 权限都齐的情况下,跳转相机 openCamera(); } else { if (activity != null) { 请求权限 ActivityCompat.requestPermissions(activity, String[]{ Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_EXTERNAL_STORAGE },PHOTO_REQUEST_CAMERA); } } } }); tvAlbum.setOnClickListener(相册 权限都齐的情况下,跳转相册 openAlbum(); } String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE,PHOTO_REQUEST_ALBUM); } } } }); }

权限申请回调:

    @Override
    void onRequestPermissionsResult(int requestCode,@NonNull String[] permissions,@NonNull int[] grantResults) {
        .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();
                    }  {
                        无权限提示
                        Toast.makeText(context,"权限未通过",Toast.LENGTH_SHORT).show();
                    }
                }
                break;
             PHOTO_REQUEST_ALBUM:
                跳转相册
                        openAlbum();
                    } ;
        }
    }

跳转相机:

    private  openCamera(){
        Intent intent =  Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        判断是否有相机
        null && context != null && intent.resolveActivity(activity.getPackageManager()) != ){
            File file;
            Uri uri =  (isAndroidQ){
                适配Android10
                uri = createImageUri(context);
            }  {
                Android10以下
                file = createImageFile(context);
                if (file != ){
                    Android10以下
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
                        适配Android7.0文件权限
                        uri = FileProvider.getUriForFile(context,"com.example.camera.test" {
                        uri = Uri.fromFile(file);
                    }
                }
            }
            imageUri = uri;
            Log.e(TAG,"相机保存的图片Uri:" + imageUri);
            if (uri != ){
                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 =  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卡
         (status.equals(Environment.MEDIA_MOUNTED)){
            return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,contentValues);
        }  {
             context.getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI,contentValues);
        }
    }

Android 10以下的返回一个file来保存拍照后的图片:

 File createImageFile(@NonNull Context context){
        File file = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        null && !file.exists()){
             (file.mkdir()){
                Log.e(TAG,"文件夹创建成功");
            }  {
                Log.e(TAG,"file为空或者文件夹创建失败");
            }
        }
        File tempFile =  File(file,SAVE_AVATAR_NAME);
        Log.e(TAG,"临时文件路径:" + tempFile.getAbsolutePath());
        if (!Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(tempFile))){
            return ;
        }
         tempFile;
    }

跳转相册:

 openAlbum(){
        Intent intent = new Intent(Intent.ACTION_PICK,1)">);
        intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,1)">);
        startActivityForResult(intent,ALBUM_REQUEST_CODE);
    }

跳转裁剪,裁剪在相机拍照后跳转,用一个file来加载:

 openCrop(Uri uri){
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) && context != ){
            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,1)"> 设置裁剪
        intent.putExtra("crop","true" aspectX aspectY 是宽高的比例
        intent.putExtra("aspectX",1);
        intent.putExtra("aspectY",1)"> 裁剪后输出图片的尺寸大小
        intent.putExtra("outputX",250);
        intent.putExtra("outputY",1)">适配Android10,存放图片路径
        intent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(file));
         图片格式
        intent.putExtra("outputFormat","PNG");
        intent.putExtra("noFaceDetection",1)">true); 取消人脸识别
        intent.putExtra("return-data",1)"> true:不返回uri,false:返回uri
        startActivityForResult(intent,TAILOR_REQUEST_CODE);
    }

跳转相机、相册和裁剪的回调,如果有上传需求的,直接上传代码中的file即可:

protected void onActivityResult( resultCode,@Nullable Intent data) {
        .onActivityResult(requestCode,resultCode,data);
        if (resultCode == -1){
            回调成功
             (requestCode) {
                 CAMERA_REQUEST_CODE:
                    相机回调
                    Log.e(TAG,"相机回调");
                     (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                        照片裁剪
                        openCrop(imageUri);
                    }  {
                        Toast.makeText(context,"未找到存储卡";
                 ALBUM_REQUEST_CODE:
                    相册回调
                    Log.e(TAG,"相册回调"if (data != null && data.getData() != ) {
                        image.setImageURI(data.getData());
                        如果需要上传操作的可以使用这个方法
                        File file = FileUtils.uriToFile(data.getData(),context);
                        这里的file就是需要上传的图片了
                    }
                     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;
                    ;
            }
        }  {
            Toast.makeText(context,"取消"

相关文章

Android 如何解决dialog弹出时无法捕捉Activity的back事件 在...
Android实现自定义带文字和图片的Button 在Android开发中经常...
Android 关于长按back键退出应用程序的实现最近在做一个Andr...
android自带的时间选择器只能精确到分,但是对于某些应用要求...