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版权协议,转载请附上原文出处链接和本声明。