Android系统中有一个媒体库,这个大家应该有所了解,平时在开发过程中如果不涉及媒体文件(图片、音频、视频)这块则很少接触到。有些时候我们在本地添加一张图片,但是在相册中却无法搜索到,这里主要原因就是没有通知系统媒体库刷新导致的。本篇我们就探讨下Android上媒体库的这些事。

为什么通知媒体库后,媒体库里就能找到了呢?兴许你还会遇到一种情况,就是明明相册里可以发现这张图片,可是到图片的具体路径下却找不到这张图片。到此应该会猜测到是不是媒体库和本地相册都持有一份媒体文件信息呢?基本上猜到了八九不离十了,其实媒体库就是一个数据库,专门管理媒体文件的相关信息,例如图片信息,缩略图等。

多媒体文件管理

Android多媒体文件扫描管理简单来说,有以下四部分内容:

  • 通知:MediaScannerReceiver
  • 扫描:MediaScannerService
  • 存储:MediaProvider
  • 查询:MediaStore

MediaScannerReceiver

MediaScannerReceiver主要用于接受扫描通知,然后启动MediaScannerService进行扫描操作。

    @Overridepublic void onReceive(Context context, Intent intent) {final String action = intent.getAction();final Uri uri = intent.getData();if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {// Scan internal only.scan(context, MediaProvider.INTERNAL_VOLUME);} else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {scanTranslatable(context);} else {if (uri.getScheme().equals("file")) {// handle intents related to external storageString path = uri.getPath();String externalStoragePath = Environment.getExternalStorageDirectory().getPath();String legacyPath = Environment.getLegacyExternalStorageDirectory().getPath();try {path = new File(path).getCanonicalPath();} catch (IOException e) {Log.e(TAG, "couldn't canonicalize " + path);return;}if (path.startsWith(legacyPath)) {path = externalStoragePath + path.substring(legacyPath.length());}Log.d(TAG, "action: " + action + " path: " + path);if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {// scan whenever any volume is mountedscan(context, MediaProvider.EXTERNAL_VOLUME);} else if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) &&path != null && path.startsWith(externalStoragePath + "/")) {scanFile(context, path);}}}。。。private void scan(Context context, String volume) {Bundle args = new Bundle();args.putString("volume", volume);context.startService(new Intent(context, MediaScannerService.class).putExtras(args));}private void scanFile(Context context, String path) {Bundle args = new Bundle();args.putString("filepath", path);context.startService(new Intent(context, MediaScannerService.class).putExtras(args));}private void scanTranslatable(Context context) {final Bundle args = new Bundle();args.putBoolean(MediaStore.RETRANSLATE_CALL, true);context.startService(new Intent(context, MediaScannerService.class).putExtras(args));}

从onReceive方法中可以看出,MediaScannerReceiver执行scan的时机有四种:

  1. 启动完毕,扫描内部存储和外部存储
  2. 本地语言切换
  3. sdcard挂载完毕,扫描外部存储
  4. 扫描单个文件

MediaScannerService

MediaScannerService主要负责媒体文件的扫描过程,因此是耗时的,其内部的scan()方法是扫描核心。

    private void scan(String[] directories, String volumeName) {...try {...sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));try {if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {openDatabase(volumeName);}try (MediaScanner scanner = new MediaScanner(this, volumeName)) {scanner.scanDirectories(directories);}} catch (Exception e) {Log.e(TAG, "exception in MediaScanner.scan()", e);}...} finally {sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));}}

核心内容也很简单,最终调用MediaScanner的scanDirectories()执行文件扫描。其中MediaScanner是一个专门用于媒体文件扫描的类,其scanDirectories()内部核心就是采用ContentProviderClient来对MediaProvider进行数据的更新或删除操作。

MediaProvider

我们都知道Android有四大组件,Activity、Service、BroadcastReceiver、ContentProvider。MediaProvider就是Android系统中的一个数据库,类似的还有TelephonyProvider、CalendarProvider、ContactsProvider,这些数据库的源码都在/packages/providers/目录下。

