android 11 版本下图片的保存方式改变以及保存图片到相册

android 11 版本下图片的保存方式改变(建议大家从android 10开始适配,我就遇到了部分android 10手机也出现这个问题)

android 11已经出来了半年的,有的手机已经升级到android 11了,比如小米10等。在android 11下,我们会发现应用有些功能变得不正常了,比如图片的保存。android 11有两个可以保存的地方,第一个是项目的私有目录,一个是公共目录。而项目的私有目前的图片是可以改变的,但公共目录的不可以。

项目私有目录
public static String getDownloadPath(Context context) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { //android 11
            return Constants.SDCardConstants.getDir(context) + File.separator;
        }else {
            return Environment.getExternalStorageDirectory() + "/winetalk/";
        }
    }

上面的代码是我在兼容了android 11后写的。Constants.SDCardConstants.getDir(context) + File.separator;是项目的私有目录路径。如果在android 11上还是用Environment.getExternalStorageDirectory()这样的路径,就会报没有访问目录的权限。之前一直以为是因为缺少权限,也确实是缺少该路径的权限,因为android 11已经拒绝我们再访问了。所以后续存放文件位置需要改为新的这个。

把透明网络图替换为白色背景并显示
/**
     * 给透明图片添加白色底色,转换为jpg格式保存到本地后并获取本地图片并显示,然后删除本地图片
     * @param resource
     * @param view
     * @param url
     */
    public static void saveAndGetImage(Context context,Bitmap resource,View view,String url,String type){
        //由于图片有透明背景,但又要求显示时添加白色背景。此处的处理:
        //1.复制出一个新的Bitmap,然后给新的Bitmap添加一个白色的背景画布,然后把这个图转换为jpg下载到本地。
        //2.从手机本地取出该图片显示即可。

        String imgPath = C.getDownloadPath(context)+G.urlToFileName(url);
            File jpg = new File(imgPath);
            Bitmap outB=resource.copy(Bitmap.Config.ARGB_8888,true); //复制出一个新的Bitmap
            Canvas canvas=new Canvas(outB);   //给新的Bitmap 添加一个白色的画布
            canvas.drawColor(Color.WHITE);
            canvas.drawBitmap(resource, 0, 0, null);
            try {
                FileOutputStream out = new FileOutputStream(jpg); //保存到本地,格式为JPEG
                if (outB.compress(Bitmap.CompressFormat.JPEG, 100, out)) {
                    out.flush();
                    out.close();
                }
            } catch (FileNotFoundException e) {
                G.look("FileNotFoundException e.toString: "+e.toString());
                e.printStackTrace();
            } catch (IOException e) {
                G.look("IOException e.toString: "+e.toString());
                e.printStackTrace();
            }
            //从本地获取保存的图片并显示

            Bitmap bitmap = BitmapFactory.decodeFile(imgPath,null);
            G.look("saveAndGetImage Bitmap: "+bitmap);
            if (type.equals("PhotoView")) {
                photoView = (PhotoView) view;
                photoView.setImageBitmap(bitmap);
            }
            //删除本地图片
            File file = new File(imgPath);
            // 如果已经存在则不需要下载
            if (file != null && file.exists()) {
                file.delete();
                return;
            }

    }
调用
Utils.saveAndGetImage(context,resource,holder.image,images.get(arg1),"PhotoView");
保存图片到公共相册

android 10及以下版本适用的方法

