Android Q 保存图片至系统相册填坑指南

开发中,我们一般调用```MediaStore.Images.Media.insertImage```将app图片导出到系统相册,实践过程中,有2个埋点坑:

1、保存后相册无刷新
这里保存后,需要发广播告知系统刷新相册,全部代码下面会给出
2、 MediaStore.Images.Media.insertImage保存会生成两张图
需要使用ContentValues向系统插入,见下面的代码
3、android 10.0以上版本兼容
10.0之后,Android采取了更为严格的沙盒模式,限定了每个app的私有目录的使用,如果用户使用app私有目录,则无需动态申请存储读和写权限,具体来说:
· APP 卸载后,目录数据会清除;
· APP 访问自己的 App-specific 目录时无需任何权限;
· 可以使用FileProvider分享使用自己私有目录的文件。

ps:
内部存储(InternalStorage),目录路径即:`data/data/包名`,用户一般无法直接访问,其下分为`shared_prefs`、`databases`、`files`和`cache`四部分;
外部存储(但是打开手机文件管理器之后,国内系统一般给它命名为“内部存储”,容易混淆)是系统允许我们操作的目录结构,不同厂家命名不同,一般就是`storage`或`mnt`,目录路径是:`/storage/emulated/0`,(注:KitKat之后的版本不再支持用户对外置SDcard(Secondary Storage)的写入等操作,如果用户想要将文件等copy到手机中,则只能存储到内部存储器中,而无法存储到外置sdcard中)。外部存储有9大共有目录(表中第一条是外部存储路径):

Environment.getExternalStorageDirectory() /storage/sdcard/0
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_ALARMS) /storage/sdcard/0/Alarms
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) /storage/sdcard/0/DCIM
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS /storage/sdcard/0/Download
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES) /storage/sdcard/0/Movies
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC) /storage/sdcard/0/Music
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_NOTIFICATIONS) /storage/sdcard/0/Notifications
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) /storage/sdcard/0/Pictures
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PODCASTS) /storage/sdcard/0/Podcasts
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES) /storage/sdcard/0/Ringtones

这里有个小技巧,即和App有关的路径,都是Context调用的方法,而与App不相关的则调用Environment来调用;
通常,并不建议将app自己的数据直接放在上面的共有目录,而是放在app私有目录下,也就是上面所说的app私有目录路径:`/storage/emulated/0/Android/data/包名/`,关于这些,可以参考我整理好的工具类库下面的SDCardUtils和PathUtils。
今天的主题就是将app私有目录下的图片导出到系统相册中:
10.0之后,getExternalStorageDirectory()方法被废弃,谷歌建议使用`Context`的`getExternalFilesDir`方法代替,并且这个方法访问的目录无需动态申请存储权限,而`getExternalStorageDirectory`返回的路径则可能无法直接访问。
下面是全部代码,代码中需要开辟新的线程,使用到了工具类ThreadPoolUtils