其中MediaProvider又称多媒体数据库,保存了手机上存储的所有媒体文件的信息。这个数据库存放在/data/data/com.android.providers.media/databases当中,里面有两个数据库:internal.db和external.db,internal.db存放的是系统分区的文件信息,开发者是没法通过接口获得其中的信息的,而external.db存放的则是我们用户能看到的存储区的文件信息,即包含了手机内置存储,还包含了SD卡。

MediaStore

MediaStore主要用于提供内部或外部存储中所有可用的媒体文件的各种信息,我们后边都需要借助此类来进行媒体库的操作(添加,删除等)。

媒体库信息查询

上边我们知道MediaStore是专门用于存放多媒体信息的,通过ContentResolver即可对数据库进行操作。

MediaStore中的资源有四类:

  • MediaStore.Files
  • MediaStore.Audio
  • MediaStore.Images
  • MediaStore.Video

这些内部类中都又包含了Media,Thumbnails和相应的MediaColumns,分别提供了媒体信息,缩略信息和操作字段。

查询

媒体库是通过ContentResolver来进行查询的,其核心扫描方法如下:

public final @Nullable Cursor query(@RequiresPermission.Read @NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder@Nullable CancellationSignal cancellationSignal)

开发过程中可能根据不同情况调用此方法的重载方法,下边我们就解析一下这个方法的相关入参。

Uri uri

Uri uri = MediaStore.Video.Media.INTERNAL_CONTENT_URI;
Uri uri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;

uri表示数据资源路径,总共有两种,分别对应内部存储和外部存储。

需要注意的是,MediaStore.Files没有EXTERNAL_CONTENT_URI,所以只能用getContentUri()自行获取MediaStore.Images.Files.getContentUri("external")

以MediaStore.Images.Media为例,其URI有三种写法:

Uri uri1 = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Uri uri2 = MediaStore.Images.Media.getContentUri("external");
Uri uri3 = Uri.parse("content://media/external/images/media");

String[] projection

