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

前言

最近一直被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());
    }

  /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     */
    private static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.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)...
 }

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容