美文网首页
Android10以上拍照和选择相册适配以及向下兼容适配

Android10以上拍照和选择相册适配以及向下兼容适配

作者: 程序猿峰岑 | 来源:发表于2020-07-22 14:36 被阅读0次

    前言

    最近一直被Android10相关的适配搞得焦头烂额,之前也听说过android10中的所有的视频,音频以及图片等资源为了统一的管理,统一放在一个共有的文件下,也就是所谓的沙箱。看了一下其他人写的文章,也提供了一种简单粗暴的方式也就是在清单文件application下配置#android:requestLegacyExternalStorage="true"#,但如果android11出来又得适配了,android11是禁止除了共有文件夹下新建视频,音频以及图片等文件。不然就会报找不到文件相关的错误,好了说到底我们还是得花点时间去适配android10以上的沙箱适配。

    进入正题,接下来我们就来适配Android10相关的拍照以及选择相册并兼容android10以下的绝大部分的适配。

    相册相关的适配

    进入系统相册的操作都是一样的这里就不赘述:

    private fun openGallery() {
         val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
          startActivityForResult(intent, OPEN_GALLERY_PIC_CODE)
    }
    

    主要的还是要对返回的data进行处理,也就是把uri转换成文件File的路径path

    适配AndroidQ及以上uri转path

       /**
         * 适配Android 4.4及以上,根据uri获取图片的绝对路径
         *
         * @param context 上下文对象
         * @param uri     图片的Uri
         * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
         */
        @SuppressLint("NewApi")
        public static String getRealPathFromUriAboveApiAndroidK(Context context, Uri uri) {
            String filePath = null;
            if (DocumentsContract.isDocumentUri(context, uri)) {
                // 如果是document类型的 uri, 则通过document id来进行处理
                String documentId = DocumentsContract.getDocumentId(uri);
                if (isMediaDocument(uri)) {
                    // 使用':'分割
                    String id = documentId.split(":")[1];
                    String selection = MediaStore.Images.Media._ID + "=?";
                    String[] selectionArgs = {id};
                    filePath = getDataColumn(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, selectionArgs);
                } else if (isDownloadsDocument(uri)) {
                    Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(documentId));
                    filePath = getDataColumn(context, contentUri, null, null);
                }
            } else if ("content".equalsIgnoreCase(uri.getScheme())) {
                // 如果是 content 类型的 Uri
                filePath = getDataColumn(context, uri, null, null);
            } else if ("file".equals(uri.getScheme())) {
                // 如果是 file 类型的 Uri,直接获取图片对应的路径
                filePath = uri.getPath();
            }
            return filePath;
        }
    

    适配android KITKAT及以上uri转path

    /**
         * 适配Android 4.4及以上,根据uri获取图片的绝对路径
         *
         * @param context 上下文对象
         * @param uri     图片的Uri
         * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
         */
        @SuppressLint("NewApi")
        public static String getRealPathFromUriAboveApiAndroidK(Context context, Uri uri) {
            String filePath = null;
            if (DocumentsContract.isDocumentUri(context, uri)) {
                // 如果是document类型的 uri, 则通过document id来进行处理
                String documentId = DocumentsContract.getDocumentId(uri);
                if (isMediaDocument(uri)) {
                    // 使用':'分割
                    String id = documentId.split(":")[1];
                    String selection = MediaStore.Images.Media._ID + "=?";
                    String[] selectionArgs = {id};
                    filePath = getDataColumn(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, selectionArgs);
                } else if (isDownloadsDocument(uri)) {
                    Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(documentId));
                    filePath = getDataColumn(context, contentUri, null, null);
                }
            } else if ("content".equalsIgnoreCase(uri.getScheme())) {
                // 如果是 content 类型的 Uri
                filePath = getDataColumn(context, uri, null, null);
            } else if ("file".equals(uri.getScheme())) {
                // 如果是 file 类型的 Uri,直接获取图片对应的路径
                filePath = uri.getPath();
            }
            return filePath;
        }
    

    适配android KITKAT及以下uri转path

    /**
         * 适配Android 4.4以下(不包括api19),根据uri获取图片的绝对路径
         *
         * @param context 上下文对象
         * @param uri     图片的Uri
         * @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
         */
        public static String getRealPathFromUriBelowApiAndroidK(Context context, Uri uri) {
            return getDataColumn(context, uri, null, null);
        }
    
    /**
         * Get the value of the data column for this Uri. This is useful for
         * MediaStore Uris, and other file-based ContentProviders.
         *
         * @param context       The context.
         * @param uri           The Uri to query.
         * @param selection     (Optional) Filter used in the query.
         * @param selectionArgs (Optional) Selection arguments used in the query.
         * @return The value of the _data column, which is typically a file path.
         */
        private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
    
            Cursor cursor = null;
            final String column = "_data";
            final String[] projection = {column};
            try {
                cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
                if (cursor != null && cursor.moveToFirst()) {
                    final int column_index = cursor.getColumnIndexOrThrow(column);
                    return cursor.getString(column_index);
                }
            } finally {
                if (cursor != null)
                    cursor.close();
            }
            return null;
        }
    

    检查Uri

    /**
         * @param uri the Uri to check
         * @return Whether the Uri authority is MediaProvider
         */
        private static boolean isMediaDocument(Uri uri) {
            return "com.android.providers.media.documents".equals(uri.getAuthority());
        }
    
        /**
         * @param uri the Uri to check
         * @return Whether the Uri authority is DownloadsProvider
         */
        private static boolean isDownloadsDocument(Uri uri) {
            return "com.android.providers.downloads.documents".equals(uri.getAuthority());
        }
    

    Bitmap转Uri Android10以上适配

      /**
         * copy或者下载文件到公有目录,保存Bitmap同理,如Download,MIME_TYPE类型可以自行参考对应的文件类型
         * @param context      context
         * @param bitmap   源文件
         * @param fileName 保存的文件名
         */
        public static Uri copyToDownloadAndroidQ(Context context, Bitmap bitmap, String fileName){
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
            ContentValues values = new ContentValues();
            values.put(MediaStore.Downloads.DISPLAY_NAME, fileName);
            values.put(MediaStore.Images.Media.TITLE, fileName);
            values.put(MediaStore.MediaColumns.MIME_TYPE, "images/*");
            values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
            ContentResolver resolver = context.getContentResolver();
            Uri insertUri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
            if(insertUri == null) {
                return null;
            }
            InputStream is = null;
            OutputStream os = null;
            try {
                os = resolver.openOutputStream(insertUri);
                if(os == null){
                    return null;
                }
                int read;
                is = new ByteArrayInputStream(baos.toByteArray());
                byte[] buffer = new byte[1024*1024];
                while ( (read = is.read(buffer)) != -1){
                    os.write(buffer,0,read);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                close(is,os);
            }
            return insertUri;
        }
    
        private static void close(InputStream is ,OutputStream os){
            if (is != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            if (os != null){
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
    

    图片转换成uri然后转化成文件路径

    /**
         * 专为Android4.4设计的从Uri获取文件绝对路径
         */
        public static String getPath(final Context context, final Uri uri) {
    
            final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
            // DocumentProvider
            if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
                // ExternalStorageProvider
                if (isExternalStorageDocument(uri)) {
                    final String docId = DocumentsContract.getDocumentId(uri);
                    final String[] split = docId.split(":");
                    final String type = split[0];
    
                    if ("primary".equalsIgnoreCase(type)) {
                        return Environment.getExternalStorageDirectory() + "/" + split[1];
                    }
                }
                // DownloadsProvider
                else if (isDownloadsDocument(uri)) {
    
                    final String id = DocumentsContract.getDocumentId(uri);
                    final Uri contentUri = ContentUris.withAppendedId(
                            Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
    
                    return getDataColumn(context, contentUri, null, null);
                }
                // MediaProvider
                else if (isMediaDocument(uri)) {
                    final String docId = DocumentsContract.getDocumentId(uri);
                    final String[] split = docId.split(":");
                    final String type = split[0];
    
                    Uri contentUri = null;
                    if ("image".equals(type)) {
                        contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                    } else if ("video".equals(type)) {
                        contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                    } else if ("audio".equals(type)) {
                        contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                    }
    
                    final String selection = "_id=?";
                    final String[] selectionArgs = new String[]{split[1]};
    
                    return getDataColumn(context, contentUri, selection, selectionArgs);
                }
            }
            // MediaStore (and general)
            else if ("content".equalsIgnoreCase(uri.getScheme())) {
                return getDataColumn(context, uri, null, null);
            }
            // File
            else if ("file".equalsIgnoreCase(uri.getScheme())) {
                return uri.getPath();
            }
            return null;
        }
    
     /**
         * Get the value of the data column for this Uri. This is useful for
         * MediaStore Uris, and other file-based ContentProviders.
         *
         * @param context       The context.
         * @param uri           The Uri to query.
         * @param selection     (Optional) Filter used in the query.
         * @param selectionArgs (Optional) Selection arguments used in the query.
         * @return The value of the _data column, which is typically a file path.
         */
        private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
    
            Cursor cursor = null;
            final String column = "_data";
            final String[] projection = {column};
            try {
                cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
                if (cursor != null && cursor.moveToFirst()) {
                    final int column_index = cursor.getColumnIndexOrThrow(column);
                    return cursor.getString(column_index);
                }
            } finally {
                if (cursor != null)
                    cursor.close();
            }
            return null;
        }
    

    适配Bitmap转uri转换成文件路径最终转换成文件File

     public static File getFile(Bitmap bitmap, Context context) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
                String fileName = "/DCIM/CATCH/" + System.currentTimeMillis() + ".jpg";
                Uri  uri = Android10FileUtils.copyToDownloadAndroidQ(context,bitmap,fileName);
                String path = FileUtils.getPath(context,uri);
                File file = new File(path);
                if (!file.exists()){
                    file.mkdirs();
                }
                return file;
            }else{
                Uri uri = Uri.parse(MediaStore.Images.Media.insertImage(context.getContentResolver(), bitmap, null,null));
                return  FileUtils.uriToFile(uri,context);
            }
        }
    
     public static File uriToFile(Uri uri, Context context) {
            String mediaStoreData = "";
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                mediaStoreData = MediaStore.Images.ImageColumns.RELATIVE_PATH;
            } else {
                mediaStoreData = MediaStore.Images.ImageColumns.DATA;
            }
            String path = null;
            if ("file".equals(uri.getScheme())) {
                path = uri.getEncodedPath();
                if (path != null) {
                    path = Uri.decode(path);
                    ContentResolver cr = context.getContentResolver();
                    StringBuffer buff = new StringBuffer();
                    buff.append("(").append(mediaStoreData).append("=").append("'" + path + "'").append(")");
                    Cursor cur = cr.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[]{MediaStore.Images.ImageColumns._ID,
                            mediaStoreData
                    }, buff.toString(), null, null);
                    int index = 0;
                    int dataIdx = 0;
                    for (cur.moveToFirst(); !cur.isAfterLast(); cur.moveToNext()) {
                        index = cur.getColumnIndex(MediaStore.Images.ImageColumns._ID);
                        index = cur.getInt(index);
    
                        dataIdx = cur.getColumnIndex(mediaStoreData);
                        path = cur.getString(dataIdx);
                    }
                    cur.close();
                    if (index == 0) {
                    } else {
                        Uri u = Uri.parse("content://media/external/images/media/" + index);
                        System.out.println("temp uri is :" + u);
                    }
                }
                if (path != null) {
                    File file = new File(path);
                    if (!file.exists()){
                        file.mkdirs();
                    }
                    return file;
                }
            } else if ("content".equals(uri.getScheme())) {
                // 4.2.2以后
                String[] proj = {mediaStoreData};
                Cursor cursor = context.getContentResolver().query(uri, proj, null, null, null);
                if (cursor.moveToFirst()) {
    
                    int columnIndex = cursor.getColumnIndexOrThrow(mediaStoreData);
                    path = cursor.getString(columnIndex);
                }
                cursor.close();
    
                File file = new File(path);
                if (!file.exists()){
                    file.mkdirs();
                }
                return file;
            } else {
                //Log.i(TAG, "Uri Scheme:" + uri.getScheme());
            }
            return null;
        }
    

    当然了拍照和位图Bitmap相关的适配是一样的。

    7.0以上Android版本的路径配置
    第一步

     <provider
                android:name="androidx.core.content.FileProvider"
                android:authorities="[包名].fileprovider"
                android:exported="false"
                android:grantUriPermissions="true">
                <meta-data
                    android:name="android.support.FILE_PROVIDER_PATHS"
                    android:resource="@xml/file_camera_paths" />
            </provider>
    

    @xml/file_camera_paths 文件配置

    <paths xmlns:android="http://schemas.android.com/apk/res/android">
        <!--代表外部存储区域的根目录下的文件 Environment.getExternalStorageDirectory()/DCIM/camerademo目录-->
        <external-path name="hm_DCIM" path="." />
        <!--代表外部存储区域的根目录下的文件 Environment.getExternalStorageDirectory()/Pictures/camerademo目录-->
        <external-path name="hm_Pictures" path="Pictures/camerademo" />
        <!--代表app 私有的存储区域 Context.getFilesDir()目录下的images目录 /data/user/0/com.hm.camerademo/files/images-->
        <files-path name="hm_private_files" path="images" />
        <!--代表app 私有的存储区域 Context.getCacheDir()目录下的images目录 /data/user/0/com.hm.camerademo/cache/images-->
        <cache-path name="hm_private_cache" path="images" />
        <!--代表app 外部存储区域根目录下的文件 Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)目录下的Pictures目录-->
        <!--/storage/emulated/0/Android/data/com.hm.camerademo/files/Pictures-->
        <external-files-path name="hm_external_files" path="Pictures" />
        <!--代表app 外部存储区域根目录下的文件 Context.getExternalCacheDir目录下的images目录-->
        <!--/storage/emulated/0/Android/data/com.hm.camerademo/cache/images-->
        <external-cache-path name="hm_external_cache" path="images" />
    
        <root-path path="" name="camera_photos" />
    
    </paths>
    

    拍照相关的配置

    拍照首先需要申请拍照权限
    申请完后调用camera就可以拍照,拍照主要就是在调用相机的时需要自己去配置文件存储路径。

    private fun openCamera(){
        Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE)
      captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, getPhotoUri())
      captureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
      startActivityForResult(captureIntent, CAMERA_REQUEST_CODE)
    }
    
    private fun getPhotoUri():Uri{
           return  if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.Q){
                    getUriAboveAndroidQ()
            }else{
                getUriBelowAndroidQ()
          }
    }
    
    
    // 适配android10 创建文件
        private fun getUriAboveAndroidQ(): Uri? {
            val contentValues = ContentValues()
            val fileName = "/IMG_" + System.currentTimeMillis() + ".jpg"
            contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
            contentValues.put(MediaStore.Images.Media.RELATIVE_PATH,   "DCIM/Pictures")
            contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/JPEG")
            return contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
        }
    
    //适配Android10版本一下的uri配置
    private fun getUriBelowAndroidQ():Uri{
       val photoUri = if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
                FileProvider.getUriForFile(this, "$packageName.fileprovider", getFilePath())
            }else{
                Uri.fromFile(getFilePath())
            }
        return photoUri
    }
    
      //向下兼容
        private fun getFilePath(): File? {
            return File(
                checkDirPath(Environment.getExternalStorageDirectory().path + "/DCIM/CATCH/"),
                System.currentTimeMillis().toString() + ".jpg"
            )
        }
    
    
        /**
         * 检查文件是否存在
         */
        private fun checkDirPath(dirPath: String?): String? {
            if (TextUtils.isEmpty(dirPath)) {
                return ""
            }
            val dir = File(dirPath)
            if (!dir.exists()) {
                dir.mkdirs()
            }
            return dirPath
        }
    

    回调处理这里我就不赘述了 主要是对Android10以上关于图片配置相关的处理
    既然关于Android 10那么我们也讲一下关于定位方面的适配,Android10以上需要在清淡文件中添加一个新的定位权限

    <!--定位权限,Android 10 新增后台定位权限-->
        <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
    
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
    RxPermissions(this).request(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION)...
    }else{
       RxPermissions(this).request(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION)...
     }
    
    

    好了 拍照和选择相册图片相关的适配也就展示完毕,顺便把定位相关的适配也讲解了下,如果对你有帮助,以及不理解或者有更好的方法都可以留言。共同学习共同进步。

    相关文章

      网友评论

          本文标题:Android10以上拍照和选择相册适配以及向下兼容适配

          本文链接:https://www.haomeiwen.com/subject/fmaakktx.html