混乱的 Android 存储

作者: 小强开学前 | 来源:发表于2021-08-25 16:12 被阅读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 真机 创建文件夹这一步失败

相关文章

  • 混乱的 Android 存储

    Android 10(API 29,代号 Q)开始启用分区存储。禁止一切应用访问除本应用文件夹外的所有文件,自己创...

  • Android10+分区存储适配

    什么是分区存储: 为了让用户更好地控制自己的文件并减少混乱,Android 10 针对应用推出了一种新的存储范例,...

  • Android ContentProvider(一)

    Android数据存储(一) Android数据存储(二) Android数据存储(三) Android数据存储(...

  • Android 11 分区存储

    1.分区存储概念 为了让用户更好地控制自己的文件并减少混乱,Android10针对应用推出了一个新的存储规范,新的...

  • Android storage

    Android存储结构android中的文件操作详解以及内部存储和外部存储彻底理解android中的内部存储与外部...

  • android存储

    Android中的存储 参考 彻底搞懂Android文件存储---内部存储,外部存储以及各种存储路径解惑[http...

  • Android存储及路径

    Android存储及路径 Android手机存储分类 分为:内部存储和外部存储 如何区分内部存储和外部存储:可以从...

  • Android数据存储

    Android数据存储 Android提供了5种方式存储数据 SharedPreferences它是Android...

  • Android Q 隐私权变更:应用作用域和媒体作用域存储空间

    为了让用户更好地控制自己的文件,并限制文件混乱情况,Android Q 更改了应用访问设备外部存储空间中文件的方式...

  • Android SD卡下载路径切换

    Android SD卡下载路径切换 存储机制原理及初始化 Android存储机制 原文链接: Android | ...

网友评论

    本文标题:混乱的 Android 存储

    本文链接:https://www.haomeiwen.com/subject/baxeiltx.html