1. 创建MediaScanner
认识一下MediaScanner,它的代码如下所示:
**MediaScanner.java**
~~~
public class MediaScanner
{
static {
/*
加载libmedia_jni.so,这么重要的库竟然放在如此不起眼的MediaScanner类中加载。
个人觉得,可能是因为开机后多媒体系统中最先启动的就是媒体扫描工作吧。
*/
System.loadLibrary("media_jni");
native_init();
}
//创建媒体扫描器
public MediaScanner(Context c) {
native_setup();//调用JNI层的函数做一些初始化工作
......
}
~~~
在上面的MS中,比较重要的几个调用函数是:
- native_init和native_setup,关于它们的故事,在分析JNI层时再做介绍。
MS创建好后,MSS将调用它的scanDirectories开展扫描工作,下面来看这个函数。
2. scanDirectories的分析
scanDirectories的代码如下所示:
**MediaScanner.java**
~~~
public void scanDirectories(String[]directories, String volumeName) {
try {
long start = System.currentTimeMillis();
initialize(volumeName);//①初始化
prescan(null);//②扫描前的预处理
long prescan = System.currentTimeMillis();
for(int i = 0; i < directories.length; i++) {
/*
③ processDirectory是一个native函数,调用它来对目标文件夹进行扫描,
其中MediaFile.sFileExtensions是一个字符串,包含了当前多媒体系统所支持的
媒体文件的后缀名,例如.MP3、.MP4等。mClient为MyMediaScannerClient类型,
它是从MediaScannerClient类派生的。它的作用我们后面再做分析。
*/
processDirectory(directories[i], MediaFile.sFileExtensions,
mClient);
}
long scan = System.currentTimeMillis();
postscan(directories);//④扫描后处理
long end = System.currentTimeMillis();
......//统计扫描时间等
}
~~~
上面一共列出了四个关键点,下面逐一对其分析。
(1)initialize的分析
initialize主要是初始化一些Uri,因为扫描时需把文件的信息插入媒体数据库中,而媒体数据库针对Video、Audio、Image文件等都有对应的表,这些表的地址则由Uri表示。下面是initialize的代码:
**MediaScanner.java**
~~~
private void initialize(String volumeName) {
//得到IMediaProvider对象,通过这个对象可以对媒体数据库进行操作。
mMediaProvider=
mContext.getContentResolver().acquireProvider("media");
//初始化Uri,下面分别介绍一下。
//音频表的地址,也就是数据库中的audio_meta表。
mAudioUri =Audio.Media.getContentUri(volumeName);
//视频表地址,也就是数据库中的video表。
mVideoUri = Video.Media.getContentUri(volumeName);
//图片表地址,也就是数据库中的images表。
mImagesUri = Images.Media.getContentUri(volumeName);
//缩略图表地址,也就是数据库中的thumbs表。
mThumbsUri = Images.Thumbnails.getContentUri(volumeName);
//如果扫描的是外部存储,则支持播放列表、音乐的流派等内容。
if(!volumeName.equals("internal")) {
mProcessPlaylists = true;
mProcessGenres = true;
mGenreCache = new HashMap<String, Uri>();
mGenresUri = Genres.getContentUri(volumeName);
mPlaylistsUri = Playlists.getContentUri(volumeName);
if ( Process.supportsProcesses()) {
//SD卡存储区域一般使用FAT文件系统,所以文件名与大小写无关
mCaseInsensitivePaths = true;
}
}
}
~~~
下面看第二个关键函数prescan。
(2)prescan的分析
在媒体扫描过程中,有个令人头疼的问题,来举个例子,这个例子会贯穿在对这个问题整体分析的过程中。例子:假设某次扫描之前SD卡中有100个媒体文件,数据库中有100条关于这些文件的记录,现因某种原因删除了其中的50个媒体文件,那么媒体数据库什么时候会被更新呢?
读者别小瞧这个问题。现在有很多文件管理器支持删除文件和文件夹,它们用起来很方便,却没有对应地更新数据库,这导致了查询数据库时还能得到这些媒体文件信息,但这个文件实际上已不存在了,而且后面所有和此文件有关的操作都会因此而失败。
其实,MS已经考虑到这一点了,prescan函数的主要作用是在扫描之前把数据库中和文件相关的信息取出并保存起来,这些信息主要是媒体文件的路径,所属表的Uri。就上面这个例子来说,它会从数据库中取出100个文件的文件信息。
prescan的代码如下所示:
**MediaScanner.java**
~~~
privatevoid prescan(String filePath) throws RemoteException {
Cursor c = null;
String where = null;
String[] selectionArgs = null;
//mFileCache保存从数据库中获取的文件信息。
if(mFileCache == null) {
mFileCache = new HashMap<String, FileCacheEntry>();
}else {
mFileCache.clear();
}
......
try {
//从Audio表中查询其中和音频文件相关的文件信息。
if (filePath != null) {
where = MediaStore.Audio.Media.DATA + "=?";
selectionArgs = new String[] { filePath };
}
//查询数据库的Audio表,获取对应的音频文件信息。
c = mMediaProvider.query(mAudioUri, AUDIO_PROJECTION, where,
selectionArgs,null);
if (c != null) {
try {
while (c.moveToNext()) {
long rowId =c.getLong(ID_AUDIO_COLUMN_INDEX);
//音频文件的路径
String path =c.getString(PATH_AUDIO_COLUMN_INDEX);
long lastModified =
c.getLong(DATE_MODIFIED_AUDIO_COLUMN_INDEX);
if(path.startsWith("/")) {
String key = path;
if(mCaseInsensitivePaths) {
key =path.toLowerCase();
}
//把文件信息存到mFileCache中
mFileCache.put(key,
new FileCacheEntry(mAudioUri, rowId, path,
lastModified));
}
}
} finally {
c.close();
c = null;
}
}
......//查询其他表,取出数据中关于视频,图像等文件的信息并存入到mFileCache中。
finally {
if (c != null) {
c.close();
}
}
}
~~~
懂了前面的例子,在阅读prescan函数时可能就比较轻松了。prescan函数执行完后,mFileCache保存了扫描前所有媒体文件的信息,这些信息是从数据库中查询得来的,也就是旧有的信息。
接下来,看最后两个关键函数。
(3)processDirectory和postscan的分析
processDirectory是一个native函数,其具体功能放到JNI层再分析,这里先简单介绍,它在解决上一节那个例子中提出的问题时,所做的工作。答案是:processDirectory将扫描SD卡,每扫描一个文件,都会设置mFileCache中对应文件的一个叫mSeenInFileSystem的变量为true。这个值表示这个文件目前还存在于SD卡上。这样,待整个SD卡扫描完后,mFileCache的那100个文件中就会有50个文件的mSeenInFileSystem为true,而剩下的另50个文件则为初始值false。
看到上面的内容,可以知道postscan的作用了吧?就是它把不存在于SD卡的文件信息从数据库中删除,而使数据库得以彻底更新的。来看postscan函数是否是这样处理的:
**MediaScanner.java**
~~~
private void postscan(String[] directories)throws RemoteException {
Iterator<FileCacheEntry> iterator =mFileCache.values().iterator();
while(iterator.hasNext()) {
FileCacheEntry entry = iterator.next();
String path = entry.mPath;
boolean fileMissing = false;
if (!entry.mSeenInFileSystem) {
if (inScanDirectory(path, directories)) {
fileMissing = true; //这个文件确实丢失了
} else {
File testFile = newFile(path);
if (!testFile.exists()) {
fileMissing = true;
}
}
}
//如果文件确实丢失,则需要把数据库中和它相关的信息删除。
if(fileMissing) {
MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);
int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);
if(MediaFile.isPlayListFileType(fileType)) {
......//处理丢失文件是播放列表的情况
} else {
/*由于文件信息中还携带了它在数据库中的相关信息,所以从数据库中删除对应的信息会非常快。
*/
mMediaProvider.delete(ContentUris.withAppendedId(
entry.mTableUri, entry.mRowId), null, null);
iterator.remove();
}
}
}
......//删除缩略图文件等工作
}
~~~
Java层中的四个关键点,至此已介绍了三个,另外一个processDirectory是媒体扫描的关键函数,由于它是一个native函数,所以下面将转战到JNI层来进行分析。
- 前言
- 第1章 阅读前的准备工作
- 1.1 系统架构
- 1.1.1 Android系统架构
- 1.1.2 本书的架构
- 1.2 搭建开发环境
- 1.2.1 下载源码
- 1.2.2 编译源码
- 1.3 工具介绍
- 1.3.1 Source Insight介绍
- 1.3.2 Busybox的使用
- 1.4 本章小结
- 第2章 深入理解JNI
- 2.1 JNI概述
- 2.2 学习JNI的实例:MediaScanner
- 2.3 Java层的MediaScanner分析
- 2.3.1 加载JNI库
- 2.3.2 Java的native函数和总结
- 2.4 JNI层MediaScanner的分析
- 2.4.1 注册JNI函数
- 2.4.2 数据类型转换
- 2.4.3 JNIEnv介绍
- 2.4.4 通过JNIEnv操作jobject
- 2.4.5 jstring介绍
- 2.4.6 JNI类型签名介绍
- 2.4.7 垃圾回收
- 2.4.8 JNI中的异常处理
- 2.5 本章小结
- 第3章 深入理解init
- 3.1 概述
- 3.2 init分析
- 3.2.1 解析配置文件
- 3.2.2 解析service
- 3.2.3 init控制service
- 3.2.4 属性服务
- 3.3 本章小结
- 第4章 深入理解zygote
- 4.1 概述
- 4.2 zygote分析
- 4.2.1 AppRuntime分析
- 4.2.2 Welcome to Java World
- 4.2.3 关于zygote的总结
- 4.3 SystemServer分析
- 4.3.1 SystemServer的诞生
- 4.3.2 SystemServer的重要使命
- 4.3.3 关于 SystemServer的总结
- 4.4 zygote的分裂
- 4.4.1 ActivityManagerService发送请求
- 4.4.2 有求必应之响应请求
- 4.4.3 关于zygote分裂的总结
- 4.5 拓展思考
- 4.5.1 虚拟机heapsize的限制
- 4.5.2 开机速度优化
- 4.5.3 Watchdog分析
- 4.6 本章小结
- 第5章 深入理解常见类
- 5.1 概述
- 5.2 以“三板斧”揭秘RefBase、sp和wp
- 5.2.1 第一板斧--初识影子对象
- 5.2.2 第二板斧--由弱生强
- 5.2.3 第三板斧--破解生死魔咒
- 5.2.4 轻量级的引用计数控制类LightRefBase
- 5.2.5 题外话-三板斧的来历
- 5.3 Thread类及常用同步类分析
- 5.3.1 一个变量引发的思考
- 5.3.2 常用同步类
- 5.4 Looper和Handler类分析
- 5.4.1 Looper类分析
- 5.4.2 Handler分析
- 5.4.3 Looper和Handler的同步关系
- 5.4.4 HandlerThread介绍
- 5.5 本章小结
- 第6章 深入理解Binder
- 6.1 概述
- 6.2 庖丁解MediaServer
- 6.2.1 MediaServer的入口函数
- 6.2.2 独一无二的ProcessState
- 6.2.3 时空穿越魔术-defaultServiceManager
- 6.2.4 注册MediaPlayerService
- 6.2.5 秋风扫落叶-StartThread Pool和join Thread Pool分析
- 6.2.6 你彻底明白了吗
- 6.3 服务总管ServiceManager
- 6.3.1 ServiceManager的原理
- 6.3.2 服务的注册
- 6.3.3 ServiceManager存在的意义
- 6.4 MediaPlayerService和它的Client
- 6.4.1 查询ServiceManager
- 6.4.2 子承父业
- 6.5 拓展思考
- 6.5.1 Binder和线程的关系
- 6.5.2 有人情味的讣告
- 6.5.3 匿名Service
- 6.6 学以致用
- 6.6.1 纯Native的Service
- 6.6.2 扶得起的“阿斗”(aidl)
- 6.7 本章小结
- 第7章 深入理解Audio系统
- 7.1 概述
- 7.2 AudioTrack的破解
- 7.2.1 用例介绍
- 7.2.2 AudioTrack(Java空间)分析
- 7.2.3 AudioTrack(Native空间)分析
- 7.2.4 关于AudioTrack的总结
- 7.3 AudioFlinger的破解
- 7.3.1 AudioFlinger的诞生
- 7.3.2 通过流程分析AudioFlinger
- 7.3.3 audio_track_cblk_t分析
- 7.3.4 关于AudioFlinger的总结
- 7.4 AudioPolicyService的破解
- 7.4.1 AudioPolicyService的创建
- 7.4.2 重回AudioTrack
- 7.4.3 声音路由切换实例分析
- 7.4.4 关于AudioPolicy的总结
- 7.5 拓展思考
- 7.5.1 DuplicatingThread破解
- 7.5.2 题外话
- 7.6 本章小结
- 第8章 深入理解Surface系统
- 8.1 概述
- 8.2 一个Activity的显示
- 8.2.1 Activity的创建
- 8.2.2 Activity的UI绘制
- 8.2.3 关于Activity的总结
- 8.3 初识Surface
- 8.3.1 和Surface有关的流程总结
- 8.3.2 Surface之乾坤大挪移
- 8.3.3 乾坤大挪移的JNI层分析
- 8.3.4 Surface和画图
- 8.3.5 初识Surface小结
- 8.4 深入分析Surface
- 8.4.1 与Surface相关的基础知识介绍
- 8.4.2 SurfaceComposerClient分析
- 8.4.3 SurfaceControl分析
- 8.4.4 writeToParcel和Surface对象的创建
- 8.4.5 lockCanvas和unlockCanvasAndPost分析
- 8.4.6 GraphicBuffer介绍
- 8.4.7 深入分析Surface的总结
- 8.5 SurfaceFlinger分析
- 8.5.1 SurfaceFlinger的诞生
- 8.5.2 SF工作线程分析
- 8.5.3 Transaction分析
- 8.5.4 关于SurfaceFlinger的总结
- 8.6 拓展思考
- 8.6.1 Surface系统的CB对象分析
- 8.6.2 ViewRoot的你问我答
- 8.6.3 LayerBuffer分析
- 8.7 本章小结
- 第9章 深入理解Vold和Rild
- 9.1 概述
- 9.2 Vold的原理与机制分析
- 9.2.1 Netlink和Uevent介绍
- 9.2.2 初识Vold
- 9.2.3 NetlinkManager模块分析
- 9.2.4 VolumeManager模块分析
- 9.2.5 CommandListener模块分析
- 9.2.6 Vold实例分析
- 9.2.7 关于Vold的总结
- 9.3 Rild的原理与机制分析
- 9.3.1 初识Rild
- 9.3.2 RIL_startEventLoop分析
- 9.3.3 RIL_Init分析
- 9.3.4 RIL_register分析
- 9.3.5 关于Rild main函数的总结
- 9.3.6 Rild实例分析
- 9.3.7 关于Rild的总结
- 9.4 拓展思考
- 9.4.1 嵌入式系统的存储知识介绍
- 9.4.2 Rild和Phone的改进探讨
- 9.5 本章小结
- 第10章 深入理解MediaScanner
- 10.1 概述
- 10.2 android.process.media分析
- 10.2.1 MSR模块分析
- 10.2.2 MSS模块分析
- 10.2.3 android.process.media媒体扫描工作的流程总结
- 10.3 MediaScanner分析
- 10.3.1 Java层分析
- 10.3.2 JNI层分析
- 10.3.3 PVMediaScanner分析
- 10.3.4 关于MediaScanner的总结
- 10.4 拓展思考
- 10.4.1 MediaScannerConnection介绍
- 10.4.2 我问你答
- 10.5 本章小结