Android媒体库框架(mediascanner)
- 格式:doc
- 大小:216.00 KB
- 文档页数:6
Android多媒体框架Android是目前全球使用最广泛的移动操作系统之一,拥有强大的多媒体功能支持。
在Android上,我们可以通过多媒体框架来实现音频、视频和图像的处理和播放。
本文将介绍Android多媒体框架的概念和相关API,帮助开发者更好地掌握Android多媒体开发的技术。
1. 多媒体框架概述Android多媒体框架是一组API和工具,用于处理和播放音频、视频和图像等多媒体资源。
它为开发者提供了丰富的功能和灵活的控制,可以实现高质量的多媒体应用。
Android的多媒体框架主要包括以下几个部分:MediaRecorder(音视频录制类):用于录制音频和视频。
MediaPlayer(音视频播放类):用于播放音频和视频。
Mediac(音视频编解码类):用于音视频的编解码。
MediaExtractor(音视频分离器类):用于将音视频文件分离成音频和视频轨道。
AudioTrack(音频播放类):用于音频的低级播放。
SoundPool(音频播放类):用于短音频的播放。
ImageReader(图像读取类):用于读取图像数据。
,Android还提供了一些其他的辅助类和接口,如MediaSession、MediaBrowser、MediaMetadataRetriever等,用于更高级的多媒体功能实现和交互。
2. 音频处理和播放音频是Android多媒体开发中非常重要的一部分。
在Android 上,我们可以使用MediaPlayer或AudioTrack来处理和播放音频。
MediaPlayer是Android中最常用的音频播放类,它可以播放本地文件或网络上的音频流。
我们可以通过setDataSource()方法设置音频源,通过prepareAsync()方法准备音频流,并通过start()方法开始播放。
AudioTrack是一个低级的音频播放类,它可以直接操作音频数据。
我们可以通过创建AudioTrack对象,并设置音频参数和缓冲区来进行音频播放。
android多媒体框架Android多媒体框架1. 概述Android多媒体框架是Android系统中的一个重要模块,它提供了丰富的API和功能,用于处理音频、视频和图像等多媒体数据。
开发人员可以利用这些功能,实现多媒体相关的应用程序,如音乐播放器、视频播放器和相机等。
2. 媒体格式支持Android多媒体框架支持多种音频、视频和图像格式。
常见的音频格式包括MP3、AAC和WAV等,常见的视频格式包括MP4、AVI和MKV等。
此外,Android还支持常见的图像格式,如JPEG、PNG和GIF等。
3. 音频处理Android多媒体框架提供了对音频数据的处理能力。
开发人员可以利用框架中的API,实现音频的播放、录制和编辑等功能。
以下是一些常用的音频处理功能:- 音频播放:通过MediaPlayer类,开发人员可以实现音频的播放功能。
可以设置音量、循环播放等参数。
- 音频录制:通过MediaRecorder类,开发人员可以实现音频的录制功能。
可以设置音频编码格式、录制源等参数。
- 音频编辑:通过AudioTrack和AudioRecord类,开发人员可以实现对音频数据的实时编辑功能。
可以控制音频输入输出的格式和采样率等。
4. 视频处理Android多媒体框架提供了对视频数据的处理能力。
开发人员可以利用框架中的API,实现视频的播放、录制和编辑等功能。
以下是一些常用的视频处理功能:- 视频播放:通过VideoView和SurfaceView类,开发人员可以实现视频的播放功能。
可以控制视频的尺寸、播放状态等。
- 视频录制:通过MediaRecorder类,开发人员可以实现视频的录制功能。
可以设置视频编码格式、录制源等参数。
- 视频编辑:通过MediaCodec类,开发人员可以实现对视频数据的实时编辑功能。
可以实现视频的剪切、合并等操作。
5. 图像处理Android多媒体框架提供了对图像数据的处理能力。
android mediarecorder 原理[Android MediaRecorder 原理]在Android开发中,我们经常会遇到需要使用摄像头和麦克风进行视频和音频录制的需求。
而为了满足这一需求,Android系统提供了一个非常方便的类——MediaRecorder。
MediaRecorder类可以用来实现音频和视频的录制,并且可以设置多种参数以满足不同的需求。
本文将会详细解释Android MediaRecorder的原理,包括其基本用法、内部实现原理以及一些常见问题的解决方法。
# 什么是MediaRecorder?MediaRecorder是Android系统提供的一个多媒体录制类,可以用来录制音频和视频。
使用MediaRecorder可以方便地实现录制功能,无需自己实现复杂的音视频处理逻辑。
同时,MediaRecorder还提供了丰富的配置选项,可以满足不同场景下的录制需求。
# MediaRecorder的基本用法要使用MediaRecorder进行音视频录制,首先需要获取摄像头和麦克风的权限。
一般情况下,我们需要在AndroidManifest.xml文件中添加相应的权限声明:xml<uses-permission android:name="android.permission.CAMERA" /><uses-permission android:name="android.permission.RECORD_AUDIO" />接着,我们需要通过Camera类获取摄像头实例,并且初始化MediaRecorder:javacamera = Camera.open();mediaRecorder = new MediaRecorder();然后,我们需要设置MediaRecorder的参数,例如设置音频源、视频源、输出格式、输出文件路径等。
An droid 的MediaPlayer 架构介绍【IT168技术文档】本文主要介绍的是Android中很重要也最为复杂的媒体播放器( MediaPlayer)部分的架构。
对于Android这样一个完整又相对复杂的系统,一个MediaPlayer功能的实现不在其具体的功能,而是具体功能如何适应Android系统Android MediaPlayer的主要具体实现在OpenCore的Player中,这部分不是本文的关注点。
本文关注的是MediaPlayer系统的架构,其他的一些Android的应用程序也使用类似的架构。
对于开源事业在中国的发展,hanchao3c认为应该共享的不仅仅是代码,文档、设计思想、理念甚至对于技术的理解都应该得到充分的共享。
Android为中国人进入大规模的开源项目提供了很好的机遇,对于走在技术前沿的人们,不应将技术视为私有财产,而应该将自己理解更好地奉献给大众,提高大众的学习速度,从中也可以得到反馈,从而促进自己的进步。
仅以此文奉献给所有关系技术的朋友,希望可以抛砖引玉,促进我们共同的技术进步!第一部分MediaPlayer概述Android 的MediaPlayer 包含了Audio 和video 的播放功能,在Android 的界面上,Music 和Video 两个应用程序都是调用MediaPlayer实现的。
MediaPlayer 在底层是基于OpenCore(PacketVideo)的库实现的,为了构建一个MediaPlayer 程序,上层还包含了进程间通讯等内容,这种进程间通讯的基础是An droid基本库中的Bin der 机制。
以开源的An droid为例MediaPlayer的代码主要在以下的目录中:JAVA程序的路径:packages/apps/Music/src/com/a ndroid/music/JAVA类的路径:frameworks/base/media/java/a ndroid/media/MediaPlayer.javaJAVA本地调用部分(JNI):frameworks/base/media/j ni/a ndroid media MediaPlayer.cpp 这部分内容编译成为目标是libmedia_j ni.so 。
Android的MediaRecorder框架介绍框架AndroidJNI应用服务器多线程第一部分 MediaRecorder概述Android的MediaRecorder包含了Audio和video的记录功能,在Android的界面上,Music和Video两个应用程序都是调用MediaRecorder实现的。
MediaRecorder在底层是基于OpenCore(PacketVideo)的库实现的,为了构建一个MediaRecorder程序,上层还包含了进程间通讯等内容,这种进程间通讯的基础是Android基本库中的Binder机制。
以开源的Android为例MediaRecorder的代码主要在以下的目录中:JAVA程序的路径:/packages/apps/SoundRecorder/src/com/android/soundrecorder/SoundRecorder.java JAVA Framework的路径:frameworks/base/media/java/android/media/MediaRecorder.javaJAVA本地调用部分(JNI):frameworks/base/media/jni/android_media_MediaRecorder.cpp这部分内容编译成为目标是libmedia_jni.so。
主要的头文件在以下的目录中:frameworks/base/include/media/多媒体底层库在以下的目录中:frameworks/base/media/libmedia/这部分的内容被编译成库libmedia.so。
多媒体服务部分:frameworks/base/media/libmediaplayerservice/MediaRecorder和MeidaPlayer使用相同的服务。
基于OpenCore部分PVAuthor是基于OpenCore的AuthorEngine的Android实现,代码在以下路径中: external/opencore/android/author这部分内容被编译成库libopencoreauthor.so。
MediaScanner分析模块工作流程MediaScannerReceiverMediaScannerReceiver代码:packages\providers\MediaProvider\src\com\android\providers\media\MediaScannerReceiver. javaMediaScannerReceiver是用来接收广播任务的,它收到广播后,会启动MediaService进行扫描工作。
MediaScannerReceiver从BroadcastReceiver派生,主要用来接收广播任务。
ACTION_BOOT_COMPLETEDACTION_MEDIA_MOUNTEDACTION_MEDIA_SCANNER_SCAN_FILEMediaScannerReceive会在以上3个intent发出时启动 MediaScannerService。
MediaScannerServiceMediaScannerService代码:packages\providers\MediaProvider\src\com\android\providers\media\MediaScannerService.j avapublic class MediaScannerService extends Service implements Runnable MediaScannerService是一个service,在onCreate中单独启动一个带消息循环的子线程,主线程通过onStartCommand接收系统发送来的任务,再由mServiceHandler.sendMessage(msg)发送到子线程,子线程消息处理函数handleMessage(Message msg)通过解析消息,创建MediaScanner去执行扫描MediaScanner该部分代码在frameworks\base\media\目录下,包括jni和java文件,MediaScanner的调用过程复杂,而且和MediaProvider交互频繁。
mediacontroller方法1.引言1.1 概述概述在现代社会中,媒体播放器已成为人们日常生活中不可或缺的一部分。
随着手机和电脑等移动设备的普及,人们越来越多地使用这些设备来收听音乐、观看视频或玩游戏。
然而,如何在这些设备上方便地控制媒体播放成为了一个重要的问题。
为了解决这个问题,Android操作系统引入了一个重要的类——MediaController。
MediaController类是Android提供的一个用于控制媒体播放的工具类。
它提供了一系列的方法和功能,使用户可以方便地控制音乐或视频的播放。
此外,MediaController类还提供了一些额外的控制选项,如播放、暂停、快进、快退等功能,用户可以通过触摸屏幕或点击按钮来操作。
这使得用户可以根据自己的需要来调整音量、跳过特定的部分或切换到其他媒体文件。
在本文中,我们将详细介绍MediaController方法的定义与功能,并探讨它在不同场景下的使用方式。
我们还将总结MediaController方法的优势和应用,并展望其在未来的发展前景。
通过阅读本文,读者将了解到如何合理地使用MediaController类及其方法来提高媒体播放的操作性和用户体验。
1.2文章结构1.2 文章结构本文将围绕着"MediaController方法"展开讨论。
为了更好地探究MediaController方法的定义、功能以及其在实际应用中的使用场景,本文按照以下方式组织文章内容:第二部分将详细介绍MediaController方法的定义与功能。
首先,将给出MediaController方法的准确定义,解释它是什么以及它所具备的功能。
接下来,将详细讨论MediaController方法的各个组成部分以及它们之间的关系,以帮助读者全面了解这个方法的内涵。
第三部分将深入探讨MediaController方法的使用场景。
通过实际案例和经典示例,将展示MediaController方法在不同领域中的应用情况。
android应⽤刷新系统多媒体库(增加or删除多媒体⽂件)系统:android4.4及其以上功能:app中拍照,并实现浏览、删除照⽚操作。
实现:1.拍照,存储到指定路径path2.通知系统多媒体数据库刷新数据。
主要使⽤MediaScannerConnection,该类向应⽤提供了将新增多媒体⽂件发送给多媒体扫描服务的⽅法,进⽽将数据写⼊到系统多媒体数据库,参考实现如下:public class MediaScanner {private MediaScannerConnection mediaScanConn = null;private PhotoSannerClient client = null;private String filePath = null;private String fileType = null;private static MediaScanner mediaScanner= null;/*** 然后调⽤MediaScanner.scanFile("/sdcard/2.mp3");* */public MediaScanner(Context context) {// 创建MusicSannerClientif (client == null) {client = new PhotoSannerClient();}if (mediaScanConn == null) {mediaScanConn = new MediaScannerConnection(context, client);}}public static MediaScanner getInstanc(Context context){if (mediaScanner==null){mediaScanner = new MediaScanner(context);}return mediaScanner;}private class PhotoSannerClient implementsMediaScannerConnection.MediaScannerConnectionClient {public void onMediaScannerConnected() {if (filePath != null) {mediaScanConn.scanFile(filePath, fileType);}filePath = null;fileType = null;}public void onScanCompleted(String path, Uri uri) {// TODO Auto-generated method stubmediaScanConn.disconnect();}}/*** 扫描⽂件标签信息** @param filePath* ⽂件路径 eg:/sdcard/MediaPlayer/dahai.mp3* @param fileType* ⽂件类型 eg: audio/mp3 media/* application/ogg* */public void scanFile(String filepath, String fileType) {this.filePath = filepath;this.fileType = fileType;// 连接之后调⽤MusicSannerClient的onMediaScannerConnected()⽅法mediaScanConn.connect();}public String getFilePath() {return filePath;}public void setFilePath(String filePath) {this.filePath = filePath;}public String getFileType() {return fileType;}public void setFileType(String fileType) {this.fileType = fileType;}}View Code3.删除照⽚,并删除多媒体数据库中的相关内容。
Android 多媒体扫描过程(Android Media Scanner Process)下面是系统图MediaScannerReceiver 会在任何的ACTION_BOOT_COMPLETED, ACTION_MEDIA_MOUNTED 或ACTION_MEDIA_SCANNER_SCAN_FILE 意图(intent )发出的时候启动。
因为解析媒体文件的元数据或许会需要很长时间,所以MediaScannerReceiver 会启动MediaScannerService 。
MediaScannerService 调用一个公用类MediaScanner 去处理真正的工作。
MediaScannerReceiver 维持两种扫描目录:一种是内部卷(internal volume )指向$(ANDROID_ROOT)/media. 另一种是外部卷(external volume )指向$(EXTERNAL_STORAGE).扫描和解析工作位于JAVA 层和C++ 层。
JAVA 层是启动器。
MediaScanner 扫描所有目录,如下步骤:1.JAVA 层初始化在这一步骤中,它会根据目录是在内部卷还是外部卷打开不同的数据库。
2.Java 层预扫描首先清除文件和播放列表的缓存条目。
然后根据MediaProvider 返回的请求结果生成新文件和播放列表缓存条目。
3.C++ 层处理目录列举出所有文件和特定的所有子目录(如果子目录包含一个.nomedia 隐藏文件,则不会被列举出来。
)。
被列举的文件是根据文件扩展来判断文件是否被支持。
如果支持这种文件扩展,C++ 层就会回调到JAVA 层扫描文件。
这种扩展就会被扫描到MediaFile.java 中列出。
下面是支持的文件扩展列表。
/* Audio */addFileType("MP3", FILE_TYPE_MP3, "audio/mpeg");addFileType("M4A", FILE_TYPE_M4A, "audio/mp4");addFileType("WAV", FILE_TYPE_WAV, "audio/x-wav");addFileType("AMR", FILE_TYPE_AMR, "audio/amr");addFileType("AWB", FILE_TYPE_AWB, "audio/amr-wb");addFileType("WMA", FILE_TYPE_WMA, "audio/x-ms-wma");addFileType("OGG", FILE_TYPE_OGG, "application/ogg");addFileType("MID", FILE_TYPE_MID, "audio/midi");addFileType("XMF", FILE_TYPE_MID, "audio/midi");addFileType("RTTTL", FILE_TYPE_MID, "audio/midi");addFileType("SMF", FILE_TYPE_SMF, "audio/sp-midi");addFileType("IMY", FILE_TYPE_IMY, "audio/imelody");/* Video */addFileType("MP4", FILE_TYPE_MP4, "video/mp4");addFileType("M4V", FILE_TYPE_M4V, "video/mp4");addFileType("3GP", FILE_TYPE_3GPP, "video/3gpp");addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp");addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2");addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2");addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv");/* Image */addFileType("JPG", FILE_TYPE_JPEG, "image/jpeg");addFileType("JPEG", FILE_TYPE_JPEG, "image/jpeg");addFileType("GIF", FILE_TYPE_GIF, "image/gif");addFileType("PNG", FILE_TYPE_PNG, "image/png");addFileType("BMP", FILE_TYPE_BMP, "image/x-ms-bmp");addFileType("WBMP", FILE_TYPE_WBMP, "image/vnd.wap.wbmp");/* Audio Play List */addFileType("M3U", FILE_TYPE_M3U, "audio/x-mpegurl");addFileType("PLS", FILE_TYPE_PLS, "audio/x-scpls");addFileType("WPL", FILE_TYPE_WPL, "application/vnd.ms-wpl");4.Java 层扫描文件a )Java 层开始文件首先它忽略一些MacOS 和Windows Media Player 特殊的文件。
android源码解析------Media多媒体framework层分析1.packages\providers\MediaProvider :含以下 java 文件MediaProvider.javaMediaScannerReceiver.javaMediaScannerService.javaMediaThumbRequest.java2.查看该目录下AndroidMainfest.xml,从MediaScannerService.java 入手。
if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {//收到” 启动完毕“广播后,扫描内部存储/system/media目录,扫描手机内存中的媒体文件scan(context, MediaProvider.INTERNAL_VOLUME);}if (action.equals(Intent.ACTION_MEDIA_MOUNTED) &&externalStoragePath.equals(path)) {// 收到MOUNT 信息后,扫描外部存储,/mnt/sdcard,sdcard 挂载完毕后扫描扩展卡的媒体文件scan(context, MediaProvider.EXTERNAL_VOLUME);}if(action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) && path != null && path.startsWith(externalStoragePath + "/")) { // 收到请求启动单个文件的扫描工作,注意这个文件必须位于SD 卡上。
scanFile(context, path);}通过scan(...),scanFile(...)函数启动MediaScannerService.context.startService(newIntent(context,MediaScannerService.class).putExtras(args));3.MediaScannerService.javaonCreate(...)函数开启新的线程。
Android平台上的媒体文件管理和桌面系统不同。
在桌面系统上,不同目录下的媒体文件呈树状结构显示给用户,用户需要进入不同目录寻找该目录下的文件。
而在Android平台上,不同目录下的媒体文件则以一层列表方式显示给用户,用户不需进入子目录就可以列出(某种类型的)所有媒体文件。
在Android上,为了实现这种模式的媒体文件管理,对所有管理的媒体文件抽取其元数据,也就是ID3(mp3文件包含的元数据可参考/wiki/ID3),存储在数据库中,并作为一个content provider提供给其他应用使用。
用户的每一次显示媒体文件的操作,就是对这个数据库的一次查询操作。
在多媒体管理模块中,主要分成三个模块:
多媒体数据库
MediaStore这个类是android系统提供的一个多媒体数据库,android中多媒体信息都可以从这里提取。
这个MediaStore包括了多媒体数据库的所有信息,包括音频,视频和图像,android把所有的多媒体数据库接口进行了封装,所有的数据库不用自己进行创建,直接调用利用ContentResolver去掉用那些封装好的接口就可以进行数据库的操作,多媒体数据库的使用方法和SQLITE3的方法是一样的。
MediaStore中的数据是在MediaScanner扫描后通过MediaProvider中的一个service进行更新的。
框架图如下:
MediaScanner
在Android系统中,多媒体库是通过MediaScanner去扫描磁盘文件,对元信息的处理,并通过MediaProvider保存到MediaStore中。
下图为MediaScannerr 框架:
图1-1 MediaScanner框架流程
MediaScanner可以通过手动控制,在ANDROID系统中,已经定制了三种事件会触发MediaScanner去扫描磁盘文件:ACTION_BOOT_COMPLETED、ACTION_MEDIA_MOUNTED、ACTION_MEDIA_SCANNER_SCAN_FILE。
其中ACTION_BOOT_COMPLETED是系统启动完后发出这个消息,ACTION_MEDIA_MOUNTED是插卡事件触发的消息,ACTION_MEDIA_SCANNER_SCAN_FILE 消息一般是在一些文件操作后,开发人员手动发出的一个重新扫描多媒体文件的消息。
发送消息通过sendBroadcast函数完成,比如广播一个ACTION_MEDIA_MOUNTED消息:sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"
+ Environment.getExternalStorageDirectory())));
由上可知是通过发送了一个广播(传递对应的扫描要求)来触发重新扫描磁盘事件,那么可以猜测系统肯定存在一个广播接收器(何时何地注册?),在收到这个广播消息后,通过对应参数启动MediaScannerService。
MediaScannerService调用一个公用类MediaScanner去处理真正的工作。
MediaScannerReceiver维持两种扫描目录:一种是内部卷(internal volume)指向$(ANDROID_ROOT)/media. 另一种是外部卷(external volume)指向$(EXTERNAL_STORAGE),扫描的位置可以修改(一般外部不用修改,默认为SDCARD,内部根据驱动命名的INAND路经名做对应的修改),对应图1-1系统源码具体位置:MediaScannerReceiver:
/mydroid/packages/providers/MediaProvider/src/com/android/providers/media/Media ScannerReceiver.java
Scanner事件接收,继承了BroadcastReceiver类,收到扫描消息后启动MediaScannerService,但有一点要注意的是: Service的onCreate的方法只会被调用一次,就是你无论多少次的startService又 bindService,Service只被创建一次。
如果先是bind 了,那么start的时候就直接运行Service的 onStart方法,如果先是start,那么bind的时
候就直接运行onBind方法。
如果你先bind上了,就stop不掉了,对啊,就是stopService不好使了,只能先UnbindService, 再StopService,所以是先start还是先bind行为是有区别的。
(关于BINDLE接口请参考其它文档)
MediaScannerService:
/mydroid/packages/providers/MediaProvider/src/com/android/providers/media/M ediaScannerService.java
通过此服务,去调用MediaScanner的具体实现方法。
MediaScanner: (方法)
\frameworks\base\media\java\android\media\ MediaScanner.java
MediaPlayer整体流程:
Thumbnails缩略图概述
缩略图保存位置:/sdcard/DCIM/.thumbnails
缩略图(大):一张张的JPEG文件
所有的都存入同一个二进制文件.thumbdata+version+”-”+hashcode,字节大小均为10000bytes
详解:
对于每一张图片,都会生成大缩略图和小缩略图,大缩略图保存在(外部设备)
/sdcard/DCIM/.thumbnails/ 目录下,大缩略图大小上限是512 X 384,下限1 X1。
小缩略图被统一保存到一个随机访问文件(外部设备)/sdcard/DCIM/.thumbnails/
+".thumbdata" + version + "-"+ mUri.hashcode()
缩略图存储到数据库里面
扫描一个文件的最后endfile()会mMediaProvider.insert()或者mMediaProvider.update ()。
在mMediaProvider.insert()时对于IMAGES_MEDIA 和VIDEO_MEDIA类型:requestMediaThumbnail()--->mCurrentThumbRequest.execute(); 在执行时会首先会去缩略图的数据库中查询,查询到就返回,未查询到会
ThumbnailUtil.createVideoThumbnail(mPath)或者
ThumbnailUtil.createImageThumbnail(mCr, mPath, mUri,
mOrigId,Images.Thumbnails.MICRO_KIND, true/*saveMini*/));
创建图片缩略图:
创建thumbnail时调用ThumbnailUtil.makeBitmap()创建;如果saveMini为真会保存缩略图ThumbnailUtil.storeThumbnail(cr, origId, thumbData, bitmap.getWidth(), bitmap.getHeight());; 保存时会通过origId向数据库查询,查到返回对应的uri,未查找到就插入数据库并返回uri,最终返回bitmap。
对于创建视频缩略图:直接在视频文件中取一帧得到bintmap并返回保存大缩略图到文件:
press(pressFormat.JPEG, 75, miniOutStream);保存小缩略图到随机访问文件:data = miniOutStream.toByteArray();
miniThumbFile.saveMiniThumbToFile(data, mOrigId, magic);
在mMediaProvider.update()时,对IMAGES_MEDIA,IMAGES_MEDIA_ID,VIDEO_MEDIA,VIDEO_MEDIA_ID类型会查询magic,如果没查到会执行requestMediaThumbnail(),流程同上。
MediaProvider是对上面整个流程的实现。
可参考里面的代码。
图3 MediaProvider 代码架构。