Android 10开发之 保存、读取图片
概述
从Android 10(Q)开始,谷歌就开始修改了外部存储权限,叫做分区存储,分区存储可以分为两个目录,分别是 沙盒目录(App-specific directory 和 公共目录(Public Directory)
沙盒目录
沙盒目录存储在 /Android/data/包名,保存文件到该目录,一般通过 Context.getExternalFilesDir() ,例如:context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) 表示路径为:/Android/data/包名/Pictures/,app一旦卸载,沙盒目录下的文件都会被删除。Android 10以上去除了WRITE_EXTERNAL_STORAGE权限,不需要这个权限就可以保存文件到沙盒目录
公共目录
公共目录包括 多媒体目录 和 下载目录
公共目录的媒体文件(Photos, Images, Videos, Audio)通过MediaStore来访问,另外,MediaStore的DATA字段从Android 10开始被标记为deprecated,通过该字段获取的文件路径不再可靠,Android 10以上新增字段RELATIVE_PATH,代表文件的相对路径,在使用MediaStore保存媒体文件时,可以通过设置该字段来设置媒体文件保存的文件夹
保存文件(以图片为例)
沙盒目录
/**
* 保存图片到沙盒目录
* @param context 上下文
* @param fileName 文件名
* @param bitmap 文件
* @return 路径,为空时表示保存失败
*/
public static String FileSaveToInside(Context context, String fileName, Bitmap bitmap) {
FileOutputStream fos = null;
String path = null;
try {
//设置路径 /Android/data/com.panyko.filesave/Pictures/
File folder = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
//判断目录是否存在
//目录不存在时自动创建
if (folder.exists() ||folder.mkdir()) {
File file = new File(folder, fileName);
fos = new FileOutputStream(file);
//写入文件
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
path = file.getAbsolutePath();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
//关闭流
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//返回路径
return path;
}
保存文件到沙盒目录时,操作简单,不需要判断Android版本做兼容
公共目录
/**
* 保存文件到公共目录
* @param context 上下文
* @param fileName 文件名
* @param bitmap 文件
* @return 路径,为空时表示保存失败
*/
public static String fileSaveToPublic(Context context, String fileName, Bitmap bitmap) {
String path = null;
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
//Android 10以下版本
FileOutputStream fos = null;
try {
//设置路径 Pictures/
File folder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
//判断目录是否存在
//目录不存在时自动创建
if (folder.exists() || folder.mkdir()) {
File file = new File(folder, fileName);
fos = new FileOutputStream(file);
//写入文件
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
path = file.getAbsolutePath();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
} else {
//Android 10及以上版本
//设置路径 Pictures/
String folder = Environment.DIRECTORY_PICTURES;
//设置保存参数到ContentValues中
ContentValues values = new ContentValues();
//设置图片名称
values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
//设置图片格式
values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
//设置图片路径
values.put(MediaStore.Images.Media.RELATIVE_PATH, folder);
//执行insert操作,向系统文件夹中添加文件
//EXTERNAL_CONTENT_URI代表外部存储器,该值不变
Uri uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
OutputStream os = null;
try {
if (uri != null) {
//若生成了uri,则表示该文件添加成功
//使用流将内容写入该uri中即可
os = context.getContentResolver().openOutputStream(uri);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
os.flush();
path = uri.getPath();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return path;
}
Android10开始,访问公共目录的方式有改变,通过 MediaStore 来访问,因为不能完全保证Android10以下的手机能通过MediaStore方式保存文件,因此在保存文件时,根据Android版本分成两种情况(Android10以下、Android10及以上),这样稳妥
读取文件(以图片为例)
/**
* 根据路径和名字查出文件
* @param context 上下文
* @param filePath 文件路径
* @param fileName 文件名
* @return
*/
public static Uri FileGetFromPublic(Context context, String filePath, String fileName) {
String queryPath;
//判断是否有加斜杠
if (!filePath.endsWith("/")) {
filePath = filePath + File.separator;
}
//判断Android版本
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
//Android10以下
//加上文件名
filePath = filePath + fileName;
//使用DATA字段做查询
queryPath = MediaStore.Images.Media.DATA;
} else {
//Android10及以上
//使用RELATIVE_PATH字段做查询
queryPath = MediaStore.Images.Media.RELATIVE_PATH;
}
//拼接查询条件
//queryPath表示文件所在路径
//DISPLAY_NAME表示文件名
String selection = queryPath + "=? and " + MediaStore.Images.Media.DISPLAY_NAME + "=?";
Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[]{MediaStore.Files.FileColumns._ID}, selection, new String[]{filePath, fileName}, null);
if (cursor != null && cursor.moveToFirst()) {
//查出id
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.Images.Media._ID));
//根据id查询URI
Uri uri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
return uri;
}
//关闭查询
if (cursor != null) {
cursor.close();
}
return null;
}
Android 10以下是可以通过 MediaStore.Images.Media.DATA 获取文件绝对路径,Android 10 及以上DATA字段被弃用,增加了 MediaStore.Images.Media.RELATIVE_PATH获取文件相对路径。这里根据版本判断,当Android10以下时,查询DATA字段;当Android10及以上时,查询RELATIVE_PATH字段,再获取id,根据id获取URI
这里要注意下,如果是Android10及以上的,通过RELATIVE_PATH查询时,路径后面一定要检查是否加了斜杠,如果没加,一定要加斜杠;如果是Android10以下的,通过DATA查询时,路径一定要完整(包括文件名),我就是因为这个原因,一直查不到数据,后面把media数据库导出来,查看了一下才知道。
Android9,data字段:
Android11,relative_path字段: