MediaScanner源码相关分析

来源:互联网 发布:在线饲料配方软件 编辑:程序博客网 时间:2024/05/10 11:42

http://wangwenbingood1988.blog.163.com/blog/static/35154593201352410548507/

另:http://www.apkbus.com/android-5376-1-1.html

由于特殊原因,得一点点的学framework了。由于工作时长接触到mediascanner,于是今天就先从它看起i,缕了下,有了点头绪,在此记录,仅供参考。如有错误,海涵!


我很耐心的写的,是整个mediascanner过程,所以有点长,请耐心一点点的顺着看!

在看MediaScanner前,先看到MediaScanner.java文件开头处的注释,官方的流程介绍:

/** * Internal service helper that no-one should use directly. * * The way the scan currently works is: * - The Java MediaScannerService creates a MediaScanner (this class), and calls * MediaScanner.scanDirectories on it. * - scanDirectories() calls the native processDirectory() for each of the specified directories. * - the processDirectory() JNI method wraps the provided mediascanner client in a native * 'MyMediaScannerClient' class, then calls processDirectory() on the native MediaScanner * object (which got created when the Java MediaScanner was created). * - native MediaScanner.processDirectory() calls * doProcessDirectory(), which recurses over the folder, and calls * native MyMediaScannerClient.scanFile() for every file whose extension matches. * - native MyMediaScannerClient.scanFile() calls back on Java MediaScannerClient.scanFile, * which calls doScanFile, which after some setup calls back down to native code, calling * MediaScanner.processFile(). * - MediaScanner.processFile() calls one of several methods, depending on the type of the * file: parseMP3, parseMP4, parseMidi, parseOgg or parseWMA. * - each of these methods gets metadata key/value pairs from the file, and repeatedly * calls native MyMediaScannerClient.handleStringTag, which calls back up to its Java * counterparts in this file. * - Java handleStringTag() gathers the key/value pairs that it's interested in. * - once processFile returns and we're back in Java code in doScanFile(), it calls * Java MyMediaScannerClient.endFile(), which takes all the data that's been * gathered and inserts an entry in to the database. * * In summary: * Java MediaScannerService calls * Java MediaScanner scanDirectories, which calls * Java MediaScanner processDirectory (native method), which calls * native MediaScanner processDirectory, which calls * native MyMediaScannerClient scanFile, which calls * Java MyMediaScannerClient scanFile, which calls * Java MediaScannerClient doScanFile, which calls * Java MediaScanner processFile (native method), which calls * native MediaScanner processFile, which calls * native parseMP3, parseMP4, parseMidi, parseOgg or parseWMA, which calls * native MyMediaScanner handleStringTag, which calls * Java MyMediaScanner handleStringTag. * Once MediaScanner processFile returns, an entry is inserted in to the database. * * The MediaScanner class is not thread-safe, so it should only be used in a single threaded manner. * * {@hide} */


MediaScannerReceiver.java文件用于接收系统重启后,已经插拔卡,以及文件复制与拷贝等消息,收到消息后会进行扫描。启动MediaScannerService,进行相关操作,MediaScannerService的主线程接收通知,然后单独启动一个handler去处理消息。

MediaScannerReceiver.java文件很简单,先看一下

public class MediaScannerReceiver extends BroadcastReceiver { private final static String TAG = "MediaScannerReceiver"; @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)) {

//系统启动完毕后会发送broadcast,MediaScannerReceiver收到消息开始扫描内外存储设备,

//在此,我的理解,internal是存储系统相关的,external存储的是所有的自带存储空间以及外面插入的sd卡等信息 // Scan both internal and external storage scan(context, MediaProvider.INTERNAL_VOLUME); scan(context, MediaProvider.EXTERNAL_VOLUME); } else { if (uri.getScheme().equals("file")) { // handle intents related to external storage String path = uri.getPath(); String externalStoragePath = Environment.getExternalStorageDirectory().getPath(); Log.d(TAG, "action: " + action + " path: " + path); if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) { // scan whenever any volume is mounted

// sd卡挂载后扫描,也可以看出,他只扫描external ----> external.db中 scan(context, MediaProvider.EXTERNAL_VOLUME); } else if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) && path != null && path.startsWith(externalStoragePath + "/")) {

//如果是有文件拷贝进入,且路径不为空,且是在第一存储设备上(手机存储有预先制定好的ExternalStorage

//与SecondStorage,并不是说外卡就是externalStorage,内卡就是SecondStorage,我在项目中犯过这个错误理解)

scanFile(context, path);//扫描单个文件 } } } } private void scan(Context cdontext, String volume) {

//启动MediaScannerService,扫描目录

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

倒是很简洁,其启动了MediaScannerService,打开 android4.2/packages/providers/mediaprovider/src/com/android/providers/media/MediaScannerService.java