/**
* 下载网络图片
* @param context
* @param urls 图片的网络路径
*/
public static void downLoad(Context context,final String urls) {
        String[] split = urls.split("\\?");
        final String url = split[0];
        if (url.startsWith("file")) {
            G.toast(context, "此为本地图片,不用下载,路径为" + url.replace("file://", ""));
            return;
        }

        if (OKHttpUtils.isNetworkAvailable(context)) {
            G.showPd(context);
            TDUtils.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        File file = new File(C.getDownloadPath(context));
                        if (!file.exists()) {
                            file.mkdir();
                        }
                        File jpg = new File(C.getDownloadPath(context) + G.urlToFileName(url));
                        // 如果已经存在则不需要下载
                        if (jpg != null && jpg.exists()) {
                            G.dismissProgressDialogInThread();

                            G.toastInThread(context,
                                    "该文件已被下载到" + jpg.getParent() + context.getResources().getString(R.string.xia));
                            return;
                        }
                        // 先从缓存中查找
                        File tmpFile = NetAide.getBitmapUtils().getBitmapFileFromDiskCache(url);
                        if (tmpFile != null && tmpFile.exists()) {
                            G.look("---从缓存中查找到图片----");
                            Bitmap bm = BitmapFactory.decodeFile(tmpFile.getAbsolutePath());
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { //android 11
                                Utils.saveImageToGallery2(context,bm);
                                G.dismissProgressDialogInThread();
                                G.toastInThread(context, "你现在可以在图库中查看该图片了");
                            }else {
                                //给透明图添加白色背景
                                Bitmap outB = bm.copy(Bitmap.Config.ARGB_8888, true); //复制出一个新的Bitmap
                                Canvas canvas = new Canvas(outB);
                                canvas.drawColor(Color.WHITE);
                                canvas.drawBitmap(bm, 0, 0, null);
                                FileOutputStream fos = new FileOutputStream(jpg);
                                outB.compress(Bitmap.CompressFormat.JPEG, 100, fos);
                                fos.close();
                                G.dismissProgressDialogInThread();

                                // 通知图库更新
                                C.noticeImageRefresh(context, jpg);

                                G.toastInThread(context, context.getResources().getString(R.string.downLoadUrl)
                                        + jpg.getParent() + context.getResources().getString(R.string.xia));
                                return;
                            }
                        }

                        // 从网络上下载保存
                        Bitmap bm = BitmapFactory.decodeStream(new URL(url).openStream());
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { //android 11
                            Utils.saveImageToGallery2(context,bm);
                            G.dismissProgressDialogInThread();
                            G.toastInThread(context, "你现在可以在图库中查看该图片了");
                        }else {
                            //给透明图添加白色背景
                            Bitmap outB = bm.copy(Bitmap.Config.ARGB_8888, true); //复制出一个新的Bitmap
                            Canvas canvas = new Canvas(outB);
                            canvas.drawColor(Color.WHITE);
                            canvas.drawBitmap(bm, 0, 0, null);
                            FileOutputStream fos = new FileOutputStream(jpg);
                            outB.compress(Bitmap.CompressFormat.JPEG, 100, fos);
                            fos.close();
                            G.dismissProgressDialogInThread();

                            // 通知图库更新
                            C.noticeImageRefresh(context, jpg);
                            G.toastInThread(context, "你现在可以在图库中查看该图片了");
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        G.dismissProgressDialogInThread();

                        G.toastInThread(context, context.getResources().getString(R.string.downLoadFail));

                        File jpg = new File(C.getDownloadPath(context) + G.urlToFileName(url));
                        if (jpg != null && jpg.exists()) {
                            jpg.delete();
                        }
                    }
                }
            });
        }

    }