String[] mediaColumns = {MediaStore.Video.Media._ID,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"}

定制化查询条件,这两个必须结合使用,前者表示条件语句,后者表示对应的条件参数。

String sortOrder

查询的排序方式

CancellationSignal cancellationSignal

取消正在进行的操作的信号,如果没有则为空。如果操作被取消,那么在执行查询时将抛出OperationCanceledException异常

举栗

1.利用MediaStore.Files,查询所有类型的文件:

    /*** 获取所有文件**/public static List<FileEntity> getFilesByType(Context context) {List<FileEntity> files = new ArrayList<>();// 扫描files文件库Cursor c = null;try {mContentResolver = context.getContentResolver();c = mContentResolver.query(MediaStore.Files.getContentUri("external"), null, null, null, null);int columnIndexOrThrow_ID = c.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID);int columnIndexOrThrow_MIME_TYPE = c.getColumnIndexOrThrow(MediaStore.Files.FileColumns.MIME_TYPE);int columnIndexOrThrow_DATA = c.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA);int columnIndexOrThrow_SIZE = c.getColumnIndexOrThrow(MediaStore.Files.FileColumns.SIZE);// 更改时间int columnIndexOrThrow_DATE_MODIFIED = c.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATE_MODIFIED); int tempId = 0;while (c.moveToNext()) {String path = c.getString(columnIndexOrThrow_DATA);String minType = c.getString(columnIndexOrThrow_MIME_TYPE);LogUtil.d("FileManager", "path:" + path);int position_do = path.lastIndexOf(".");if (position_do == -1) {continue;}int position_x = path.lastIndexOf(File.separator);if (position_x == -1) {continue;}String displayName = path.substring(position_x + 1, path.length());long size = c.getLong(columnIndexOrThrow_SIZE);long modified_date = c.getLong(columnIndexOrThrow_DATE_MODIFIED);File file = new File(path);String time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(file.lastModified()));FileEntity info = new FileEntity();info.setName(displayName);info.setPath(path);info.setSize(ShowLongFileSzie(size));info.setId((tempId++) + "");info.setTime(time);files.add(info);}} catch (Exception e) {e.printStackTrace();} finally {if (c != null) {c.close();}}return files;}

2.指定获取文件字段

String[] columns = new String[]{MediaStore.Files.FileColumns._ID, MediaStore.Files.FileColumns.MIME_TYPE, MediaStore.Files.FileColumns.SIZE, MediaStore.Files.FileColumns.DATE_MODIFIED, MediaStore.Files.FileColumns.DATA};
c = mContentResolver.query(MediaStore.Files.getContentUri("external"), columns, null, null, null);

3.根据文件夹的名称查询

//查找文件夹ScreenRecord下的文件
c = mContentResolver.query(MediaStore.Files.getContentUri("external"), null, MediaStore.Video.Media.BUCKET_DISPLAY_NAME+"=?", "ScreenRecord", null);

4.查询指定类型的文件

String select = "(" + MediaStore.Files.FileColumns.DATA + " LIKE '%.doc'" + " or " + MediaStore.Files.FileColumns.DATA + " LIKE '%.docx'" + ")";
c = mContentResolver.query(MediaStore.Files.getContentUri("external"), null, select , null, null);

5.指定排序类型,如根据id倒序查询

c = mContentResolver.query(MediaStore.Files.getContentUri("external"), null, null, null, MediaStore.Files.FileColumns._ID+"DESC");

刷新媒体库

媒体库刷新方法

刷新媒体库常用的有如下几种方式:

  1. 通过ContentProvider操作媒体数据库。
  2. 发送广播更新MediaStore。
  3. 通过操作MediaScannerConnection类。

通过ContentProvider操作媒体数据库

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);

和上边所讲的媒体库信息查询一样,直接对数据库操作。需要注意的是这种方式不能和其他刷新媒体库方式公用,有可能同时存入两张一模一样的文件。

发送广播更新MediaStore进行刷新

Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile(new File(filePath)));
context.sendBroadcast(intent);

在Android4.4之前,是可以通过ACTION_MEDIA_MOUNTED广播,来通知系统刷新MediaStore的,4.4后系统封闭这种方式,取而代之的是ACTION_MEDIA_SCANNER_SCAN_FILE,建议单个文件扫描插入。

通过操作MediaScannerConnection类进行刷新

Android4.0系统API中多了一个更新媒体库的方法——MediaScannerConnection,这也是我们比较推荐的。MediaScannerConnection有一个静态方法scanFile(),可直接操作此方法完成媒体库刷新操作。并且可对其刷新完成后回调更新。

public static void insert(Context context, String[] paths, String[] types) {MediaScannerConnection.scanFile(context,paths,types,new MediaScannerConnection.OnScanCompletedListener() {@Overridepublic void onScanCompleted(String path, Uri uri) {LogUtils.i(TAG, "insert onScanCompleted path " + path + " uri " + uri);}});
}

当然你也可以实现MediaScannerConnection.MediaScannerConnectionClient来进行扫描,在构造方法中执行connect(),在onScanCompleted()方法中执行disconnect()关闭链接,在onMediaScannerConnected()中执行scanFile()进行扫描。

媒体文件添加后刷新

通常我们在图片或者音视频添加后,在需要更新的地方执行刷新媒体库操作才能在媒体库中看到。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {new MediaScanner(context, file);
} else {Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);intent.setData(Uri.fromFile(file));context.sendBroadcast(intent);
}

媒体文件删除后刷新

有时候我们需要删除本地图片同时又希望刷新一下媒体库,让媒体库中去除此图片,以上边刷新媒体库的方式大多都是insert模式,那我们只能直接操作数据库了。需要注意file.delete()后不可立即将file置为null;