 //onCreate 函数

@Override public void onCreate() { PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);

//获取屏幕常亮,扫描期间不停歇 StorageManager storageManager = (StorageManager)getSystemService(Context.STORAGE_SERVICE); mExternalStoragePaths = storageManager.getVolumePaths(); // Start up the thread running the service. Note that we create a // separate thread because the service normally runs in the process's // main thread, which we don't want to block. Thread thr = new Thread(null, this, "MediaScannerService");

//新起线程,防止主线程阻塞,其启动run函数 thr.start(); }

public void run() { // reduce priority below other background threads to avoid interfering // with other services at boot time. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_LESS_FAVORABLE); Looper.prepare();// 把这个looper 对象设置到线程本地存储 //在单独的线程中启动了ServiceHandler,其用于处理将要扫描的消息,待会贴ServiceHandler代码 mServiceLooper = Looper.myLooper(); mServiceHandler = new ServiceHandler(); Looper.loop(); }

@Override public int onStartCommand(Intent intent, int flags, int startId) {

//由于是异步创建,所以得加上同步机制,等待创建完成 while (mServiceHandler == null) { synchronized (this) { try { wait(100); } catch (InterruptedException e) { } } } if (intent == null) { Log.e(TAG, "Intent is null in onStartCommand: ", new NullPointerException()); return Service.START_NOT_STICKY; } //获取MediaScannerReceiver发送的消息,比如:args.putString("volume", volume)或者args.putString("filepath", path); Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId;

msg.obj = intent.getExtras();

//发送mServiceHandler处理消息 mServiceHandler.sendMessage(msg); // Try again later if we are killed before we can finish scanning. return Service.START_REDELIVER_INTENT; }

private final class ServiceHandler extends Handler { @Override public void handleMessage(Message msg) { Bundle arguments = (Bundle) msg.obj; String filePath = arguments.getString("filepath"); try { if (filePath != null) {

//android binder机制,我也不能讲清楚,网上说binder的很多 IBinder binder = arguments.getIBinder("listener"); IMediaScannerListener listener = (binder == null ? null : IMediaScannerListener.Stub.asInterface(binder)); Uri uri = null; try {

//调用scanFile扫描单个文件 uri = scanFile(filePath, arguments.getString("mimetype")); } catch (Exception e) { Log.e(TAG, "Exception scanning file", e); } if (listener != null) {

//scanCompleted会发送扫描完毕信息,很多应用程序需要再扫描完毕时做些处理操作,比如刷新界面 listener.scanCompleted(filePath, uri); } } else { String volume = arguments.getString("volume"); String[] directories = null; if (MediaProvider.INTERNAL_VOLUME.equals(volume)) { // scan internal media storage directories = new String[] { Environment.getRootDirectory() + "/media", }; } else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) { // scan external storage volumes directories = mExternalStoragePaths; } if (directories != null) { if (false) Log.d(TAG, "start scanning volume " + volume + ": " + Arrays.toString(directories));

////调用scan扫描目录 scan(directories, volume); if (false) Log.d(TAG, "done scanning volume " + volume); } } } catch (Exception e) { Log.e(TAG, "Exception in handleMessage", e); } //扫描完毕,停止service,msg.arg1是onStartCommand传入的,msg.arg1 = startId stopSelf(msg.arg1); } };

//上述所调函数

private void scan(String[] directories, String volumeName) { Uri uri = Uri.parse("file://" + directories[0]); // don't sleep while scanning mWakeLock.acquire();//屏幕常亮 try { ContentValues values = new ContentValues(); values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName); Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values); //有的开发的应用程序需要知道何时开始扫描 sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri)); try { if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) { openDatabase(volumeName); } MediaScanner scanner = createMediaScanner();

//真正扫描的核心函数scanDirectories scanner.scanDirectories(directories, volumeName); } catch (Exception e) { Log.e(TAG, "exception in MediaScanner.scan()", e); } getContentResolver().delete(scanUri, null, null); } finally {

// 通知扫描完毕 sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri)); mWakeLock.release(); } }

//上面的是扫描目录的,如果是单个文件调用此函数,这个就简单很多,其核心函数也在MediaScanner.java中

private Uri scanFile(String path, String mimeType) { String volumeName = MediaProvider.EXTERNAL_VOLUME; openDatabase(volumeName); MediaScanner scanner = createMediaScanner(); try { // make sure the file path is in canonical form String canonicalPath = new File(path).getCanonicalPath(); return scanner.scanSingleFile(canonicalPath, volumeName, mimeType); } catch (Exception e) { Log.e(TAG, "bad path " + path + " in scanFile()", e); return null; } }


看完MediaScannerService.java,可以看到刚才的代码调到了关键的核心入口函数scanDirectories与scanSingleFile,打开:
android4.2/framework/base/media/java/android/media/MediaScanner.java
说这个函数前,先大概说明其情况,它是在java层对扫描的目录处理,其实际处理实现当然是由JNI调到native,但是我想说的是,其将目录转为扫描到某个文件时候,又从native到JNI再到java层,然后再从调用单个文件接口从java->jni->native,仔细想来,我个人觉得其目的是一种设计问题。不是非要这样处理的技术问题。是为了方便代码重用,否则如果java层想单独处理一个扫描文件,岂不是找不到接口?

下面看scanDirectories函数

public void scanDirectories(String[] directories, String volumeName) { try { long start = System.currentTimeMillis(); //初始化,从其代码可以看出是在初始化mediaProvider所需要的音频、视频、图像、缩略图、文件uri initialize(volumeName);

// FileCache ,用来缓存扫描文件的一些信息,例如 last_modified 等 prescan(null, true);

//// 扫描前的预处理 long prescan = System.currentTimeMillis(); if (ENABLE_BULK_INSERTS) { // create MediaInserter for bulk inserts mMediaInserter = new MediaInserter(mMediaProvider, 500); } for (int i = 0; i < directories.length; i++) {//核心函数入口,对每个需要扫描的目录进行此函数调用以做进一步分析 processDirectory(directories[i], mClient); } if (ENABLE_BULK_INSERTS) { // flush remaining inserts mMediaInserter.flushAll(); mMediaInserter = null; } long scan = System.currentTimeMillis(); //处理后续数据,比如清除video后清除其相对应的缩略图信息 postscan(directories); long end = System.currentTimeMillis(); if (false) { Log.d(TAG, " prescan time: " + (prescan - start) + "ms\n"); Log.d(TAG, " scan time: " + (scan - prescan) + "ms\n"); Log.d(TAG, "postscan time: " + (end - scan) + "ms\n"); Log.d(TAG, " total time: " + (end - start) + "ms\n"); } } catch (SQLException e) { // this might happen if the SD card is removed while the media scanner is running Log.e(TAG, "SQLException in MediaScanner.scan()", e); } catch (UnsupportedOperationException e) { // this might happen if the SD card is removed while the media scanner is running Log.e(TAG, "UnsupportedOperationException in MediaScanner.scan()", e); } catch (RemoteException e) { Log.e(TAG, "RemoteException in MediaScanner.scan()", e); } }

上述代码刚追踪到核心入口函数为:processDirectory,是对每一个目录进行处理,将其转为对processFile的处理,这是一个native函数。先看到jni层,打开:android\frameworks\base\media\jni\android_media_MediaScanner.cpp
在说这个文件前,我先说出我一开始看到这块以及后来的不明白的地方,因为这几个不明白的地方后来看网友说以及代码部分,发现是在这个文件中得以解决
1. 代码中 MediaScanner *mp = getNativeScanner_l(env, thiz); 当初只看到其get,但是不知道这个MediaScanner指针所指向的真实子类应该是哪个?因为你得去找真正函数代码实现。后来只知道函数实现代码在StageFrightMediaScanner中,但当时就没看到他俩之间关系,但在这个文件中有明文说明
2. 由于我没有认真看过JNI的一些语法,所以当时也不知道其从processDirectory调用到了底层,是如何又掉回到java层的,当然这个是语法问题,不是android问题。

jni中有个初始化的setup函数static voidandroid_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz){ ALOGV("native_setup");// 创建MediaScanner 对象, 注意此处的MediaScannaer对象是StagefrightMediaScanner。这是刚才我说到的那个实现问题//因为后来有很多调用mp->函数的地方,那么需要直接到StagefrightMediaScanner去找函数实现 MediaScanner *mp = new StagefrightMediaScanner; if (mp == NULL) { jniThrowException(env, kRunTimeException, "Out of memory"); return; } // 并且将这个MediaScanner对象设置到java 对象的mNativeContext 去保存,如果需要时直接get即可

env->SetIntField(thiz, fields.context, (int)mp);}


继续贴代码,jni中有这样MyMediaScannerClient 一个类,其实java层有个与其名字一模一样的类。

class MyMediaScannerClient : public MediaScannerClient{

//这个类会一直被作为参数带到底层去,因为底层做完由directory分析到file的操作后,会调用这个类中的scanFile函数,而这个类的scanFile函数会调用上面java层的scanFile,以至于最终是从java层的scanFile走入到最底层

//从构造函数中,可以看到其使用了java层传过来的MyMediaScannerClient类来构建此对象,这一块有些jni函数我也没查过,说下我知道的public: MyMediaScannerClient(JNIEnv *env, jobject client) : mEnv(env), mClient(env->NewGlobalRef(client)), mScanFileMethodID(0), mHandleStringTagMethodID(0), mSetMimeTypeMethodID(0) { ALOGV("MyMediaScannerClient constructor"); jclass mediaScannerClientInterface = env->FindClass(kClassMediaScannerClient); if (mediaScannerClientInterface == NULL) { ALOGE("Class %s not found", kClassMediaScannerClient); } else {

//我也没看过GetMethodID函数,但其长的很像java层的动态定义函数,所以也就是这个事了。这三个函数刚好是

//这一层的三个接口函数,都是能调回到上层的 mScanFileMethodID = env->GetMethodID( mediaScannerClientInterface, "scanFile", "(Ljava/lang/String;JJZZ)V"); mHandleStringTagMethodID = env->GetMethodID( mediaScannerClientInterface, "handleStringTag", "(Ljava/lang/String;Ljava/lang/String;)V"); mSetMimeTypeMethodID = env->GetMethodID( mediaScannerClientInterface, "setMimeType", "(Ljava/lang/String;)V"); } } virtual ~MyMediaScannerClient() { ALOGV("MyMediaScannerClient destructor"); mEnv->DeleteGlobalRef(mClient); } virtual status_t scanFile(const char* path, long long lastModified, long long fileSize, bool isDirectory, bool noMedia) { ALOGV("scanFile: path(%s), time(%lld), size(%lld) and isDir(%d)", path, lastModified, fileSize, isDirectory); jstring pathStr; if ((pathStr = mEnv->NewStringUTF(path)) == NULL) { mEnv->ExceptionClear(); return NO_MEMORY; } //当初不懂如何回到java层,就是不知道CallVoidMethod函数是干嘛的,查了下即明白它是入java层的接口。

//说到这,我觉得私下里是有必要继续去学习下常用的这几个jni的接口,至少对看代码有帮助,否则都联系不起来关系 mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize, isDirectory, noMedia);//最终又调回上层MediaScanner.java中的scanFile方法 mEnv->DeleteLocalRef(pathStr); return checkAndClearExceptionFromCallback(mEnv, "scanFile"); }

//handleStringTag这个函数是将获取的metadata信息键值对通过调用上层接口,传回给java层。这样java层以方便根据查询分析的

//metadata信息通过endFile函数写入android数据库中

virtual status_t handleStringTag(const char* name, const char* value) { ALOGV("handleStringTag: name(%s) and value(%s)", name, value); jstring nameStr, valueStr; if ((nameStr = mEnv->NewStringUTF(name)) == NULL) { mEnv->ExceptionClear(); return NO_MEMORY; } // Check if the value is valid UTF-8 string and replace // any un-printable characters with '?' when it's not. char *cleaned = NULL; if (utf8_length(value) == -1) { cleaned = strdup(value); char *chp = cleaned; char ch; while ((ch = *chp)) { if (ch & 0x80) { *chp = '?'; } chp++; } value = cleaned; } valueStr = mEnv->NewStringUTF(value); free(cleaned); if (valueStr == NULL) { mEnv->DeleteLocalRef(nameStr); mEnv->ExceptionClear(); return NO_MEMORY; }//此处就是讲键值对回传给java的接口 mEnv->CallVoidMethod( mClient, mHandleStringTagMethodID, nameStr, valueStr); mEnv->DeleteLocalRef(nameStr); mEnv->DeleteLocalRef(valueStr); return checkAndClearExceptionFromCallback(mEnv, "handleStringTag"); }

//继续说核心入口函数,刚才说到了processDirectory

static voidandroid_media_MediaScanner_processDirectory( JNIEnv *env, jobject thiz, jstring path, jobject client){ ALOGV("processDirectory");//获取MediaScanner,这个是setup函数中初始化过后的,现在只需要get即可 MediaScanner *mp = getNativeScanner_l(env, thiz);//参数判断,并抛出异常 if (mp == NULL) { jniThrowException(env, kRunTimeException, "No scanner available"); return; } if (path == NULL) { jniThrowException(env, kIllegalArgumentException, NULL); return; } const char *pathStr = env->GetStringUTFChars(path, NULL); if (pathStr == NULL) { // Out of memory return; }//初始化MyMediaScannerClient client实例,这个对象会一直被每个核心函数都带入到底层

MyMediaScannerClient myClient(env, client); //mp调用processDirectory,由于mp 是StageFrightMediaScanner,但其也是MediaScanner对象 MediaScanResult result = mp->processDirectory(pathStr, myClient); if (result == MEDIA_SCAN_RESULT_ERROR) { ALOGE("An error occurred while scanning directory '%s'.", pathStr); }//gc处理 env->ReleaseStringUTFChars(path, pathStr);}

//顺便提一下此处的另一个核心函数processFile,因为待会再次进来时,是调用那个函数的,代码到时候再贴

打开:android4.2/frameworks/av/media/libmedia/MediaScanner.cpp
找到processDirectory函数

MediaScanResult MediaScanner::processDirectory( const char *path, MediaScannerClient &client) { int pathLength = strlen(path); if (pathLength >= PATH_MAX) { return MEDIA_SCAN_RESULT_SKIPPED; } char* pathBuffer = (char *)malloc(PATH_MAX + 1); if (!pathBuffer) { return MEDIA_SCAN_RESULT_ERROR; } int pathRemaining = PATH_MAX - pathLength; strcpy(pathBuffer, path); if (pathLength > 0 && pathBuffer[pathLength - 1] != '/') { pathBuffer[pathLength] = '/'; pathBuffer[pathLength + 1] = 0; --pathRemaining; } client.setLocale(locale());//核心入口函数 MediaScanResult result = doProcessDirectory(pathBuffer, pathRemaining, client, false);//释放内存 free(pathBuffer); return result;}

//继续打开入口函数:doProcessDirectory

MediaScanResult MediaScanner::doProcessDirectory( char *path, int pathRemaining, MediaScannerClient &client, bool noMedia) { // place to copy file or directory name char* fileSpot = path + strlen(path); struct dirent* entry; if (shouldSkipDirectory(path)) { ALOGD("Skipping: %s", path); return MEDIA_SCAN_RESULT_OK; } // Treat all files as non-media in directories that contain a ".nomedia" file if (pathRemaining >= 8 /* strlen(".nomedia") */ ) { strcpy(fileSpot, ".nomedia"); if (access(path, F_OK) == 0) { ALOGV("found .nomedia, setting noMedia flag"); noMedia = true; } // restore path fileSpot[0] = 0; } DIR* dir = opendir(path);//c语言函数,用于打开文件夹 dir*结构体存取文件名等 if (!dir) { ALOGW("Error opening directory '%s', skipping: %s.", path, strerror(errno)); return MEDIA_SCAN_RESULT_SKIPPED; } MediaScanResult result = MEDIA_SCAN_RESULT_OK; while ((entry = readdir(dir))) {//读取目录中的每个文件 if (doProcessDirectoryEntry(path, pathRemaining, client, noMedia, entry, fileSpot) == MEDIA_SCAN_RESULT_ERROR) {//此处相对于之前的版本有所改变,最开始是对此处是文件夹还是文件分开判断 result = MEDIA_SCAN_RESULT_ERROR;

//如果是文件夹,递归调用doProcessDirectory;

//否则调用doProcessDirectoryEntry//如果是扩展名符合的文件,调用client的scanFile方法

break; } } closedir(dir);//关闭打开的文件夹 return result;}

MediaScanResult MediaScanner::doProcessDirectoryEntry( char *path, int pathRemaining, MediaScannerClient &client, bool noMedia, struct dirent* entry, char* fileSpot) { struct stat statbuf; const char* name = entry->d_name;//文件名或者是文件夹名 // ignore "." and ".."//对于文件名开头为. 或者 .. 的文件夹忽略处理 if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) { return MEDIA_SCAN_RESULT_SKIPPED; } int nameLength = strlen(name); if (nameLength + 1 > pathRemaining) { // path too long! return MEDIA_SCAN_RESULT_SKIPPED; } strcpy(fileSpot, name); int type = entry->d_type; if (type == DT_UNKNOWN) { // If the type is unknown, stat() the file instead. // This is sometimes necessary when accessing NFS mounted filesystems, but // could be needed in other cases well. if (stat(path, &statbuf) == 0) { if (S_ISREG(statbuf.st_mode)) { type = DT_REG;//普通文件 } else if (S_ISDIR(statbuf.st_mode)) { type = DT_DIR;//普通目录 } } else { ALOGD("stat() failed for %s: %s", path, strerror(errno) ); } } if (type == DT_DIR) { bool childNoMedia = noMedia; // set noMedia flag on directories with a name that starts with '.' // for example, the Mac ".Trashes" directory if (name[0] == '.') childNoMedia = true; // report the directory to the client if (stat(path, &statbuf) == 0) {

//如果是扩展名符合的文件,调用client的scanFile方法 status_t status = client.scanFile(path, statbuf.st_mtime, 0, true /*isDirectory*/, childNoMedia);//告知client是目录 if (status) { return MEDIA_SCAN_RESULT_ERROR; } } // and now process its contents strcat(fileSpot, "/"); MediaScanResult result = doProcessDirectory(path, pathRemaining - nameLength - 1, client, childNoMedia);//是目录,就回调处理 if (result == MEDIA_SCAN_RESULT_ERROR) { return MEDIA_SCAN_RESULT_ERROR; } } else if (type == DT_REG) { stat(path, &statbuf); status_t status = client.scanFile(path, statbuf.st_mtime, statbuf.st_size false /*isDirectory*/, noMedia);//如果是文件,则调用client.scanFile扫描,注意,此处调用的是android_media_MediaSccanner.cpp中的MyMediaScannerClient中scanFile方法,它巍峨虚函数 if (status) { return MEDIA_SCAN_RESULT_ERROR; } } return MEDIA_SCAN_RESULT_OK;}

到此处,我们就已经看完了整个从java层将目录directories,一步步转变成了调用client.scanFile,刚说过,这是jni的接口,其最终调用的是上层MediaScanner.java文件中的scanFile函数。
下面再接着看如何去处理scanFile的,回到MediaScanner.java文件中,找到scanFile函数

@Override public void scanFile(String path, long lastModified, long fileSize, boolean isDirectory, boolean noMedia) {//从jni层钓上来的 // This is the callback funtion from native codes. // Log.v(TAG, "scanFile: "+path);

//其调用doScanFile函数,再打开这个函数 doScanFile(path, null, lastModified, fileSize, isDirectory, false, noMedia); }

public Uri doScanFile(String path, String mimeType, long lastModified, long fileSize, boolean isDirectory, boolean scanAlways, boolean noMedia) { Uri result = null;// long t1 = System.currentTimeMillis(); try { FileEntry entry = beginFile(path, mimeType, lastModified, fileSize, isDirectory, noMedia); // if this file was just inserted via mtp, set the rowid to zero // (even though it already exists in the database), to trigger // the correct code path for updating its entry if (mMtpObjectHandle != 0) { entry.mRowId = 0; } // rescan for metadata if file was modified since last scan if (entry != null && (entry.mLastModifiedChanged || scanAlways)) { if (noMedia) { result = endFile(entry, false, false, false, false, false); } else { String lowpath = path.toLowerCase(); //判断该媒体路径是属于闹钟、铃声还是通知等等,通过路径是否包含闹钟等铃声文件路径来判断的 boolean ringtones = (lowpath.indexOf(RINGTONES_DIR) > 0); boolean notifications = (lowpath.indexOf(NOTIFICATIONS_DIR) > 0); boolean alarms = (lowpath.indexOf(ALARMS_DIR) > 0); boolean podcasts = (lowpath.indexOf(PODCAST_DIR) > 0); boolean music = (lowpath.indexOf(MUSIC_DIR) > 0) || (!ringtones && !notifications && !alarms && !podcasts); boolean isaudio = MediaFile.isAudioFileType(mFileType); boolean isvideo = MediaFile.isVideoFileType(mFileType); boolean isimage = MediaFile.isImageFileType(mFileType); if (isaudio || isvideo || isimage) { if (mExternalIsEmulated && path.startsWith(mExternalStoragePath)) { // try to rewrite the path to bypass the sd card fuse layer String directPath = Environment.getMediaStorageDirectory() + path.substring(mExternalStoragePath.length()); File f = new File(directPath); if (f.exists()) { path = directPath; } } } // we only extract metadata for audio and video files if (isaudio || isvideo) {

//是音视频文件,核心函数入口,不管下面的图片,我们看processFile,这在刚才说到的

//android_media_MediaScanner.cpp 这个jni层文件中 processFile(path, mimeType, this); } if (isimage) {

//是图片文件,核心函数入口 processImageFile(path); } //通过endFile将媒体信息写进数据库中 result = endFile(entry, ringtones, notifications, alarms, music, podcasts); } } } catch (RemoteException e) { Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e); }// long t2 = System.currentTimeMillis();// Log.v(TAG, "scanFile: " + path + " took " + (t2-t1)); return result; }

打开android_media_MediaScanner.cpp,并找到prcessFile,

static voidandroid_media_MediaScanner_processFile( JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client){ ALOGV("processFile"); // Lock already hold by processDirectory MediaScanner *mp = getNativeScanner_l(env, thiz); if (mp == NULL) { jniThrowException(env, kRunTimeException, "No scanner available"); return; } if (path == NULL) { jniThrowException(env, kIllegalArgumentException, NULL); return; } const char *pathStr = env->GetStringUTFChars(path, NULL); if (pathStr == NULL) { // Out of memory return; } const char *mimeTypeStr = (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL); if (mimeType && mimeTypeStr == NULL) { // Out of memory // ReleaseStringUTFChars can be called with an exception pending. env->ReleaseStringUTFChars(path, pathStr); return; } MyMediaScannerClient myClient(env, client); //调用StageFrightMediaScanner的processFile MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient); if (result == MEDIA_SCAN_RESULT_ERROR) { ALOGE("An error occurred while scanning file '%s'.", pathStr); } env->ReleaseStringUTFChars(path, pathStr); if (mimeType) { env->ReleaseStringUTFChars(mimeType, mimeTypeStr); }}


打开:
android4.2/frameworks/av/media/libstagefright/StageFrightMediaScanner.cpp
先看到processFile函数,第一个参数为path,第二个参数应该是需要知道的多媒体类型,他是一个指针,所以此刻还是待求的。其最后一个参数就是刚才的client,方便随时调用jni处的函数接口以返回java层

MediaScanResult StagefrightMediaScanner::processFile( const char *path, const char *mimeType, MediaScannerClient &client) { ALOGV("processFile '%s'.", path); client.setLocale(locale()); client.beginFile();

//其调用核心入口函数:processFileInternal MediaScanResult result = processFileInternal(path, mimeType, client); client.endFile(); return result;}

MediaScanResult StagefrightMediaScanner::processFileInternal( const char *path, const char *mimeType, MediaScannerClient &client) { const char *extension = strrchr(path, '.'); if (!extension) { return MEDIA_SCAN_RESULT_SKIPPED; } if (!FileHasAcceptableExtension(extension)) { return MEDIA_SCAN_RESULT_SKIPPED; }//color:red;mso-ansi-language:EN-US;mso-fareast-language:ZH-CN;mso-bidi-language:AR-SA;" >比较如果是这些格式,则调用mso-fareast-font-family:宋体;mso-fareast-theme-font:minor-fareast;mso-hansi-theme-font:minor-latin;mso-bidi-font-family:"Times New Roman";mso-bidi-theme-font:minor-bidi;color:red;mso-ansi-language:EN-US;mso-fareast-language:ZH-CN;mso-bidi-language:AR-SA;" >HandleMIDIcolor:red;mso-ansi-language:EN-US;mso-fareast-language:ZH-CN;mso-bidi-language:AR-SA;" >函数进行处理 if (!strcasecmp(extension, ".mid") || !strcasecmp(extension, ".smf") || !strcasecmp(extension, ".imy") || !strcasecmp(extension, ".midi") || !strcasecmp(extension, ".xmf") || !strcasecmp(extension, ".rtttl") || !strcasecmp(extension, ".rtx") || !strcasecmp(extension, ".ota") || !strcasecmp(extension, ".mxmf")) { return HandleMIDI(path, &client); } //此处定义了mRetriever的来源

sp<MediaMetadataRetriever> mRetriever(new MediaMetadataRetriever); int fd = open(path, O_RDONLY | O_LARGEFILE); status_t status; if (fd < 0) { // couldn't open it locally, maybe the media server can? status = mRetriever->setDataSource(path); } else { status = mRetriever->setDataSource(fd, 0, 0x7ffffffffffffffL); close(fd); } if (status) { return MEDIA_SCAN_RESULT_ERROR; } //核心入口函数:extractMetadata,通过这个函数去提取多媒体信息,比如歌手,歌曲名,专辑等等 const char *value; if ((value = mRetriever->extractMetadata( METADATA_KEY_MIMETYPE)) != NULL) {

//将获得的何种多媒体信息传回上层并赋值保存,上层写入db时,会将其写入 status = client.setMimeType(value); if (status) { return MEDIA_SCAN_RESULT_ERROR; } } struct KeyMap { const char *tag; int key; };

//这个键值对map就是metadata中可以获取的信息 static const KeyMap kKeyMap[] = { { "tracknumber", METADATA_KEY_CD_TRACK_NUMBER }, { "discnumber", METADATA_KEY_DISC_NUMBER }, { "album", METADATA_KEY_ALBUM }, { "artist", METADATA_KEY_ARTIST }, { "albumartist", METADATA_KEY_ALBUMARTIST }, { "composer", METADATA_KEY_COMPOSER }, { "genre", METADATA_KEY_GENRE }, { "title", METADATA_KEY_TITLE }, { "year", METADATA_KEY_YEAR }, { "duration", METADATA_KEY_DURATION }, { "writer", METADATA_KEY_WRITER }, { "compilation", METADATA_KEY_COMPILATION }, { "isdrm", METADATA_KEY_IS_DRM }, { "width", METADATA_KEY_VIDEO_WIDTH }, { "height", METADATA_KEY_VIDEO_HEIGHT }, }; static const size_t kNumEntries = sizeof(kKeyMap) / sizeof(kKeyMap[0]); for (size_t i = 0; i < kNumEntries; ++i) { const char *value; if ((value = mRetriever->extractMetadata(kKeyMap[i].key)) != NULL) { status = client.addStringTag(kKeyMap[i].tag, value); if (status != OK) { return MEDIA_SCAN_RESULT_ERROR; } } } return MEDIA_SCAN_RESULT_OK;}


大概过程就这样了,有说的不对的,还请指正。
0 0
原创粉丝点击