android 11及以上版本的适用:在上面的方法中修改兼容

 /**
     * android 11及以上保存图片到相册
     * @param context
     * @param image
     */
    public static void saveImageToGallery2(Context context, Bitmap image){
        Long mImageTime = System.currentTimeMillis();
        String imageDate = new SimpleDateFormat("yyyyMMdd-HHmmss").format(new Date(mImageTime));
        String SCREENSHOT_FILE_NAME_TEMPLATE = "winetalk_%s.png";//图片名称,以"winetalk"+时间戳命名
        String mImageFileName = String.format(SCREENSHOT_FILE_NAME_TEMPLATE, imageDate);

        final ContentValues values = new ContentValues();
        values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES
                + File.separator + "winetalk"); //Environment.DIRECTORY_SCREENSHOTS:截图,图库中显示的文件夹名。"dh"
        values.put(MediaStore.MediaColumns.DISPLAY_NAME, mImageFileName);
        values.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
        values.put(MediaStore.MediaColumns.DATE_ADDED, mImageTime / 1000);
        values.put(MediaStore.MediaColumns.DATE_MODIFIED, mImageTime / 1000);
        values.put(MediaStore.MediaColumns.DATE_EXPIRES, (mImageTime + DateUtils.DAY_IN_MILLIS) / 1000);
        values.put(MediaStore.MediaColumns.IS_PENDING, 1);

        ContentResolver resolver = context.getContentResolver();
        final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
        try {
            // First, write the actual data for our screenshot
            try (OutputStream out = resolver.openOutputStream(uri)) {
                if (!image.compress(Bitmap.CompressFormat.PNG, 100, out)) {
                    throw new IOException("Failed to compress");
                }
            }
            // Everything went well above, publish it!
            values.clear();
            values.put(MediaStore.MediaColumns.IS_PENDING, 0);
            values.putNull(MediaStore.MediaColumns.DATE_EXPIRES);
            resolver.update(uri, values, null, null);
        }catch (IOException e){
            resolver.delete(uri, null);
            G.look("Exception:"+e.toString());
        }
    }

上面的代码肯定有需要优化的地方,这里只记录我在项目中的实现,因为需要紧急更新。大家觉得有哪些需要优化的可以留言共同讨论。

最后附上Constants的工具类

public class Constants {

    /**
     * 文件存储相关常量
     */
    public static class SDCardConstants {

        private static final String TAG = "SDCardConstants";
        /**
         * 转码文件后缀
         */
        public final static String TRANSCODE_SUFFIX = ".mp4_transcode";

        /**
         * 裁剪文件后缀
         */
        public final static String CROP_SUFFIX = "-crop.mp4";

        /**
         * 合成文件后缀
         */
        public final static String COMPOSE_SUFFIX = "-compose.mp4";

        /**
         * 裁剪 & 录制 & 转码输出文件的目录
         * android Q 版本默认路径
         * /storage/emulated/0/Android/data/包名/files/Media/
         * android Q 以下版本默认"/sdcard/DCIM/Camera/"
         */
        public static String getDir(Context context) {
            String dir;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                dir = context.getExternalFilesDir("") + File.separator + "Media" + File.separator;
            } else {
                dir = Environment.getExternalStorageDirectory() + File.separator + "DCIM"
                      + File.separator + "Camera" + File.separator;
            }
            File file = new File(dir);
            if (!file.exists()) {
                //noinspection ResultOfMethodCallIgnored
                file.mkdirs();
            }
            return dir;
        }

        /**
         * 获取外部缓存目录 版本默认"/storage/emulated/0/Android/data/包名/file/Cache/svideo"
         *
         * @param context Context
         * @return string path
         */
        public static String getCacheDir(Context context) {
            File cacheDir = new File(context.getExternalCacheDir(), "svideo");
            if (!cacheDir.exists()) {
                cacheDir.mkdirs();
            }
            return cacheDir.exists() ? cacheDir.getPath() : "";
        }

        /**
         * 清空外部缓存目录文件 "/storage/emulated/0/Android/data/包名/file/Cache/svideo"
         *
         * @param context Context
         */
        public static void clearCacheDir(Context context) {
            final File cacheDir = new File(context.getExternalCacheDir(), "svideo");
            ThreadUtils.runOnSubThread(new Runnable() {
                @Override
                public void run() {
                    boolean b = deleteFile(cacheDir);
                    Log.i(TAG, "delete cache file " + b);
                }
            });
        }

        /**
         * 递归删除文件/目录
         * @param file File
         */
        private static boolean deleteFile(File file) {
            if (file == null || !file.exists()) {
                return true;
            }

            if (file.isDirectory()) {
                File[] files = file.listFiles();
                if (files == null) {
                    return true;
                }
                for (File f : files) {
                    deleteFile(f);
                }
            }
            return file.delete();
        }

    }
}

版权声明:本文为u014714188原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。