简介
Android 多媒体文件管理简单来说, 有三个部分
扫描: MediaScannerService
存储: MediaProvider
查询: MediaStore
MediaScannerService
值得一提的是, MediaScannerService 这类文件, AndroidStudio 中不能直接 search 到, 需要去官网查询其源码, 这里推荐一个在线查看 Android 源码的链接 http://androidxref.com/
- public class MediaScannerService extends Service implements Runnable
- // 核心方法
- private void scan(String[] directories, String volumeName) {
- ...
- try {
- ...
- try {
- ...
- MediaScanner scanner = createMediaScanner();
- scanner.scanDirectories(directories, volumeName);
- } catch (Exception e) {
- ...
- }
- ...
- } finally {
- ...
- }
- }
应用级 Service, 用于媒体文件扫描
scan 是其核心方法, 从源码中可以看出, 最终是调用了 MediaScanner 的 scanDirectories 方法
MediaScannerReceiver
与 MediaScannerService 配套的 BroastReceiver
- @Override
- public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
- final Uri uri = intent.getData();
- if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
- // Scan both internal and external storage
- scan(context, MediaProvider.INTERNAL_VOLUME);
- scan(context, MediaProvider.EXTERNAL_VOLUME);
- } else {
- if (uri.getScheme().equals("file")) {
- // 省略...
- if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
- // scan whenever any volume is mounted
- scan(context, MediaProvider.EXTERNAL_VOLUME);
- } else if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) &&
- path != null && path.startsWith(externalStoragePath + "/")) {
- scanFile(context, path);
- }
- }
- }
- }
从 onReceive 方法中可以看出, MediaScannerReceiver 执行 scan 的时机有三种
启动完毕, 扫描内部存储和外部存储
sdcard 挂载完毕, 扫描外部存储
扫描单个文件
MediaScanner
具体执行 scan 动作的类
MediaScannerProvider
ContentProvider 用于保存查询结果, 结果由数据库保存
以外部存储为例, 数据库的路径为 / data/data/com.Android.providers.media/databases/external.db
MediaStore
MediaStore 是面向应用的入口, 作用了是为了便于用户查询已经扫描过的数据
MediaStore 中的资源有四类
- MediaStore.Files
- MediaStore.Audio
- MediaStore.Images
- MediaStore.Video
媒体库查询
核心方法
- public final @Nullable Cursor query(@RequiresPermission.Read
- @NonNull Uri uri,
- @Nullable String[] projection,
- @Nullable String selection,
- @Nullable String[] selectionArgs,
- @Nullable String sortOrder)
- Uri uri
- Uri uri = MediaStore.Video.Media.INTERNAL_CONTENT_URI;
- Uri uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
Uri 总共有两种, 分别对应内部存储和外部存储
- String[] projection
- String[] mediaColumns = {
- MediaStore.Video.Media.DATA,
- MediaStore.Video.Media.SIZE,
- MediaStore.Video.Media.DATE_MODIFIED,
- MediaStore.Video.Media.DURATION};
用于指定查询后, 返回给用户的媒体信息
String selection & String[] selectionArgs
这两个是可选项, 用户可以通过这两个参数, 定制化查询条件
- String selection = MediaStore.Video.Media.MIME_TYPE + "=? or"
- + MediaStore.Video.Media.MIME_TYPE + "=? or"
- + MediaStore.Video.Media.MIME_TYPE + "=? or"
- + MediaStore.Video.Media.MIME_TYPE + "=? or"
- + MediaStore.Video.Media.MIME_TYPE + "=?"
- String[] selectionArgs = new String[]{"video/mp4", "video/avi", "video/quicktime", "video/webm", "video/x-ms-wmv"}
需要注意的是, selection 中的条件和 selectionArgs 数组中的类型, 需要是一一对应的, 否则会发生异常
String sortOrder
查询的排序方式
.nomedia
文件夹中新建一个. nomedia 的空文件, 会屏蔽掉系统默认的媒体库扫描. 遇到带有该文件的文件夹, 只能通过文件遍历的方式进行扫描
MIME_TYPE
需要注意的是 MIMETYPE 不一定和文件的扩展名一致, 比如 mov 格式的文件, MIMETYPE 为 video/quicktime 等等
新增视频资源并通知手机相册
基本方法
方法一: 发送广播, 通知媒体库刷新
- Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
- intent.setData(Uri.fromFile(new File(filePath)));
- context.sendBroadcast(intent);
方法二: 插入数据库
也可以使用直接插入媒体库数据库的方法来实现 (但这两种方法不能同时使用, 会导致媒体库中出现重复的数据)
- ContentValues values = new ContentValues(4);
- values.put(MediaStore.Video.Media.TITLE, "");
- values.put(MediaStore.Video.Media.MIME_TYPE, minetype);
- values.put(MediaStore.Video.Media.DATA, path);
- values.put(MediaStore.Video.Media.DURATION, duration_int);
- context.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values);
插入数据库的这种方法, 这边博文中有详细介绍
方法三: MediaScannerConnection.scanFile
- public static void insert(Context context, String[] paths, String[] types) {
- MediaScannerConnection.scanFile(
- context,
- paths,
- types,
- new MediaScannerConnection.OnScanCompletedListener() {
- @Override
- public void onScanCompleted(String path, Uri uri) {
- LogUtils.i(TAG, "insert onScanCompleted path" + path + "uri" + uri);
- }
- });
- }
机型适配
部分机型上述两种方法都不能实现实时刷新 (只能重启手机才能看到效果), 考虑可能是第三方 ROM 的改动导致的, 将目录设置为 / sdcard/DCIM/Camera 可以解决大部分机型的问题, 但 vivo 和魅族的手机仍旧不能刷新, vivo 的地址应该为 / sdcard / 相机, 魅族的地址应该为 / sdcard/DCIM/Video
下面给出一个获取输出地址的方法
- public static String getVideoOutputDir() {
- String sdcardPath = Environment.getExternalStorageDirectory().getPath() + File.separator;
- String brand = SystemUtil.getDeviceBrand();
- if (TextUtils.isEmpty(brand)) {
- return sdcardPath + "DCIM" + File.separator + "Camera";
- }
- brand = brand.toLowerCase();
- if (brand.contains("vivo")) {
- return sdcardPath + "相机";
- } else if (brand.contains("meizu")) {
- return sdcardPath + "DCIM" + File.separator + "Video";
- }
- return sdcardPath + "DCIM" + File.separator + "Camera";
- }
- public static String getDeviceBrand() {
- return Android.os.Build.BRAND;
- }
值得一提的是, 如果想删除相册中的某个资源文件, OPPO 的某些机型 (ColorOS 3.1 系统), 会弹窗提示: 删除图库中的照片和视频权限申请, 需要用户手动去确认删除. 这个没有特别好的办法
参考资料
https://my.oschina.net/youranhongcha/blog/787223
来源: https://juejin.im/entry/5bdbefad51882517102ce791