private void saveBitmap2Gallery(CustomCommonPromptDialog dialog, Bitmap bitmap) {
        ThreadPoolUtils.executeByIo(new ThreadPoolUtils.Task() {
            @Nullable
            @Override
            public Object doInBackground() throws Throwable {
                String fileName = "xiangke_" + System.currentTimeMillis() + ".jpg";

                //安卓系统环境版本在29以下时:
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
//                    saveBitmap2SelfDirectroy(bitmap, fileName);//如果不需要,可以不在app私有目录做备份
                    //系统相册目录
                    String galleryPath = SDCardUtils.getSDCardPath() + Environment.DIRECTORY_DCIM + File.separator + "Camera" + File.separator;
                    // 声明文件对象
                    File file = null;
                    //声明输出流
                    FileOutputStream fos = null;
                    try {
                        // 如果有目标文件,直接获得文件对象,否则创建一个以filename为名称的文件
                        file = new File(galleryPath, fileName);
                        // 获得文件相对路径
                        fileName = file.toString();
                        // 获得输出流,如果文件中有内容,追加内容
                        fos = new FileOutputStream(fileName);
                        if (null != fos) {
                            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        if (fos != null) {
                            fos.close();
                        }
                    }

                    ContentValues values = new ContentValues();
                    values.put(MediaStore.Images.Media.DATA, file.getAbsolutePath());
                    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
                    Uri uri = mActivity.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
                    Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
                    intent.setData(uri);
                    mActivity.sendBroadcast(intent);

//                    MediaStore.Images.Media.insertImage(mActivity.getContentResolver(), bitmap, fileName, null);
//                    Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
//                    Uri uri = Uri.fromFile(file);
//                    intent.setData(uri);
//                    mActivity.sendBroadcast(intent);

//                    try {
//                        MediaStore.Images.Media.insertImage(mActivity.getContentResolver(), file.getAbsolutePath(), fileName, null);
//                    } catch (FileNotFoundException e) {
//                        e.printStackTrace();
//                    }
//                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
//                        MediaScannerConnection.scanFile(mActivity, new String[]{file.getAbsolutePath()}, null,
//                                new MediaScannerConnection.OnScanCompletedListener() {
//                                    public void onScanCompleted(String path, Uri uri) {
//                                        Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
//                                        mediaScanIntent.setData(uri);
//                                        mActivity.sendBroadcast(mediaScanIntent);
//                                    }
//                                });
//                    } else {
//                        String relationDir = file.getParent();
//                        File file1 = new File(relationDir);
//                        mActivity.sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.fromFile(file1.getAbsoluteFile())));
//                    }

                } else {//Android Q把文件插入到系统图库
                    File file29 = null;
                    File fileDirectory = C.FilePath.getPicturesDirectory();//即:/storage/emulated/0/Android/data/包名/files/Pictures/
                    file29 = new File(fileDirectory, fileName);

                    ContentValues values = new ContentValues();
                    values.put(MediaStore.Images.Media.DESCRIPTION, "This is an qr image");
                    values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
                    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
                    values.put(MediaStore.Images.Media.TITLE, "Image.jpg");
                    values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/");

                    Uri external = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                    ContentResolver resolver = mActivity.getContentResolver();
                    Uri insertUri = resolver.insert(external, values);
                    BufferedInputStream inputStream = null;
                    OutputStream os = null;
                    try {
                        inputStream = new BufferedInputStream(new FileInputStream(file29));
                        if (insertUri != null) {
                            os = resolver.openOutputStream(insertUri);
                        }
                        if (os != null) {
                            byte[] buffer = new byte[1024 * 4];
                            int len;
                            while ((len = inputStream.read(buffer)) != -1) {
                                os.write(buffer, 0, len);
                            }
                            os.flush();
                        }
                    } catch (IOException e) {
                      e.printStackTrace();
                    } finally {
                        CloseUtils.closeIO(os, inputStream);
                        file29.delete();
                    }
                }
                return null;
            }

            @Override
            public void onSuccess(@Nullable Object result) {
                dialog.dismiss();
                ToastUtils.showToast("图片已保存");
            }

            @Override
            public void onCancel() {
                dialog.dismiss();
                ToastUtils.showToast("保存取消");
            }

            @Override
            public void onFail(Throwable t) {
                dialog.dismiss();
                ToastUtils.showToast("保存失败");
            }
        });
    }

    //保存图片至app私有目录
    private void saveBitmap2SelfDirectroy(Bitmap bitmap, String fileName) {
        File file = null;
        File fileDirectory = C.FilePath.getPicturesDirectory();//即:/storage/emulated/0/Android/data/包名/files/Pictures/
        file = new File(fileDirectory, fileName);
        try {
            FileOutputStream fos = new FileOutputStream(file);
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
            fos.flush();
            fos.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


 

你可能感兴趣的:(android开发)