if (!file.exists()) {String filePath = file.getAbsolutePath();if (filePath.endsWith(".mp4")) {context.getContentResolver().delete(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,MediaStore.Audio.Media.DATA + "= \"" + filePath + "\"",null);} else if (filePath.endsWith(".jpg") || filePath.endsWith(".png") || filePath.endsWith(".bmp")) {context.getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,MediaStore.Audio.Media.DATA + "= \"" + filePath + "\"",null);}return;
}

一个项目中使用的例子:

public class MediaScanner implements MediaScannerConnection.MediaScannerConnectionClient {private static final String TAG = MediaScanner.class.getSimpleName();/*** 刷新媒体库** @param context* @param file*/public static void refresh(Context context, File file) {if (context == null || file == null) {return;}//如果图片不存在,删除媒体库中记录if (!file.exists()) {String filePath = file.getAbsolutePath();if (filePath.endsWith(".mp4")) {context.getContentResolver().delete(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,MediaStore.Audio.Media.DATA + "= \"" + filePath + "\"",null);} else if (filePath.endsWith(".jpg") || filePath.endsWith(".png") || filePath.endsWith(".bmp")) {context.getContentResolver().delete(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,MediaStore.Audio.Media.DATA + "= \"" + filePath + "\"",null);}return;}//4.0以上的系统使用MediaScanner更新if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {new MediaScanner(context, file);} else {Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);intent.setData(Uri.fromFile(file));context.sendBroadcast(intent);}}private File mFile;private MediaScannerConnection mMsc;private MediaScanner(Context context, File file) {this.mFile = file;this.mMsc = new MediaScannerConnection(context, this);mMsc.connect();}@Overridepublic void onMediaScannerConnected() {mMsc.scanFile(mFile.getAbsolutePath(), null);}@Overridepublic void onScanCompleted(String path, Uri uri) {mMsc.disconnect();}
}

刷新过滤

有时候,我们有一些目录下的媒体文件,并不想让MediaStore扫描到,例如在SDCard上缓存的图片、图标等,这些我们都不想出现在系统相册内。怎么办呢?

很简单,文件夹中新建一个.nomedia的空文件,会屏蔽掉系统默认的媒体库扫描。带有该文件的文件夹只能通过文件遍历的方式进行扫描。

总结

以上是有关媒体库开发过程中的知识点,系统媒体库是维护了一个有关媒体文件的数据库,在开发过程中只有在需要相册内的图片或者音视频更新时才需要刷新媒体库,这点个人建议适量使用不可滥用,否则有可能会造成媒体库文件泛滥,或者媒体库中有相应文件的预览,本地却不存在此文件的bug。

参考

  • https://developer.android.com/guide/topics/data/data-storage.html
  • https://blog.csdn.net/yann02/article/details/92844364
  • https://juejin.im/post/5ae0541df265da0b9d77e45a
  • https://zhuanlan.zhihu.com/p/46533159
  • https://www.bbsmax.com/A/amd0omej5g/

Android媒体库你了解多少相关推荐

  1. android 媒体库扫描,如何扫描出Android系统媒体库中视频文件

    Android系统启动时会去扫描系统文件,并将系统支持的视频文件(mp4,3gp,wmv)扫描到媒体库(MediaStore)中,下面代码演示如何获得这些文件的信息: publicstatic Lis ...

  2. 转载—android 媒体库数据更新解决办法总结

    转载-原文地址:http://blog.csdn.net/trent1985/article/details/23907093 在项目中,我们经常要创建个自己的目录,里面存放一些图片啊文件之类,比如: ...

  3. Android 媒体库图片,音频,视频,文件的查询

    项目开发中我们要使用到本地SD卡中的媒体文件,ContentResolver 可以很方便的帮助我们查询所有信息. – 1.ContentResolver 中我们要使用到的两个方式的讲解 > 通过 ...

  4. android 监听媒体库,一个蛋疼的功能,监听android系统媒体库的变动

