媒体扫描分析文档

dragon_fly 贡献于2015-06-08

作者 刘俊荣  创建于2013-03-21 08:27:00   修改者刘俊荣  修改于2013-03-22 06:09:00字数17488

文档摘要: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).
关键词:

媒体扫描分析 1.代码位置: alps\packages\providers\MediaProvider 切入点。 2.简介. 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特殊的文件。然后它会查看被扫描的文件是否已经存在于缓存条目中,如果存在,它会检查文件上次修改的时间是否改变。最后它返回该文件是否需要进一步处理的结果。如果不需要,接下来的两步不会执行。 b)C++层扫描文件 不是所有的文件都需要交给C++层解析成元数据。只有下面的文件类型会被解析,注意,这里不处理image文件。 1. if (mFileType == MediaFile.FILE_TYPE_MP3 || 2. mFileType == MediaFile.FILE_TYPE_MP4 || 3. mFileType == MediaFile.FILE_TYPE_M4A || 4. mFileType == MediaFile.FILE_TYPE_3GPP || 5. mFileType == MediaFile.FILE_TYPE_3GPP2 || 6. mFileType == MediaFile.FILE_TYPE_OGG || 7. mFileType == MediaFile.FILE_TYPE_MID || 8. mFileType == MediaFile.FILE_TYPE_WMA) { 9. 10. 11. …… 12. 13. 14. 15. } 复制代码 对于被解析的元数据信息,C++层会回调到JAVA层的handleStringTag。Java层会记录它的name/value信息。 c)Java层结束文件 最后根据上一步解析出的值, Java层会更新相应的MeidaProvider产生的数据库表。 5.Java层发送扫描 到目前为止,所有文件已经被扫描,它最后会检查文件和播放列表缓存条目,看是否所有项仍然存在于文件系统。如果有空条目,则会从数据库中删除。这样它能够保持数据库和文件系统的一致性。 其他的应用程序通过接收MediaScannerService发出的ACTION_MEDIA_SCANNER_STARTED 和ACTION_MEDIA_SCANNER_FINISHED意图能够知道什么时候扫描操作开始和结束。 MediaScanner分析(来自网络) 一 MediaScannerService 多媒体扫描是从MediaScannerService开始的。这是一个单独的package。位于 packages/providers/MediaProvider:含以下java文件 l         MediaProvider.java l         MediaScannerReceiver.java l         MediaScannerService.java l         MediaThumbRequest.java 分析这个目录的Android.mk文件,发现它运行的进程名字就是android.process.media。 application android:process=android.process.media 1.1    MediaScannerReceiver 这个类从BroadcastReceiver中派生,用来接收任务的。 MediaScannerReceiver extends BroadcastReceiver 在它重载的onRecieve函数内有以下几种走向: if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {             // 收到”启动完毕“广播后,扫描内部存储             scan(context, MediaProvider.INTERNAL_VOLUME);         } else {             ……….                 if (action.equals(Intent.ACTION_MEDIA_MOUNTED) &&                         externalStoragePath.equals(path)) {                /收到MOUNT信息后,扫描外部存储                     scan(context, MediaProvider.EXTERNAL_VOLUME);                 }  else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) &&                         path != null && path.startsWith(externalStoragePath + "/")) {                //收到请求要求扫描某个文件,注意不会扫描内部存储上的文件                     scanFile(context, path);         …………………………..         } ……下面是它调用的scan函数: scan(Context context, String volume) Bundle args = new Bundle();         args.putString("volume", volume); //直接启动MediaScannerService了,         context.startService(                 new Intent(context, MediaScannerService.class).putExtras(args));   总结: MediaScannerReceiver是用来接收任务的,它收到广播后,会启动MediaService进行扫描工作。 下面看看MediaScannerService. 1.2    MediaScannerService MSS标准的从Service中派生下来, MediaScannerService extends Service implements Runnable //注意:是一个Runnable…,可能有线程之类的东西存在 下面从Service的生命周期的角度来看看它的工作。 1. onCreate public void onCreate()        PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);       //获得电源锁,防止在扫描过程中休眠       //单独搞一个线程去跑扫描工作,防止ANR      Thread thr = new Thread(null, this, "MediaScannerService");         thr.start(); 2. onStartCommand @Override     public int onStartCommand(Intent intent, int flags, int startId)     {       //注意这个handler,是在另外一个线程中创建的,往这个handler里sendMessage      //都会在那个线程里边处理   //不明白的可以去查看handler和Looper机制 //这里就是同步机制,等待mServiceHandler在另外那个线程创建完毕 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;         }           Message msg = mServiceHandler.obtainMessage();         msg.arg1 = startId;         msg.obj = intent.getExtras(); //把MediaScannerReceiver发出的消息传递到另外那个线程去处理。         mServiceHandler.sendMessage(msg);       …………. 基本上MSR(MediaScannerReceiver)发出的请求都会传到onStartCommand中处理。如果有多个存储的话,也只能一个一个扫描了。 下面看看那个线程的主函数 3. run 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和handler的实现         Looper.prepare();//把这个looper对象设置到线程本地存储           mServiceLooper = Looper.myLooper();         mServiceHandler = new ServiceHandler();//创建handler,默认会把这个looper //的消息队列赋值给handler的消息队列,这样往handler中发送消息就是往这个线程的looper发           Looper.loop();//消息循环,内部会处理消息队列中的消息 //也就是handleMessage函数 } 上面handler中加入了一个扫描请求(假设是外部存储的),所以要分析handleMessage函数。 4. handleMessage private final class ServiceHandler extends Handler     {         @Override         public void handleMessage(Message msg)         {             Bundle arguments = (Bundle) msg.obj;             String filePath = arguments.getString("filepath");                         try { ……… 这里不讲了                 } 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)) {                       //是扫描外部存储的请求?获取外部存储的路径                         directories = new String[] {                                 Environment.getExternalStorageDirectory().getPath(),                                 };                     }                     if (directories != null) { //真正的扫描开始了,上面只不过是把存储路径取出来罢了.                         scan(directories, volume);                          ….. //扫描完了,就把service停止了             stopSelf(msg.arg1);         } }; 5. scan函数 private void scan(String[] directories, String volumeName) {        mWakeLock.acquire(); //下面这三句话很深奥… //从 getContentResolver获得一个ContentResover,然后直接插入 //根据AIDL,这个ContentResover的另一端是MediaProvider。只要去看看它的 //insert函数就可以了 //反正这里知道获得了一个扫描URI即可。   ContentValues values = new ContentValues();    values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);    Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values);           Uri uri = Uri.parse("file://" + directories[0]); //发送广播,通知扫描开始了         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); //通知扫描完毕         sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));         mWakeLock.release(); } 说说上面那个深奥的地方,在MediaProvider中重载了insert函数,insert函数会调用insertInternal函数。 如下: private Uri insertInternal(Uri uri, ContentValues initialValues) {         long rowId;         int match = URI_MATCHER.match(uri);         // handle MEDIA_SCANNER before calling getDatabaseForUri() //刚才那个insert只会走下面这个分支,其实就是获得一个地址…. //太绕了!!!!!         if (match == MEDIA_SCANNER) {             mMediaScannerVolume = initialValues.getAsString(MediaStore.MEDIA_SCANNER_VOLUME);             return MediaStore.getMediaScannerUri();         } …….. 再看看它创建了什么样的Scanner,这就是MSS中的createMediaScanner private MediaScanner createMediaScanner() { //下面这个MediaScanner在framework/base/中,待会再分析         MediaScanner scanner = new MediaScanner(this); //设置当前的区域,这个和字符编码有重大关系。         Locale locale = getResources().getConfiguration().locale;         if (locale != null) {             String language = locale.getLanguage();             String country = locale.getCountry();             String localeString = null;             if (language != null) {                 if (country != null) { //给扫描器设置当前国家和语言。                     scanner.setLocale(language + "_" + country);                 } else {                     scanner.setLocale(language);                 }             }            }         return scanner; } 至此,MSS的任务完成了。接下来是MediaScanner的工作了。 6. 总结 MSS的工作流程如下: l         1 单独启动一个带消息循环的工作线程。 l         2 主线程接收系统发来的任务,然后发送给工作线程去处理。 l         3 工作线程接收任务,创建一个MediaScanner去扫描。 l         4 MSS顺带广播一下扫描工作启动了,扫描工作完毕了。   二 MediaScanner MediaScanner位置在 frameworks/base/media/下,包括jni和java文件。 先看看java实现。 这个类巨复杂,而且和MediaProvider交互频繁。在分析的时候要时刻回到MediaProvider去看看。 1.       初始化 public class MediaScanner { static { //libmedia_jni.so的加载是在MediaScanner类中完成的 //这么重要的so为何放在如此不起眼的地方加载???         System.loadLibrary("media_jni");         native_init(); } public MediaScanner(Context c) {         native_setup();//调用jni层的初始化,暂时不用看了,无非就是一些 //初始化工作,待会在再进去看看 ……..     } 刚才MSS中是调用scanDirectories函数,我们看看这个。 2.       scanDirectories 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++) { //扫描文件夹,这里有一个很重要的参数 mClient // processDirectory是一个native函数                 processDirectory(directories[i], MediaFile.sFileExtensions, mClient);             }             long scan = System.currentTimeMillis();             postscan(directories);//扫描后处理             long end = System.currentTimeMillis();            …..打印时间,异常处理…没了… 下面简单讲讲initialize ,preScan和postScan都干嘛了。 private void initialize(String volumeName) { //打开MediaProvider,获得它的一个实例         mMediaProvider = mContext.getContentResolver().acquireProvider("media"); //得到一些uri         mAudioUri = Audio.Media.getContentUri(volumeName);         mVideoUri = Video.Media.getContentUri(volumeName);         mImagesUri = Images.Media.getContentUri(volumeName);         mThumbsUri = Images.Thumbnails.getContentUri(volumeName); //外部存储的话,可以支持播放列表之类的东西,搞了一些个缓存池之类的 //如mGenreCache等         if (!volumeName.equals("internal")) {             // we only support playlists on external media             mProcessPlaylists = true;            mGenreCache = new HashMap();          … preScan,这个函数很复杂: 大概就是创建一个FileCache,用来缓存扫描文件的一些信息,例如last_modified等。这个FileCache是从MediaProvider中已有信息构建出来的,也就是历史信息。后面根据扫描得到的新信息来对应更新历史信息。 postScan,这个函数做一些清除工作,例如以前有video生成了一些缩略图,现在video文件被干掉了,则对应的缩略图也要被干掉。 另外还有一个mClient,这个是从MediaScannerClient派生下来的一个东西,里边保存了一个文件的一些信息。后续再分析。   刚才说到,具体扫描工作是在processDirectory函数中完成的。这个是一个native函数。 在frameworks/base/media/jni/android_media_MediaScanner.cpp中。   三  MediaScanner JNI层分析 MediaScanner JNI层内容比较多,单独搞一节分析吧。 先看看android_media_MediaScanner这个文件。 1. native_init函数,jni对应的函数如下 static void android_media_MediaScanner_native_init(JNIEnv *env) {      jclass clazz; clazz = env->FindClass("android/media/MediaScanner"); //得都JAVA类中mNativeContext这个成员id     fields.context = env->GetFieldID(clazz, "mNativeContext", "I");    //不熟悉JNI的自己去学习下吧 } 3.       native_setup函数,jni对应函数如下: android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz) { //创建MediaScanner对象     MediaScanner *mp = createMediaScanner(); //太变态了,自己不保存这个对象指针. //却把它设置到java对象的mNativeContext去保存     env->SetIntField(thiz, fields.context, (int)mp); } //创建MediaScanner函数 static MediaScanner *createMediaScanner() { #if BUILD_WITH_FULL_STAGEFRIGHT    .. //使用google自己的   return new StagefrightMediaScanner;    #endif #ifndef NO_OPENCORE //使用opencore提供的 ….     return new PVMediaScanner(); #endif   4.       processDirectories函数,jni对应如下: android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, jobject client) {     MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context); //每次都要回调到JAVA中去取这个Scanner!!  ………     const char *pathStr = env->GetStringUTFChars(path, NULL);     const char *extensionsStr = env->GetStringUTFChars(extensions, NULL); ……. //又在C++这里搞一个client,然后把java的client放到C++Client中去保存 //而且还是栈上的临时变量.. MyMediaScannerClient myClient(env, client); //scanner扫描文件夹,用得是C++的client     mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env);     env->ReleaseStringUTFChars(path, pathStr);     env->ReleaseStringUTFChars(extensions, extensionsStr); } 到这里似乎就没有了,那么扫描后的数据库是怎么更新的呢?为什么要传入一个client进去呢?看来必须得trace到scanner中去才知道了。   四 PVMediaScanner 这个在external/opencore/android/mediascanner.cpp中。 1.       processDirectory status_t MediaScanner::processDirectory(const char *path, const char* extensions,         MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv) {     InitializeForThread();     int error = 0; status_t result = PVMFSuccess; …. //调用client的设置区域函数    client.setLocale(mLocale); //扫描文件夹,咋还没开始??    result = doProcessDirectory(pathBuffer, pathRemaining, extensions, client, exceptionCheck, exceptionEnv); .. 2.       doProcessDirectory status_t MediaScanner::doProcessDirectory(char *path, int pathRemaining, const char* extensions,         MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv) { …终于看到点希望了 //打开这个文件夹,枚举其中的内容。 //题外话,这个时候FileManager肯定删不掉这个文件夹!!     DIR* dir = opendir(path);     while ((entry = readdir(dir))) {         const char* name = entry->d_name; //不处理.和..文件夹  if (isDirectory) { …….. //不处理.开头的文件夹。如果是文件夹,递归调用doProcessDirectory //深度优先啊!             int err = doProcessDirectory(path, pathRemaining - nameLength - 1, extensions, client, exceptionCheck, exceptionEnv);                 if (err) {                     LOGE("Error processing '%s' - skipping/n", path);                     continue;                 }             } else if (fileMatchesExtension(path, extensions)) { //是一个可以处理的文件,交给client处理 //彻底疯掉了….这是干嘛呢???               client.scanFile(path, statbuf.st_mtime, statbuf.st_size); 这里要解释下,刚才createMediaScanner中,明明创建的是PVMediaScanner,为何这里看得是MediaScanner代码呢? l         因为PVMediaScanner从MediaScanner中派生下来的,而且没有重载processDirectory函数 l         Eclaire没有PVMediaScanner这样的东西,估计是froyo又改了点啥吧。   FT,processDirctory无非是列举一个目录内容,然后又反回去调用client的scanFile处理了。为何搞这么复杂?只有回去看看C++的client干什么了。   3.       MediaScannerClient---JNI层 JNI中的这个类是这样的: class MyMediaScannerClient : public MediaScannerClient //这是它的scanFile实现 virtual bool scanFile(const char* path, long long lastModified, long long fileSize)     { //再次崩溃了,C++的client调用了刚才传进去的java Client的 //scanFile函数…不过这次还传进去了该文件的路径,最后修改时间和文件大小。        mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize); …..想死,,,     } 没办法了,只能再去看看MediaScanner.java传进去的那个client了。 4.       MediaScannerClient----JAVA层 这个类在MediaScanner.java中实现。 private class MyMediaScannerClient implements MediaScannerClient: public void scanFile(String path, long lastModified, long fileSize) { //调用doScanFile..很烦..            doScanFile(path, null, lastModified, fileSize, false); … //下面是doScanFile public Uri doScanFile(String path, String mimeType, long lastModified, long fileSize, boolean scanAlways) { //预处理,看看之前创建的文件缓存中有没有这个文件      FileCacheEntry entry = beginFile(path, mimeType, lastModified, fileSize);                 // rescan for metadata if file was modified since last scan                 if (entry != null && (entry.mLastModifiedChanged || scanAlways)) { //如果事先有这个文件的信息,则需要修改一些信息,如长度,最后修改时间等        ….. //真正的扫描文件                         processFile(path, mimeType, this); //扫描完了,做最后处理                     endFile(entry, ringtones, notifications, alarms, music, podcasts); processFile又是jni层的。 对应android_media_MediaScanner_processFile函数 android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client) {     MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context);   //无语了,又搞一个  MyMediaScannerClient MyMediaScannerClient myClient(env, client);     mp->processFile(pathStr, mimeTypeStr, myClient); …} 第一次到PVMediaScanner中来了 status_t PVMediaScanner::processFile(const char *path, const char* mimeType, MediaScannerClient& client) {     status_t result;     InitializeForThread(); //调用client的beginFile,估计是做一些啥预处理     client.setLocale(locale());     client.beginFile();         //LOGD("processFile %s mimeType: %s/n", path, mimeType);     const char* extension = strrchr(path, '.');       if (extension && strcasecmp(extension, ".mp3") == 0) {         result = parseMP3(path, client);//client又传进去了 …根据后缀名去扫描…. } client.endFile(); 到parseMP3去看看。这个在 static PVMFStatus parseMP3(const char *filename, MediaScannerClient& client) {//这个函数太专业了,和编解码有关,我们重点关注client在这里干什么了 … //原来,解析器从文件中解析出一个信息,就调用client的addStringTag if (!client.addStringTag("duration", buffer)) …. addStringTag在JNI的client中处理。 这个MediaScannerClient是在opencore中的那个MediaScanner.cpp中实现的,而android_media_MediaScanner.cpp中的是MyMediaScannerClient,从MediaScannerClient派生下来的     bool MediaScannerClient::addStringTag(const char* name, const char* value) {     if (mLocaleEncoding != kEncodingNone) {         //字符串编码之类的转换。不详述了         bool nonAscii = false;         const char* chp = value;         char ch;         while ((ch = *chp++)) {             if (ch & 0x80) {                 nonAscii = true;                 break;             }         } //如果不是ASCII编码的话,内部先保存一下这些个tag信息 //待会扫描完后再集中做一次字符串编码转换         if (nonAscii) {             // save the strings for later so they can be used for native encoding detection             mNames->push_back(name);             mValues->push_back(value);             return true;         }         // else fall through     } //调用子类的handleStringTag     return handleStringTag(name, value); } class MyMediaScannerClient : public MediaScannerClient{ //调用到子类的handleStringTag了 virtual bool handleStringTag(const char* name, const char* value) { //又传递到JAVA层的handleStringTag来处理 //麻木了..     mEnv->CallVoidMethod(mClient, mHandleStringTagMethodID, nameStr, valueStr);        } JAVA层 MediaScannerService中的MyMediaScannerClient类 public void handleStringTag(String name, String value) { //下层扫描的文件tag信息,全部处理后赋值给java层这个MyScannerClient了 例如MP3的title,专辑名等等。                ….               int num = parseSubstring(value, 0, 0);                 mTrack = (num * 1000) + (mTrack % 1000);             } else if (name.equalsIgnoreCase("duration")) {                 mDuration = parseSubstring(value, 0, 0);             } else if (name.equalsIgnoreCase("writer") || name.startsWith("writer;")) {                 mWriter = value.trim(); 到这里了,还没写到数据库呢?啥时候更新数据库?看来是在client.endFile()中了。 但是这个endClient并没有调用到JAVA层去。那在哪里结束呢? 还记得JAVA中的doScanFile函数吗,对了,这个endFile就是在那里直接由JAVA调用的。 private Uri endFile(FileCacheEntry entry, boolean ringtones, boolean notifications,                 boolean alarms, boolean music, boolean podcasts)                 throws RemoteException {             // update database             Uri tableUri;             boolean isAudio = MediaFile.isAudioFileType(mFileType);             boolean isVideo = MediaFile.isVideoFileType(mFileType);             boolean isImage = MediaFile.isImageFileType(mFileType);           …. //来了一个新文件,直接插入数据库                 result = mMediaProvider.insert(tableUri, values); //或者更新数据库 mMediaProvider.update(result, values, null, null); 这回真算是完了。 5.流程总结 l         MediaScanner(MS)调用scanDirectories中的processDirectory,进入到JNI层 l         JNI调用PVMediaScanner的processDirectory l         PVMediaScanner的processDirectory为目录下的文件调用MyMediaScannerClient的scanFile l         MyMediaScannerClient  

下载文档到电脑,查找使用更方便

文档的实际排版效果,会与网站的显示效果略有不同!!

需要 5 金币 [ 分享文档获得金币 ] 0 人已下载

下载文档