Android——MediaStore媒体文件库

在应用中,将文件保存到本地,是很常见的 I/O 操作。而有的图片或者视频文件不单只是需要保存到本地,涉及到的不仅仅是一个 I/O 操作了,还需要考虑如何更新 MediaStore,这样才可以在系统相册中,看到它。

MediaStore的本质

MediaStore,本质上是 Android 维护的一个文件系统的数据库,它记录了当前磁盘上所有的文件索引,我们可以通过它,快速的查找当前系统的文件。

保存文件到MediaStore

MediaStore 刷新的时机是不一定的,也就是说,保存的一张图片文件,MediaStore 并不会立即刷新文件系统,将此文件索引记录下来。而系统本身是存在一些自动刷新 MediaStore 的时机,例如:重启手机。表现就是,当你保存了一张图片到本地文件夹中之后,通过文件管理器类的 App,可以在目录下找到这涨照片,但是在系统相册中,是无法立即看到它的,同时你想用诸如 微信、QQ 去分享这张图片的时候,也是找不到的。所以在我们保存图片文件之后,去触发系统刷新 MediaStore 就尤为重要了。

在Android10之前,在保存文件后,刷新系统 MediaStore 的方式有:

  • 通过操作 MediaStore 类。
  • 发送广播更新 MediaStore。
  • 通过操作 MediaScannerConnection 类。

而在Android10提出了分区存储,只能通过操作 MediaStore 类来刷新系统MediaStore。

一、 通过操作MediaStore 类
保存图片

// 创建图片的索引
            ContentValues values = new ContentValues();
            String fileName = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
            values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
            values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/ScreenShot");
            }
            values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
            ContentResolver contentResolver = mContext.getContentResolver();
            // 将索引信息添加到数据表中,得到该条索引信息的uri
            Uri uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            try {
                OutputStream imageOutStream = contentResolver.openOutputStream(uri);
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, imageOutStream);
                imageOutStream.close();
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    values.clear();
                    values.put(MediaStore.Video.Media.IS_PENDING, 0);
                    contentResolver.update(uri, values, null, null);
                }
                return bitmap;
            } catch (IOException e) {
                e.printStackTrace();
            }

如果你的应用程序需要执行一些非常耗时的操作,比如写入媒体文件,那么在文件被处理时对其进行独占访问是非常有用的。在运行Android 10或更高版本的设备上,你可以通过将IS_PENDING标志的值设置为1来获得这种独占访问。只有你的应用程序可以查看该文件,直到将IS_PENDING的值更改回0。

保存视频

private Uri insertVideoToMediaStore(String filePath, String fileName) {
        ContentResolver resolver = getApplicationContext().getContentResolver();
        // 拿到MediaStore.Video表的uri
        Uri tableUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
        // 创建视频索引
        ContentValues values = new ContentValues();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            // 相对路径
            values.put(MediaStore.Video.Media.RELATIVE_PATH, "Movies/ScreenRecord");
            values.put(MediaStore.Video.Media.IS_PENDING, 1);
        } else {
            // 绝对路径
            values.put(MediaStore.Video.Media.DATA, filePath);
            MediaPlayer mp = MediaPlayer.create(this, Uri.parse(filePath));
            int duration = mp.getDuration();
            mp.release();
            values.put(MediaStore.Video.Media.DURATION, duration);
        }
        values.put(MediaStore.Video.Media.DISPLAY_NAME, fileName);
        values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
        // 将索引信息插入到数据表中,获得视频的uri
        Uri uri = resolver.insert(tableUri, values);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            copyVideoToMediaStore(uri, resolver, filePath);
            values.clear();
            values.put(MediaStore.Video.Media.IS_PENDING, 0);
            resolver.update(uri, values, null, null);
        }
        return uri;
    }

    private void copyVideoToMediaStore(Uri uri, ContentResolver resolver, String filePath) {
        try {
            // android 10 及以上MediaStore无法访问getExternalFilesDir路径中存储的内容
            // 需要将视频文件复制到MediaStore中
            ParcelFileDescriptor fileDescriptor = resolver.openFileDescriptor(uri, "rw");
            FileOutputStream fileOutputStream = new FileOutputStream(fileDescriptor.getFileDescriptor());
            // Get the already saved video as fileinputstream from here
            FileInputStream fileInputStream = new FileInputStream(filePath);
            byte[] buf = new byte[8192];
            int len;
            while ((len = fileInputStream.read(buf)) > 0) {
                fileOutputStream.write(buf, 0, len);
            }
            fileOutputStream.close();
            fileInputStream.close();
            fileDescriptor.close();
        } catch (Exception e) {
            mHandler.sendEmptyMessage(MSG_HIDE_PROGRESS_BAR);
            e.printStackTrace();
        }
    }

二、发送广播
通过广播刷新 MediaStore 的方式非常的简单,只需要指定文件路径和 Action 就好了。

  1. 保存图片后通过广播刷新
Uri contentUri = Uri.fromFile(File(filePath))
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,contentUri)
mContext.sendBroadcast(mediaScanIntent)

正常情况下,它是没有问题的,不过假如你发现它不生效,就需要检查一下你文件的路径是否传递正确。

通过查看 MediaScannerReceiver 的源码,可以发现 onReceive() 方法中,针对 ACTION_MEDIA_SCANNER_SCAN_FILE 还有一个限制条件,那就是传递进去的文件绝对路径,必须是以 Environment.getExternalStorageDirectory() 方法的返回值开头。

三、 操作 MediaScannerConnection 类
主要是利用 MediaScannerConnection 类的 scanFile() 方法进行触发扫描。通过 scanFile() 方法,我们只需要指定一个待刷新的文件路径和对应的 MimeType 即可,它支持传递多个路径,也可就是支持批量扫描。注意这里的 MimeType 是一定要填写的,并且不能写通配符 / 或 null,否则会导致刷新失败,通常我们保存的是一个图片的话,只需要传递 image/jpeg 即可。最后一个参数, onScanCompletedListener 中可以监听我们扫描的结果。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值