当前位置: 首页 > article >正文

Android开发——MediaProvider源码分析

转自http://www.cnblogs.com

 

--------------START------------

MediaProvider包括五个类:

  • com.android.providers.media.MediaProvider
  • com.android.providers.media.MediaScannerCursor
  • com.android.providers.media.MediaScannerReceiver
  • com.android.providers.media.MediaScannerService
  • com.android.providers.media.MediaThumbRequest

1.MediaProvider

此类继承ContentProvider,实现一个内容提供者。主要用于创建媒体库的数据库表。有自己创建过ContentProvider的同学相信都比较清楚的。

特别说明一下在MediaProvider中有个广播接收者,代码如下:

   1:
private
BroadcastReceiver mUnmountReceiver = new
BroadcastReceiver() {
   2:
@Override
   3:
public
void
onReceive(Context context, Intent intent) {
   4:
if
(intent.getAction().equals(Intent.ACTION_MEDIA_EJECT)) {
   5:
// Remove the external volume and then notify all cursors backed by
   6:
// data on that volume
   7:
detachVolume(Uri.parse("content://media/external"
));
   8:
sFolderArtMap.clear();
   9:
MiniThumbFile.reset();
  10:
}
  11:
}
  12:
};

此接收者是用来接收Sdcard卸载的广播。当Sdcard从手机中分离出来的时候,Sdcard中的媒体文件相对应的数据库将无法操作。

   1:
private
void
detachVolume(Uri uri) {
   2:
//判断是否是同一个进程
   3:
if
(Process.supportsProcesses() && Binder.getCallingPid() != Process.myPid()) {
   4:
throw
new
SecurityException(
   5:
"Opening and closing databases not allowed."
);
   6:
}
   7:
//此方法只是操作Sdcard的媒体数据库,不支持手机内存的媒体数据库
   8:
String volume = uri.getPathSegments().get(0);
   9:
if
(INTERNAL_VOLUME.equals(volume)) {
  10:
throw
new
UnsupportedOperationException(
  11:
"Deleting the internal volume is not allowed"
);
  12:
} else
if
(!EXTERNAL_VOLUME.equals(volume)) {
  13:
throw
new
IllegalArgumentException(
  14:
"There is no volume named "
+ volume);
  15:
}
  16:
  17:
synchronized (mDatabases) {
  18:
DatabaseHelper database = mDatabases.get(volume);
  19:
if
(database == null
) 
return
;
   21:
try
{
  22:
// touch the database file to show it is most recently used
  23:
File file = new
File(database.getReadableDatabase().getPath());
  24:
file.setLastModified(System.currentTimeMillis());
  25:
} catch
(SQLException e) {
  26:
Log.e(TAG, "Can't touch database file"
, e);
  27:
}
  28:
//移除数据库
  29:
mDatabases.remove(volume);
  30:
database.close();
  31:
}
  32:
  33:
getContext().getContentResolver().notifyChange(uri, null
);
  34:
if
(LOCAL_LOGV) Log.v(TAG, "Detached volume: "
+ volume);
  35:
}

注意移除数据库并非删除数据库文件(*.db),mDatabases是一个HashMap<String,DatabaseHelper>,移除的含义是暂时无法操作,也可以说说是查询返回的数据都是空的。

2.MediaScannerCursor

一个自定义游标,用来查询媒体文件的扫描状态。主要有一个volume字段,用来区分是内置媒体数据库还是Sdcard的媒体数据库。

3.MediaScannerReceiver

此类实现广播接收者。接收到广播的时候对手机的媒体文件进行扫描。

   1:
public
class
MediaScannerReceiver extends BroadcastReceiver
   2:
{
   3:
private
final static
String TAG = "MediaScannerReceiver"
;
   4:
   5:
@Override
   6:
public
void
onReceive(Context context, Intent intent) {
   7:
String action = intent.getAction();
   8:
Uri uri = intent.getData();
   9:
String externalStoragePath = Environment.getExternalStorageDirectory().getPath();
  10:
//系统启动完毕
  11:
if
(action.equals(Intent.ACTION_BOOT_COMPLETED)) {
  12:
// scan internal storage
  13:
scan(context, MediaProvider.INTERNAL_VOLUME);
  14:
} else
{
  15:
if
(uri.getScheme().equals("file"
)) {
  16:
// handle intents related to external storage
  17:
String path = uri.getPath();
  18:
if
(action.equals(Intent.ACTION_MEDIA_MOUNTED/*Sdcard挂载广播*/
) && 
  19:
externalStoragePath.equals(path)) {
  20:
scan(context, MediaProvider.EXTERNAL_VOLUME);
  21:
} else
if
(action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE/*单个文件扫描广播*/
) &&
  22:
path != null
&& path.startsWith(externalStoragePath + "/"
)) {
  23:
scanFile(context, path);
  24:
}
  25:
}
  26:
}
  27:
}

扫描分为两种三种情况:

a,启动完毕扫面手机内存中的媒体文件

b.sdcard挂载完毕扫描扩展卡的媒体文件

c,扫描单个文件

应用实例:我们可以发送不同的广播让系统去扫描媒体文件。当需要扫描单个文件的时候需要设置一些参数,如下:

   1:
/**
   2:
     * 扫描文件
   3:
     * 
   4:
     * @param filePath 文件路径
   5:
     * @author http://t.sina.com.cn/halzhang
   6:
     */
   7:
public
void
scanOneFile(final String filePath) {
   8:
Intent intent = new
Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
   9:
Uri uri = Uri.parse("file://"
+ filePath);
  10:
intent.setData(uri);
  11:
sendBroadcast(intent);
  12:
}

接着看一下scan scenFile 两个方法:

   1:
private
void
scan(Context context, String volume/*内置卡或者外置卡*/
) {
   2:
Bundle args = new
Bundle();
   3:
args.putString("volume"
, volume);
   4:
context.startService(
   5:
new
Intent(context, MediaScannerService.class
).putExtras(args));
   6:
}    
   7:
   8:
private
void
scanFile(Context context, String path/*文件路径*/
) {
   9:
Bundle args = new
Bundle();
  10:
args.putString("filepath"
, path);
  11:
context.startService(
  12:
new
Intent(context, MediaScannerService.class
).putExtras(args));
  13:
}  

两个方法都是启动MediaScannerService去扫描媒体文件的。

关于MediaScannerSerive且听下回分解。

-------------------END--------------

 

----------------------START---------------------------

在 上一篇文章中说到系统当接收到扫描请求广播的时候就会调用scan或者scanFile去扫描手机(手机内存和sdcard)中的媒体文件。这两个方法都 是启动MediaScannerService这个服务来完成扫描任务的。接下来我们来看看MediaScannerService是怎么工作的……

4.MediaScannerService(MSS)

MSS实现了Runnable,所以必然的需要实现run方法了,代码如下:

   1:
public
void
run()
   2:
{
   3:
// reduce priority below other background threads to avoid interfering
   4:
// with other services at boot time.
   5:
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND +
   6:
Process.THREAD_PRIORITY_LESS_FAVORABLE);
   7:
Looper.prepare();
   8:
   9:
mServiceLooper = Looper.myLooper();
  10:
mServiceHandler = new
ServiceHandler();
  11:
  12:
Looper.loop();
  13:
}

在run方法中设置了线程的优先级,优先级比较低,主要为了避免跟其他服务抢夺资源。还有就是利用looper对ServiceHandler的消息进行循环控制。

接着看一下ServiceHandler 的实现代码:

   1:
private
final class
ServiceHandler extends Handler
   2:
{
   3:
@Override
   4:
public
void
handleMessage(Message msg)
   5:
{
   6:
Bundle arguments = (Bundle) msg.obj;
   7:
//获取文件路径
   8:
String filePath = arguments.getString("filepath"
);
   9:
  10:
try
{
  11:
if
(filePath != null
) {
  12:
//文件路径不为空,则调用扫面当个文件的方法
  13:
IBinder binder = arguments.getIBinder("listener"
);
  14:
IMediaScannerListener listener = 
  15:
(binder == null
? null
: IMediaScannerListener.Stub.asInterface(binder));
  16:
Uri uri = scanFile(filePath, arguments.getString("mimetype"
));//扫描单个文件
  17:
if
(listener != null
) {
  18:
//执行扫描完成方法
  19:
listener.scanCompleted(filePath, uri);
  20:
}
  21:
} else
{
  22:
//如果文件路径为空,则获取扫面手机内存或者sdcard
  23:
String volume = arguments.getString("volume"
);
  24:
String[] directories = null
;
  25:
//内置卡
  26:
if
(MediaProvider.INTERNAL_VOLUME.equals(volume)) {
  27:
// scan internal media storage
  28:
directories = new
String[] {
  29:
Environment.getRootDirectory() + "/media"
,
  30:
};
  31:
}//外置卡
  32:
else
if
(MediaProvider.EXTERNAL_VOLUME.equals(volume)) {
  33:
// scan external storage
  34:
directories = new
String[] {
  35:
Environment.getExternalStorageDirectory().getPath(),
  36:
};
  37:
}
  38:
  39:
if
(directories != null
) {
  40:
if
(Config.LOGD) Log.d(TAG, "start scanning volume "
+ volume);
  41:
//扫描
  42:
scan(directories, volume);
  43:
if
(Config.LOGD) Log.d(TAG, "done scanning volume "
+ volume);
  44:
}
  45:
}
  46:
} catch
(Exception e) {
  47:
Log.e(TAG, "Exception in handleMessage"
, e);
  48:
}
  49:
  50:
stopSelf(msg.arg1);
  51:
}
  52:
};

在ServiceHandler中主要根据相关参数来调用不同的扫描方法。大笑

那是在哪里调用ServiceHandler发送消息的呢?请看如下代码:

   1:
@Override
   2:
public
void
onCreate() {
   3:
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
   4:
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
   5:
//启用新线程,这样就可以避免阻塞,执行run,初始化成员变量loop和handler
   6:
Thread thr = new
Thread(null
, this
, "MediaScannerService"
);
   7:
thr.start();
   8:
}
   9:
  10:
@Override
  11:
public
int
onStartCommand(Intent intent, int
flags, int
startId) {
  12:
while
(mServiceHandler == null
) {
  13:
synchronized (this
) {
  14:
try
{
  15:
wait(100);
  16:
} catch
(InterruptedException e) {
  17:
}
  18:
}
  19:
}
  20:
  21:
if
(intent == null
) {
  22:
Log.e(TAG, "Intent is null in onStartCommand: "
, new
NullPointerException());
  23:
return
Service.START_NOT_STICKY;
  24:
}
  25:
  26:
Message msg = mServiceHandler.obtainMessage();
  27:
msg.arg1 = startId;
  28:
msg.obj = intent.getExtras();
  29:
//ServiceHandler发送消息
  30:
mServiceHandler.sendMessage(msg);
  31:
  32:
// Try again later if we are killed before we can finish scanning.
  33:
return
Service.START_REDELIVER_INTENT;
  34:
}
  35:
  36:
@Override
  37:
public
void
onDestroy() {
  38:
// Make sure thread has started before telling it to quit.
  39:
while
(mServiceLooper == null
) {
  40:
synchronized (this
) {
  41:
try
{
  42:
wait(100);
  43:
} catch
(InterruptedException e) {
  44:
}
  45:
}
  46:
}
  47:
mServiceLooper.quit();
  48:
}
以上三个方法是属于Service的生命周期的。当我们调用startService的时候,如果对应的Service还未创建就会调用 onCreate方法===方法。每次startService的时候就调用onStartCommand,所以ServiceHandler就在此发送 消息了。

最后,稍微看一下MSS里面扫描方面。主要是调用MediaScanner对媒体文件进行扫描分析的。至于MediaScanner的实现以后在分析。

   1:
private
void
openDatabase(String volumeName) {
   2:
try
{
   3:
ContentValues values = new
ContentValues();
   4:
values.put("name"
, volumeName);
   5:
getContentResolver().insert(Uri.parse("content://media/"
), values);
   6:
} catch
(IllegalArgumentException ex) {
   7:
Log.w(TAG, "failed to open media database"
);
   8:
}         
   9:
}
  10:
  11:
private
void
closeDatabase(String volumeName) {
  12:
try
{
  13:
getContentResolver().delete(
  14:
Uri.parse("content://media/"
+ volumeName), null
, null
);
  15:
} catch
(Exception e) {
  16:
Log.w(TAG, "failed to close media database "
+ volumeName + " exception: "
+ e);
  17:
}
  18:
}
  19:
//创建扫描器
  20:
private
MediaScanner createMediaScanner() {
  21:
MediaScanner scanner = new
MediaScanner(this
);
  22:
Locale locale = getResources().getConfiguration().locale;
  23:
if
(locale != null
) {
  24:
String language = locale.getLanguage();
  25:
String country = locale.getCountry();
  26:
String localeString = null
;
  27:
if
(language != null
) {
  28:
if
(country != null
) {
  29:
scanner.setLocale(language + "_"
+ country);
  30:
} else
{
  31:
scanner.setLocale(language);
  32:
}
  33:
}    
  34:
}
  35:
  36:
return
scanner;
  37:
}
  38:
//扫描目录
  39:
private
void
scan(String[] directories, String volumeName) {
  40:
// don't sleep while scanning
  41:
mWakeLock.acquire();
  42:
  43:
ContentValues values = new
ContentValues();
  44:
values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);
  45:
Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values);
  46:
  47:
Uri uri = Uri.parse("file://"
+ directories[0]);
  48:
sendBroadcast(new
Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));
  49:
  50:
try
{
  51:
if
(volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {
  52:
openDatabase(volumeName);    
  53:
}
  54:
  55:
MediaScanner scanner = createMediaScanner();
  56:
scanner.scanDirectories(directories, volumeName);
  57:
} catch
(Exception e) {
  58:
Log.e(TAG, "exception in MediaScanner.scan()"
, e); 
  59:
}
  60:
  61:
getContentResolver().delete(scanUri, null
, null
);
  62:
  63:
sendBroadcast(new
Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));
  64:
mWakeLock.release();
  65:
}
  66:
//扫描文件
  67:
private
Uri scanFile(String path, String mimeType) {
  68:
String volumeName = MediaProvider.INTERNAL_VOLUME;
  69:
String externalStoragePath = Environment.getExternalStorageDirectory().getPath();
  70:
  71:
if
(path.startsWith(externalStoragePath)) {
  72:
volumeName = MediaProvider.EXTERNAL_VOLUME;
  73:
openDatabase(volumeName);
  74:
}
  75:
MediaScanner scanner = createMediaScanner();
  76:
//扫描单个文件
  77:
return
scanner.scanSingleFile(path, volumeName, mimeType);
  78:
}

在MediaProvider中还有一个类:MediaThumbRequest,用来创建预览图的,比如视频的预览图,图片的预览图,音频的专辑图片…这些图片的信息也是保存在数据库的,有兴趣的同学可以自己打开数据库看看里面的表。如下图:

media_db_tables

啰哩啰唆的写了两篇文章,希望对大家有所帮助。

其中应该有不少是错误的观点,望大家指正。

----------------------END------------------------------

 

神马是MediaScanner呢?在Android的SDK里面是看不到这个类的,因为被google隐藏了。通过Android的源码我们可以看到MediaScanner的类注解多了一个@hide的标注。所以对于一般应用开发者,此文意义不是很大,大家可以绕道。

在前两篇文章中,最后我们都了解了Android的媒体文件的扫描是在MediaScannerService中调用MediaScanner的 scanDirectories或者scanSingleFile完成最终的扫描的。那么MediaScanner是如何工作的呢?

 

google对MediaScanner写了一大堆的类注释,如下:

   1:
/* In summary:
   2:
* Java MediaScannerService calls
   3:
* Java MediaScanner scanDirectories, which calls
   4:
* Java MediaScanner processDirectory (native method), which calls
   5:
* native MediaScanner processDirectory, which calls
   6:
* native MyMediaScannerClient scanFile, which calls
   7:
* Java MyMediaScannerClient scanFile, which calls
   8:
* Java MediaScannerClient doScanFile, which calls
   9:
* Java MediaScanner processFile (native method), which calls
  10:
* native MediaScanner processFile, which calls
  11:
* native parseMP3, parseMP4, parseMidi, parseOgg or parseWMA, which calls
  12:
* native MyMediaScanner handleStringTag, which calls
  13:
* Java MyMediaScanner handleStringTag.
  14:
* Once MediaScanner processFile returns, an entry is inserted in to the database.
  15:
*
  16:
* {@hide}
  17:
*/

下面为调用时序图,如下:

这时序图好像不是很规范!点击看大图 !请见谅。吐舌笑脸 开始看代码把……

1,scanDirectories。

初始化数据并调用processDirectory 处理扫描。

   1:
public
void
scanDirectories(String[] directories, String volumeName) {
   2:
try
{
   3:
long
start = System.currentTimeMillis();
   4:
//初始化
   5:
initialize(volumeName);
   6:
//将数据库中的数据缓存到mFileCache
   7:
/*
   8:
             * mFileCache.put(key, new FileCacheEntry(uri, rowId, path, lastModified));
   9:
             */
  10:
prescan(null
);
  11:
long
prescan = System.currentTimeMillis();
  12:
  13:
for
(int
i = 0; i < directories.length; i++) {
  14:
//扫描处理
  15:
processDirectory(directories[i], MediaFile.sFileExtensions, mClient);
  16:
}
  17:
long
scan = System.currentTimeMillis();
  18:
//处理后续数据
  19:
postscan(directories);
  20:
long
end = System.currentTimeMillis();

2,processDirectory

这是一个native方法,所以我们直接转向jni,代码如下:

   1:
static
void
   2:
android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, jobject client)
   3:
{   //获取MediaScanner
   4:
MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context);
   5:
//参数判断,并抛出异常
   6:
if
(path == NULL) {
   7:
jniThrowException(env, "java/lang/IllegalArgumentException"
, NULL);
   8:
return
;
   9:
}
  10:
if
(extensions == NULL) {
  11:
jniThrowException(env, "java/lang/IllegalArgumentException"
, NULL);
  12:
return
;
  13:
}
  14:
  15:
const
char
*pathStr = env->GetStringUTFChars(path, NULL);
  16:
if
(pathStr == NULL) {  // Out of memory
  17:
jniThrowException(env, "java/lang/RuntimeException"
, "Out of memory"
);
  18:
return
;
  19:
}
  20:
const
char
*extensionsStr = env->GetStringUTFChars(extensions, NULL);
  21:
if
(extensionsStr == NULL) {  // Out of memory
  22:
env->ReleaseStringUTFChars(path, pathStr);
  23:
jniThrowException(env, "java/lang/RuntimeException"
, "Out of memory"
);
  24:
return
;
  25:
}
  26:
//初始化client实例
  27:
MyMediaScannerClient myClient(env, client);
  28:
//mp调用processDirectory
  29:
mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env);
  30:
//gc
  31:
env->ReleaseStringUTFChars(path, pathStr);
  32:
env->ReleaseStringUTFChars(extensions, extensionsStr);
  33:
}

3,mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env);

   1:
status_t MediaScanner::processDirectory(const
char
*path, const
char
* extensions,
   2:
MediaScannerClient& client, ExceptionCheck exceptionCheck, void
* exceptionEnv)
   3:
{//这方法不知道干吗的,估计跟线程有关
   4:
InitializeForThread();
   5:
   6:
int
pathLength = strlen(path);
   7:
if
(pathLength >= PATH_MAX) {
   8:
return
PVMFFailure;
   9:
}
  10:
char
* pathBuffer = (char
*)malloc(PATH_MAX + 1);
  11:
if
(!pathBuffer) {
  12:
return
PVMFFailure;
  13:
}
  14:
  15:
int
pathRemaining = PATH_MAX - pathLength;
  16:
strcpy(pathBuffer, path);
  17:
if
(pathBuffer[pathLength - 1] != '/'
) {
  18:
pathBuffer[pathLength] = '/'
;
  19:
pathBuffer[pathLength + 1] = 0;
  20:
--pathRemaining;
  21:
}
  22:
  23:
client.setLocale(mLocale);
  24:
//有是一个关键点
  25:
status_t result = doProcessDirectory(pathBuffer, pathRemaining, extensions, client, exceptionCheck, exceptionEnv);
  26:
//释放内存
  27:
free(pathBuffer);
  28:
return
result;
  29:
}

4,doProcessDirectory

   1:
status_t MediaScanner::doProcessDirectory(char
*path, int
pathRemaining, const
char
* extensions,
   2:
MediaScannerClient& client, ExceptionCheck exceptionCheck, void
* exceptionEnv)
   3:
{
   4:
……
   5:
……
   6:
if
(type == DT_REG || type == DT_DIR) {
   7:
int
nameLength = strlen(name);
   8:
bool
isDirectory = (type == DT_DIR);
   9:
  10:
if
(nameLength > pathRemaining || (isDirectory && nameLength + 1 > pathRemaining)) {
  11:
// path too long!
  12:
continue
;
  13:
}
  14:
  15:
strcpy(fileSpot, name);
  16:
if
(isDirectory) {
  17:
// ignore directories with a name that starts with '.'
  18:
// for example, the Mac ".Trashes" directory
  19:
if
(name[0] == '.'
) continue
;
  20:
  21:
strcat(fileSpot, "/"
);
  22:
//文件夹,递归调用
  23:
int
err = doProcessDirectory(path, pathRemaining - nameLength - 1, extensions, client, exceptionCheck, exceptionEnv);
  24:
if
(err) {
  25:
// pass exceptions up - ignore other errors
  26:
if
(exceptionCheck && exceptionCheck(exceptionEnv)) goto
failure;
  27:
LOGE("Error processing '%s' - skipping/n"
, path);
  28:
continue
;
  29:
}
  30:
} else
if
(fileMatchesExtension(path, extensions)) {
  31:
//文件,扩展名符合
  32:
struct
stat statbuf;
  33:
stat(path, &statbuf);
  34:
if
(statbuf.st_size > 0) {
  35:
//调用client的scanFile方法
  36:
client.scanFile(path, statbuf.st_mtime, statbuf.st_size);
  37:
}
  38:
if
(exceptionCheck && exceptionCheck(exceptionEnv)) goto
failure;
  39:
}
  40:
}
  41:
……
  42:
……

5,client.scanFile

   1:
// returns true if it succeeded, false if an exception occured in the Java code
   2:
virtual
bool
scanFile(const
char
* path, long
long
lastModified, long
long
fileSize)
   3:
{
   4:
jstring pathStr;
   5:
if
((pathStr = mEnv->NewStringUTF(path)) == NULL) return
false
;
   6:
//有点反射的感觉,调用java里面mClient中的scanFile方法
   7:
mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);
   8:
   9:
mEnv->DeleteLocalRef(pathStr);
  10:
return
(!mEnv->ExceptionCheck());
  11:
}

6,mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize); 让我们回到Java大笑

在android.media.MediaScanner.MyMediaScannerClient中的scanFile方法是直接调用doScanFile的,来看看doScanFile

   1:
   2:
public
Uri doScanFile(String path, String mimeType, long
lastModified, long
fileSize,
   3:
boolean scanAlways) {
   4:
Uri result = null
;
   5:
// long t1 = System.currentTimeMillis();
   6:
try
{
   7:
FileCacheEntry entry = beginFile(path, mimeType, lastModified, fileSize);
   8:
// rescan for metadata if file was modified since last scan
   9:
if
(entry != null
&& (entry.mLastModifiedChanged || scanAlways)) {
  10:
String lowpath = path.toLowerCase();
  11:
boolean ringtones = (lowpath.indexOf(RINGTONES_DIR) > 0);
  12:
boolean notifications = (lowpath.indexOf(NOTIFICATIONS_DIR) > 0);
  13:
boolean alarms = (lowpath.indexOf(ALARMS_DIR) > 0);
  14:
boolean podcasts = (lowpath.indexOf(PODCAST_DIR) > 0);
  15:
boolean music = (lowpath.indexOf(MUSIC_DIR) > 0)
  16:
|| (!ringtones && !notifications && !alarms && !podcasts);
  17:
  18:
if
(isMetadataSupported(mFileType)) {
  19:
// 调用jni方法
  20:
processFile(path, mimeType, this
);
  21:
} else
if
(MediaFile.isImageFileType(mFileType)) {
  22:
// we used to compute the width and height but it's not
  23:
// worth it
  24:
}
  25:
  26:
result = endFile(entry, ringtones, notifications, alarms, music, podcasts);
  27:
}
  28:
} catch
(RemoteException e) {
  29:
Log.e(TAG, "RemoteException in MediaScanner.scanFile()"
, e);
  30:
}
  31:
// long t2 = System.currentTimeMillis();
  32:
// Log.v(TAG, "scanFile: " + path + " took " + (t2-t1));
  33:
return
result;
  34:
}
补充:result = endFile(entry, ringtones, notifications, alarms, music, podcasts);就是在这里将媒体数据信息存放到数据库的

7,接着是native的 processFile

   1:
static
void
   2:
android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client)
   3:
{
   4:
MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context);
   5:
   6:
if
(path == NULL) {
   7:
jniThrowException(env, "java/lang/IllegalArgumentException"
, NULL);
   8:
return
;
   9:
}
  10:
  11:
const
char
*pathStr = env->GetStringUTFChars(path, NULL);
  12:
if
(pathStr == NULL) {  // Out of memory
  13:
jniThrowException(env, "java/lang/RuntimeException"
, "Out of memory"
);
  14:
return
;
  15:
}
  16:
const
char
*mimeTypeStr = (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL);
  17:
if
(mimeType && mimeTypeStr == NULL) {  // Out of memory
  18:
env->ReleaseStringUTFChars(path, pathStr);
  19:
jniThrowException(env, "java/lang/RuntimeException"
, "Out of memory"
);
  20:
return
;
  21:
}
  22:
  23:
MyMediaScannerClient myClient(env, client);
  24:
//调用MediaScanner的processFile
  25:
mp->processFile(pathStr, mimeTypeStr, myClient);
  26:
env->ReleaseStringUTFChars(path, pathStr);
  27:
if
(mimeType) {
  28:
env->ReleaseStringUTFChars(mimeType, mimeTypeStr);
  29:
}
  30:
}
8,mp->processFile(pathStr, mimeTypeStr, myClient);
在此方法中根据不同的文件扩展名调用更加底层的解析方法,我想主要是ID3信息解析
   1:
status_t MediaScanner::processFile(const
char
*path, const
char
* mimeType, MediaScannerClient& client)
   2:
{
   3:
status_t result;
   4:
InitializeForThread();
   5:
//初始化client
   6:
client.setLocale(mLocale);
   7:
client.beginFile();
   8:
   9:
//LOGD("processFile %s mimeType: %s/n", path, mimeType);
  10:
const
char
* extension = strrchr(path, '.'
);
  11:
//根据扩展名调用不同的解析方法
  12:
if
(extension && strcasecmp(extension, ".mp3"
) == 0) {
  13:
result = parseMP3(path, client);
  14:
} else
if
(extension &&
  15:
(strcasecmp(extension, ".mp4"
) == 0 || strcasecmp(extension, ".m4a"
) == 0 ||
  16:
strcasecmp(extension, ".3gp"
) == 0 || strcasecmp(extension, ".3gpp"
) == 0 ||
  17:
strcasecmp(extension, ".3g2"
) == 0 || strcasecmp(extension, ".3gpp2"
) == 0)) {
  18:
result = parseMP4(path, client);
  19:
} else
if
(extension && strcasecmp(extension, ".ogg"
) == 0) {
  20:
result = parseOgg(path, client);
  21:
} else
if
(extension &&
  22:
( strcasecmp(extension, ".mid"
) == 0 || strcasecmp(extension, ".smf"
) == 0
  23:
|| strcasecmp(extension, ".imy"
) == 0)) {
  24:
result = parseMidi(path, client);
  25:
} else
if
(extension &&
  26:
(strcasecmp(extension, ".wma"
) == 0 || strcasecmp(extension, ".aac"
) == 0)) {
  27:
//TODO: parseWMA needs to be renamed to reflect what it is really doing,
  28:
//ie. using OpenCORE frame metadata utility(FMU) to retrieve metadata.
  29:
result = parseWMA(path, client);
  30:
} else
{
  31:
result = PVMFFailure;
  32:
}
  33:
//调用client
  34:
client.endFile();
  35:
  36:
return
result;
  37:
}

9,client.endFile()

   1:
void
MediaScannerClient::endFile()
   2:
{
   3:
if
(mLocaleEncoding != kEncodingNone) {
   4:
int
size = mNames->size();
   5:
uint32_t encoding = kEncodingAll;
   6:
   7:
// compute a bit mask containing all possible encodings
   8:
for
(int
i = 0; i < mNames->size(); i++)
   9:
encoding &= possibleEncodings(mValues->getEntry(i));
  10:
  11:
// if the locale encoding matches, then assume we have a native encoding.
  12:
if
(encoding & mLocaleEncoding)
  13:
convertValues(mLocaleEncoding);
  14:
  15:
// finally, push all name/value pairs to the client
  16:
for
(int
i = 0; i < mNames->size(); i++) {
  17:
//在handleStringTag中是通过类反射的方法调用java中的handleStringTag
  18:
if
(!handleStringTag(mNames->getEntry(i), mValues->getEntry(i)))
  19:
break
;
  20:
}
  21:
}
  22:
// else addStringTag() has done all the work so we have nothing to do
  23:
  24:
delete mNames;
  25:
delete mValues;
  26:
mNames = NULL;
  27:
mValues = NULL;
  28:
}

10,java中的handleStringTag ,这个方法主要处理那些在底层解析后的数据返回到java层

   1:
public
void
handleStringTag(String name, String value
) {
   2:
if
(name.equalsIgnoreCase("title"
) || name.startsWith("title;"
)) {
   3:
// Don't trim() here, to preserve the special /001 character
   4:
// used to force sorting. The media provider will trim() before
   5:
// inserting the title in to the database.
   6:
mTitle = value
;
   7:
} else
if
(name.equalsIgnoreCase("artist"
) || name.startsWith("artist;"
)) {
   8:
mArtist = value
.trim();
   9:
} else
if
(name.equalsIgnoreCase("albumartist"
) || name.startsWith("albumartist;"
)) {
  10:
mAlbumArtist = value
.trim();
  11:
} else
if
(name.equalsIgnoreCase("album"
) || name.startsWith("album;"
)) {
  12:
mAlbum = value
.trim();
  13:
} else
if
(name.equalsIgnoreCase("composer"
) || name.startsWith("composer;"
)) {
  14:
mComposer = value
.trim();
  15:
} else
if
(name.equalsIgnoreCase("genre"
) || name.startsWith("genre;"
)) {
  16:
// handle numeric genres, which PV sometimes encodes like "(20)"
  17:
if
(value
.length() > 0) {
  18:
int
genreCode = -1;
  19:
char
ch = value
.charAt(0);
  20:
if
(ch == '('
) {
  21:
genreCode = parseSubstring(value
, 1, -1);
  22:
} else
if
(ch >= '0'
&& ch <= '9'
) {
  23:
genreCode = parseSubstring(value
, 0, -1);
  24:
}
  25:
if
(genreCode >= 0 && genreCode < ID3_GENRES.length) {
  26:
value
= ID3_GENRES[genreCode];
  27:
}
  28:
}
  29:
mGenre = value
;
  30:
} else
if
(name.equalsIgnoreCase("year"
) || name.startsWith("year;"
)) {
  31:
mYear = parseSubstring(value
, 0, 0);
  32:
} else
if
(name.equalsIgnoreCase("tracknumber"
) || name.startsWith("tracknumber;"
)) {
  33:
// track number might be of the form "2/12"
  34:
// we just read the number before the slash
  35:
int
num = parseSubstring(value
, 0, 0);
  36:
mTrack = (mTrack / 1000) * 1000 + num;
  37:
} else
if
(name.equalsIgnoreCase("discnumber"
) ||
  38:
name.equals("set"
) || name.startsWith("set;"
)) {
  39:
// set number might be of the form "1/3"
  40:
// we just read the number before the slash
  41:
int
num = parseSubstring(value
, 0, 0);
  42:
mTrack = (num * 1000) + (mTrack % 1000);
  43:
} else
if
(name.equalsIgnoreCase("duration"
)) {
  44:
mDuration = parseSubstring(value
, 0, 0);
  45:
} else
if
(name.equalsIgnoreCase("writer"
) || name.startsWith("writer;"
)) {
  46:
mWriter = value
.trim();
  47:
}
  48:
}

此致,此文结束,累。

三篇有关MediaScanner的文章,希望对大家有所帮助。

对C/C++不是很熟悉,如发现分析有误,请告知。

http://www.lryc.cn/news/2420964.html

相关文章:

  • android中的surface
  • 10款.net 图形插件
  • QQ邮箱模拟登录
  • 安卓唯一标识:IMEI,MEID,MAC地址,Android_id,UUID,OAID
  • 汽车电子常用外围硬件电路设计
  • Instagram 账号被封怎么办?如何申诉拿回账号?
  • JavaScript异步编程学习
  • 使用SharePoint进行编程
  • 系统调用之sys_adjtimex
  • vb.net合伙数据库access(一)——连接数据库
  • Direct3D 9 入门例子程序 圆锥体
  • 使用51单片机来实现步进电机的控制
  • RCS
  • 免费的XP/Vista无损分区软件 EASEUS Partition Master
  • 电脑动态屏保_8款电脑软件,每一款都能让你的电脑更好用
  • 网盘介绍
  • 2层框架结构柱子间距_框架结构的梁柱截面尺寸如何确定
  • Bios读文件与Grub(bootload)和initrd和内核对文件系统驱动的支持
  • Hystrix的降级与熔断测试
  • 3000字计算机领域技术发展,计算机应用技术专业毕业论文3000字
  • troublemaker中文谐音_饿狼传说谐音歌词
  • umts是移动还是联通_网络模式中的UMTS是什么意思?
  • java 开发网站_适用于高级Java开发人员的十大网站
  • ewebeditor编辑器ASP/ASPX/PHP/JSP版本漏洞利用总结及解决方法
  • 汉字编码及区位码查询算法
  • 指南】计算机二级C语言上机操作指南
  • 【软件测试】单元测试
  • FN函数小结
  • CTF中那些脑洞大开的编码和加密
  • n个结点,不同形态的二叉树(数目+生成)