android 11 版本下图片的保存方式改变以及保存图片到相册
android11版本下图片的保存方式改变(建议大家从android10开始适配,我就遇到了部分android10手机也出现这个问题)android11已经出来了半年的,有的手机已经升级到android11了,比如小米10等。在android11下,我们会发现应用有些功能变得不正常了,比如图片的保存。android11有两个可以保存的地方,第一个是项目的私有目录,一个是公共目录。而项目的私有目前的图片是可以改变的,但公共目录的不可以。
项目私有目录publicstaticStringgetDownloadPath(Contextcontext){if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.R){//android11returnConstants.SDCardConstants.getDir(context)+File.separator;}else{returnEnvironment.getExternalStorageDirectory()+"/winetalk/";}}上面的代码是我在兼容了android11后写的。Constants.SDCardConstants.getDir(context)+File.separator;是项目的私有目录路径。如果在android11上还是用Environment.getExternalStorageDirectory()这样的路径,就会报没有访问目录的权限。之前一直以为是因为缺少权限,也确实是缺少该路径的权限,因为android11已经拒绝我们再访问了。所以后续存放文件位置需要改为新的这个。
把透明网络图替换为白色背景并显示/***给透明图片添加白色底色,转换为jpg格式保存到本地后并获取本地图片并显示,然后删除本地图片*@paramresource*@paramview*@paramurl*/publicstaticvoidsaveAndGetImage(Contextcontext,Bitmapresource,Viewview,Stringurl,Stringtype){//由于图片有透明背景,但又要求显示时添加白色背景。此处的处理://1.复制出一个新的Bitmap,然后给新的Bitmap添加一个白色的背景画布,然后把这个图转换为jpg下载到本地。//2.从手机本地取出该图片显示即可。StringimgPath=C.getDownloadPath(context)+G.urlToFileName(url);Filejpg=newFile(imgPath);BitmapoutB=resource.copy(Bitmap.Config.ARGB_8888,true);//复制出一个新的BitmapCanvascanvas=newCanvas(outB);//给新的Bitmap添加一个白色的画布canvas.drawColor(Color.WHITE);canvas.drawBitmap(resource,0,0,null);try{FileOutputStreamout=newFileOutputStream(jpg);//保存到本地,格式为JPEGif(outB.compress(Bitmap.CompressFormat.JPEG,100,out)){out.flush();out.close();}}catch(FileNotFoundExceptione){G.look("FileNotFoundExceptione.toString:"+e.toString());e.printStackTrace();}catch(IOExceptione){G.look("IOExceptione.toString:"+e.toString());e.printStackTrace();}//从本地获取保存的图片并显示Bitmapbitmap=BitmapFactory.decodeFile(imgPath,null);G.look("saveAndGetImageBitmap:"+bitmap);if(type.equals("PhotoView")){photoView=(PhotoView)view;photoView.setImageBitmap(bitmap);}//删除本地图片Filefile=newFile(imgPath);//如果已经存在则不需要下载if(file!=null&&file.exists()){file.delete();return;}}调用Utils.saveAndGetImage(context,resource,holder.image,images.get(arg1),"PhotoView");保存图片到公共相册android10及以下版本适用的方法
/***下载网络图片*@paramcontext*@paramurls图片的网络路径*/publicstaticvoiddownLoad(Contextcontext,finalStringurls){String[]split=urls.split("\?");finalStringurl=split[0];if(url.startsWith("file")){G.toast(context,"此为本地图片,不用下载,路径为"+url.replace("file://",""));return;}if(OKHttpUtils.isNetworkAvailable(context)){G.showPd(context);TDUtils.execute(newRunnable(){@Overridepublicvoidrun(){try{Filefile=newFile(C.getDownloadPath(context));if(!file.exists()){file.mkdir();}Filejpg=newFile(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;}//先从缓存中查找FiletmpFile=NetAide.getBitmapUtils().getBitmapFileFromDiskCache(url);if(tmpFile!=null&&tmpFile.exists()){G.look("---从缓存中查找到图片----");Bitmapbm=BitmapFactory.decodeFile(tmpFile.getAbsolutePath());if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.R){//android11Utils.saveImageToGallery2(context,bm);G.dismissProgressDialogInThread();G.toastInThread(context,"你现在可以在图库中查看该图片了");}else{//给透明图添加白色背景BitmapoutB=bm.copy(Bitmap.Config.ARGB_8888,true);//复制出一个新的BitmapCanvascanvas=newCanvas(outB);canvas.drawColor(Color.WHITE);canvas.drawBitmap(bm,0,0,null);FileOutputStreamfos=newFileOutputStream(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;}}//从网络上下载保存Bitmapbm=BitmapFactory.decodeStream(newURL(url).openStream());if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.R){//android11Utils.saveImageToGallery2(context,bm);G.dismissProgressDialogInThread();G.toastInThread(context,"你现在可以在图库中查看该图片了");}else{//给透明图添加白色背景BitmapoutB=bm.copy(Bitmap.Config.ARGB_8888,true);//复制出一个新的BitmapCanvascanvas=newCanvas(outB);canvas.drawColor(Color.WHITE);canvas.drawBitmap(bm,0,0,null);FileOutputStreamfos=newFileOutputStream(jpg);outB.compress(Bitmap.CompressFormat.JPEG,100,fos);fos.close();G.dismissProgressDialogInThread();//通知图库更新C.noticeImageRefresh(context,jpg);G.toastInThread(context,"你现在可以在图库中查看该图片了");}}catch(Exceptione){e.printStackTrace();G.dismissProgressDialogInThread();G.toastInThread(context,context.getResources().getString(R.string.downLoadFail));Filejpg=newFile(C.getDownloadPath(context)+G.urlToFileName(url));if(jpg!=null&&jpg.exists()){jpg.delete();}}}});}}android11及以上版本的适用:在上面的方法中修改兼容
/***android11及以上保存图片到相册*@paramcontext*@paramimage*/publicstaticvoidsaveImageToGallery2(Contextcontext,Bitmapimage){LongmImageTime=System.currentTimeMillis();StringimageDate=newSimpleDateFormat("yyyyMMdd-HHmmss").format(newDate(mImageTime));StringSCREENSHOT_FILE_NAME_TEMPLATE="winetalk_%s.png";//图片名称,以"winetalk"+时间戳命名StringmImageFileName=String.format(SCREENSHOT_FILE_NAME_TEMPLATE,imageDate);finalContentValuesvalues=newContentValues();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);ContentResolverresolver=context.getContentResolver();finalUriuri=resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,values);try{//First,writetheactualdataforourscreenshottry(OutputStreamout=resolver.openOutputStream(uri)){if(!image.compress(Bitmap.CompressFormat.PNG,100,out)){thrownewIOException("Failedtocompress");}}//Everythingwentwellabove,publishit!values.clear();values.put(MediaStore.MediaColumns.IS_PENDING,0);values.putNull(MediaStore.MediaColumns.DATE_EXPIRES);resolver.update(uri,values,null,null);}catch(IOExceptione){resolver.delete(uri,null);G.look("Exception:"+e.toString());}}上面的代码肯定有需要优化的地方,这里只记录我在项目中的实现,因为需要紧急更新。大家觉得有哪些需要优化的可以留言共同讨论。
最后附上Constants的工具类
publicclassConstants{/***文件存储相关常量*/publicstaticclassSDCardConstants{privatestaticfinalStringTAG="SDCardConstants";/***转码文件后缀*/publicfinalstaticStringTRANSCODE_SUFFIX=".mp4_transcode";/***裁剪文件后缀*/publicfinalstaticStringCROP_SUFFIX="-crop.mp4";/***合成文件后缀*/publicfinalstaticStringCOMPOSE_SUFFIX="-compose.mp4";/***裁剪&录制&转码输出文件的目录*androidQ版本默认路径*/storage/emulated/0/Android/data/包名/files/Media/*androidQ以下版本默认"/sdcard/DCIM/Camera/"*/publicstaticStringgetDir(Contextcontext){Stringdir;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;}Filefile=newFile(dir);if(!file.exists()){//noinspectionResultOfMethodCallIgnoredfile.mkdirs();}returndir;}/***获取外部缓存目录版本默认"/storage/emulated/0/Android/data/包名/file/Cache/svideo"**@paramcontextContext*@returnstringpath*/publicstaticStringgetCacheDir(Contextcontext){FilecacheDir=newFile(context.getExternalCacheDir(),"svideo");if(!cacheDir.exists()){cacheDir.mkdirs();}returncacheDir.exists()?cacheDir.getPath():"";}/***清空外部缓存目录文件"/storage/emulated/0/Android/data/包名/file/Cache/svideo"**@paramcontextContext*/publicstaticvoidclearCacheDir(Contextcontext){finalFilecacheDir=newFile(context.getExternalCacheDir(),"svideo");ThreadUtils.runOnSubThread(newRunnable(){@Overridepublicvoidrun(){booleanb=deleteFile(cacheDir);Log.i(TAG,"deletecachefile"+b);}});}/***递归删除文件/目录*@paramfileFile*/privatestaticbooleandeleteFile(Filefile){if(file==null||!file.exists()){returntrue;}if(file.isDirectory()){File[]files=file.listFiles();if(files==null){returntrue;}for(Filef:files){deleteFile(f);}}returnfile.delete();}}}