    思考了很久,最后决定写博客,这是我入android坑两年多以来的第一篇博客,如果写的不好,往见谅. 废话不多说,直接上菜!!! 最近遇到一个非常奇葩的功能,做一个类似相册类的应用,名曰:智能相册,涉及 ...

  5. Android项目小记——nomedia文件与MediaStore媒体库

    避免相册显示 避免相册显示项目底下的多媒体文件,如.mp4..png等文件.在目录下放置一个.nomedia文件,则可以放置媒体库扫描该目录. 问题 项目使用了Android原生的分享功能,该功能需要 ...

  6. android sdcard文件存储 + 媒体库更新方法

    2019独角兽企业重金招聘Python工程师标准>>> 图片存储 if (Environment.getExternalStorageState().equals(Environme ...

  7. Android 删除图片后刷新媒体库

    有时候在App内删除了图片,调用图片选择器会看到删除了的图片还存在(显示不出来),这是由于图片被删除了,但是媒体库的记录还存在,没有同步刷新造成的,手机重启后媒体库会被刷新. 我们可以在删除文件后调用 ...

  8. Android网络库的比较:OkHTTP,Retrofit和Volley [关闭]

    本文翻译自:Comparison of Android networking libraries: OkHTTP, Retrofit, and Volley [closed] Two-part que ...

  9. GitHub 上排名前 100 的 Android 开源库介绍

    转自:http://www.codeceo.com/article/github-top-100-android-libs.html 本项目主要对目前 GitHub 上排名前 100 的 Androi ...

  10. Android 媒体播放框架MediaSession分析与实践

    版权声明:本文为博主原创文章,未经博主允许不得转载 源码:AnliaLee/BauzMusic 大家要是看到有错误的地方或者有啥好的建议,欢迎留言评论 前言 最近一直在忙着学习和研究音乐播放器,发现介 ...

最新文章

  1. 陀螺仪、罗经、IMU、MEMS四者的区别
  2. 选择省份时,自动显示对应省份的城市
  3. (四)训练运行Deep CycleGAN以进行移动风格迁移
  4. HDU2066--一个人的旅行(Dijkstra)
  5. 二阶振荡环节的谐振频率_什么是谐振器?谐振器与振荡器有什么区别?
  6. Carrot2 - Wikipedia, the free encyclopedia
  7. NB-IOT物联网模块BC26问题合集
  8. 双态运维联盟首个“共研基地”落户云南电网信息中心
  9. 神经元结构示意图讲解图,神经元的结构示意图
  10. atom的linux版本,Atom平台多版本Linux性能测试
  11. visual studio 调试php,使用visual studio code调试php代码
  12. CNN卷积神经网络(图解CNN)
  13. Jira Seraph 中的身份验证绕过漏洞(CVE-2022-0540)
  14. find linux 指定后缀_文件查找:find命令,文件名后缀
  15. a++与++a同a--与--a代码拆分
  16. 对话吴恩达:伟大的AI企业需要CEO的全力支持
  17. 汽车电子之功能安全介绍
  18. 7种方式企业内部资料共享,你pick谁?
  19. 移动端网页开发(一)
  20. 宝德服务器硬盘报警,宝德服务器基础培训.ppt

热门文章

  1. 2022软考高项十大领域知识整理(四)-人力资源管理、干系人管理、采购管理
  2. Android快速开发,十个最常用的框架
  3. 计算机网络双绞线和交叉线的区别,直连线和双绞线有什么区别?
  4. 如果批评《说好不哭》不自由,则赞美周杰伦无意义
  5. ansys linux卸载干净,安装了几次ansys14.5,都没有成功,删除重新安装后许可安装不了了...
  6. 佳能相机G7 Mark Ⅱ (测光与对焦)
  7. 参加第2届全国高校大数据教学研讨会总结
  8. 硬盘分区工具找不到硬盘该怎么办
  9. 面试题:一个人走到岔道处,有2人,一人只说真话一人只说假话,只能问其中一人一个问题,怎么解决?
  10. 大厂是怎样对待线上故障的?