开发中,我们一般调用```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();
}
}