混乱的 Android 存储
2021-08-25 本文已影响0人
小强开学前
Android 10(API 29,代号 Q)开始启用分区存储。
禁止一切应用访问除本应用文件夹外的所有文件,自己创建的媒体文件可以访问。
直接通过路径访问图片,Android 6.0 (API 23,M)及以上
确保
/sdcard/Download/1.webp
存在
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),"1.webp");
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
没有申请权限时,皆会抛出错误
com.android.providers.media.module E/MediaProvider: Permission to access file: /storage/emulated/0/Download/1.webp is denied
E/BitmapFactory: Unable to decode stream: java.io.FileNotFoundException: /storage/emulated/0/Download/1.webp: open failed: EACCES (Permission denied)
通过这篇文章的Intent方式返回URI获取图片
创建Contract
public static final class PickImage extends ActivityResultContract<Void, Uri> {
@NonNull
@Override
public Intent createIntent(@NonNull Context context, @Nullable Void input) {
return new Intent(Intent.ACTION_PICK).setType("image/*");
}
@Nullable
@Override
public Uri parseResult(int resultCode, @Nullable Intent intent) {
if (intent == null || resultCode != Activity.RESULT_OK) return null;
return intent.getData();
}
}
创建申请权限的Launch
private final ActivityResultLauncher<Void> imageLauncher = registerForActivityResult(new PickImage(), resultUri -> {
if (resultUri != null) {
handleResult(resultUri);
} else {
Toast.makeText(MainActivity.this, "未做处理", Toast.LENGTH_SHORT).show();
}
});
URI转Bitmap使用代码如下
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
ImageDecoder.Source source = ImageDecoder.createSource(MainActivity.this.getContentResolver(), resultUri);
bm = ImageDecoder.decodeBitmap(source);
} else {
bm = MediaStore.Images.Media.getBitmap(MainActivity.this.getContentResolver(), resultUri);
}
测试结果
机型 | 版本 | 结果 |
---|---|---|
Pixel 3 | Android 12 真机 | 成功读取无需权限 |
Pixel 3 | Android 12 模拟器 | 成功读取无需权限 |
Pixel 2 | Android 9 模拟器 | 成功读取无需权限 |
Nut Pro 2s | Android 8.1 真机 | 返回URI但报错SecurityException |
Samsung S8 | Android 7 真机 | 成功读取无需权限 |
目测一般情况是不需要任何权限的,国产机器魔改太多,为了做好兼容,还是需要捕获SecurityException
然后弹框提示用户此种情况。
存储图片到媒体文件夹sdcard/Pictures/Example/
fun saveBitmap2Gallery(context: Context, bm: Bitmap) {
val currentTime = System.currentTimeMillis()
// name the file
val imageFileName = "IMG_TEST_$currentTime.jpg"
val imageFilePath =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) Environment.DIRECTORY_PICTURES + File.separator + "Example" + File.separator
else Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).absolutePath + File.separator + "Example" + File.separator
// write to file
val resolver: ContentResolver = context.contentResolver
val newScreenshot = ContentValues().apply {
put(MediaStore.Images.ImageColumns.DATE_ADDED, currentTime)
put(MediaStore.Images.ImageColumns.DISPLAY_NAME, imageFileName)
put(MediaStore.Images.ImageColumns.MIME_TYPE, "image/jpg")
put(MediaStore.Images.ImageColumns.WIDTH, bm.width)
put(MediaStore.Images.ImageColumns.HEIGHT, bm.height)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(MediaStore.Images.ImageColumns.RELATIVE_PATH, imageFilePath)
} else {
// make sure the path is existed
val imageFileDir = File(imageFilePath)
if (!imageFileDir.exists()) {
val mkdir = imageFileDir.mkdirs()
if (!mkdir) {
logD("save failed, error: cannot create folder. Make sure app has the permission.")
return
}
}
put(MediaStore.Images.ImageColumns.DATA, imageFilePath + imageFileName)
put(MediaStore.Images.ImageColumns.TITLE, imageFileName)
}
}
// insert a new image
resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, newScreenshot)?.let { uri ->
resolver.openOutputStream(uri)?.let { os ->
bm.compress(Bitmap.CompressFormat.PNG, 80, os)
os.flush()
os.close()
newScreenshot.clear()
newScreenshot.put(MediaStore.Images.ImageColumns.SIZE, File(imageFilePath).length())
resolver.update(uri, newScreenshot, null, null)
logD("save success, you can view it in gallery")
}
}
}
机型 | 版本 | 结果 |
---|---|---|
Pixel 3 | Android 12 真机 | 保存成功 |
Pixel 3 | Android 12 模拟器 | 保存成功 |
Pixel 2 | Android 9 模拟器 | 创建文件夹这一步失败 |
Nut Pro 2s | Android 8.1 真机 | 创建文件夹这一步失败 |