Android开发之媒体扫描详细解析(上)

Android开发之媒体扫描详细解析(上)

    <div class="article_manage clearfix">
    <div class="article_l">
        <span class="link_categories">
        标签:
          <a href="http://www.csdn.net/tag/%e5%9b%be%e7%89%87" target="_blank" onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">图片</a><a href="http://www.csdn.net/tag/MediaScanner" target="_blank" onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">MediaScanner</a><a href="http://www.csdn.net/tag/MediaProvider" target="_blank" onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">MediaProvider</a><a href="http://www.csdn.net/tag/android%e5%bc%80%e5%8f%91" target="_blank" onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">android开发</a><a href="http://www.csdn.net/tag/android" target="_blank" onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_tag']);">android</a>
        </span>
    </div>
    <div class="article_r">
        <span class="link_postdate">2016-08-06 00:09</span>
        <span class="link_view" title="阅读次数">367人阅读</span>
        <span class="link_comments" title="评论次数"> <a href="#comments" onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_pinglun'])">评论</a>(0)</span>
        <span class="link_collect tracking-ad" data-mod="popu_171"> <a href="javascript:void(0);" onclick="javascript:collectArticle('Android开发之媒体扫描详细解析(上)','52133599');return false;" title="收藏">收藏</a></span>
         <span class="link_report"> <a href="#report" onclick="javascript:report(52133599,2);return false;" title="举报">举报</a></span>

    </div>
</div>
<div class="embody" style="display:none" id="embody">
    <span class="embody_t">本文章已收录于:</span>
    <div class="embody_c" id="lib" value="{&quot;err&quot;:0,&quot;msg&quot;:&quot;ok&quot;,&quot;data&quot;:[]}"></div>
</div>
<style type="text/css">        
        .embody{
            padding:10px 10px 10px;
            margin:0 -20px;
            border-bottom:solid 1px #ededed;                
        }
        .embody_b{
            margin:0 ;
            padding:10px 0;
        }
        .embody .embody_t,.embody .embody_c{
            display: inline-block;
            margin-right:10px;
        }
        .embody_t{
            font-size: 12px;
            color:#999;
        }
        .embody_c{
            font-size: 12px;
        }
        .embody_c img,.embody_c em{
            display: inline-block;
            vertical-align: middle;               
        }
         .embody_c img{               
            width:30px;
            height:30px;
        }
        .embody_c em{
            margin: 0 20px 0 10px;
            color:#333;
            font-style: normal;
        }
</style>
<script type="text/javascript">
    $(function () {
        try
        {
            var lib = eval("("+$("#lib").attr("value")+")");
            var html = "";
            if (lib.err == 0) {
                $.each(lib.data, function (i) {
                    var obj = lib.data[i];
                    //html += '<img src="' + obj.logo + '"/>' + obj.name + "&nbsp;&nbsp;";
                    html += ' <a href="' + obj.url + '" target="_blank">';
                    html += ' <img src="' + obj.logo + '">';
                    html += ' <em><b>' + obj.name + '</b></em>';
                    html += ' </a>';
                });
                if (html != "") {
                    setTimeout(function () {
                        $("#lib").html(html);                      
                        $("#embody").show();
                    }, 100);
                }
            }      
        } catch (err)
        { }

    });
</script>
  <div class="category clearfix">
    <div class="category_l">
       <img src="http://static.blog.csdn.net/images/category_icon.jpg">
        <span>分类:</span>
    </div>
    <div class="category_r">
                <label onclick="GetCategoryArticles('6348872','itluochen','top','52133599');">
                    <span onclick="_gaq.push(['_trackEvent','function', 'onclick', 'blog_articles_fenlei']);">Android开发<em>(109)</em></span>
                  <img class="arrow-down" src="http://static.blog.csdn.net/images/arrow_triangle _down.jpg" style="display:inline;">
                  <img class="arrow-up" src="http://static.blog.csdn.net/images/arrow_triangle_up.jpg" style="display:none;">
                    <div class="subItem">
                        <div class="subItem_t"><a href="http://blog.csdn.net/itluochen/article/category/6348872" target="_blank">作者同类文章</a><i class="J_close">X</i></div>
                        <ul class="subItem_l" id="top_6348872">                            
                        </ul>
                    </div>
                </label>                    
    </div>
</div>
<script type="text/javascript" src="http://static.blog.csdn.net/scripts/category.js"></script>  
用过Android手机的同学都知道,每次开机的时候系统会先扫描sdcard,sdcard重新插拔(挂载)也会扫描一次sdcard。

为什么要扫描sdcard,其实是为了给系统的其他应用提供便利,比如,Gallary、Music、VideoPlayer等应用,进入Gallary后会显示sdcard中的所有图片,

如果进入Gallary后再去扫描,可想而知,你会厌恶这个应用,因为我们会觉得它反应太慢了。还有Music你看到播放列表的时候实际能看到这首歌曲的时长、演唱者、专辑

等信息,这个也不是你进入应用后一下子可以读出来的。

所以Android使用sdcard挂载后扫描的机制,先将这些媒体相关的信息扫描出来保存在数据库中,当打开应用的时候直接去数据库读取(或者所通过MediaProvider去从数据库读取)并show给用户,这样用户体验会好很多,下面我们分析这种扫描机制是如何实现的。

在源码目录的\packages\providers\MediaProvider下面是MediaProvider的源码,它就是完成扫描并将数据保存于数据库中的程序。

先看下它的AndroidManifest.xml文件

application android:process=”android.process.media”,也就是应用程序名称为android.process.media,我们用adb 连接到android 设备,并且进入shell后输入ps可以看到的确有应用程序app_4     2796  2075  165192 19420 ffffffff 6fd0eb58 S android.process.media 在运行。另外此程序中有三个部分,分别是provider - MediaProvider 、receiver - MediaScannerReceiver、service - MediaScannerService,它并没有activity,说明它是一直运行于后台的程序。并且从receiver中的

  1. <intent-filter>  
  2.                 <action android:name=”android.intent.action.BOOT_COMPLETED” />  
  3.             </intent-filter>  



可以看出它是开机自启动的。下面从这个广播开始看代码。

  1. public void onReceive(Context context, Intent intent) {  
  2.         String action = intent.getAction();  
  3.         Uri uri = intent.getData();  
  4.         String externalStoragePath = Environment.getExternalStorageDirectory().getPath();  
  5.   
  6.         if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {  
  7.             // scan internal storage  
  8.             scan(context, MediaProvider.INTERNAL_VOLUME);  
  9.         } else {  
  10.      。 。 。  
  11.      }  
  12.   }  
  13. }  



收到开机广播后,首先执行scan函数STEP 1,
  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.     }    



scan函数主要传进来一个volume卷名,MediaProvider.INTERNAL_VOLUME实际就是内置存储卡”internal”,在此我们首先理解为开机后首先扫描内置存储卡。

然后启动services MediaScannerService,这也是此服务第一次被启动。

service的启动流程就不说了,onCreate肯定是首先被调用的

  1. public void onCreate()  
  2.    {  
  3.        PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);  
  4.        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);  
  5.   
  6.        // Start up the thread running the service.  Note that we create a  
  7.        // separate thread because the service normally runs in the process’s  
  8.        // main thread, which we don’t want to block.  
  9.        Thread thr = new Thread(nullthis“MediaScannerService”);  
  10.        thr.start();  
  11.    }  



此处前面是申请了一把wake lock ,主要是防止CPU休眠的,然后启动了一个线程实际就是 MediaScannerService自身的线程,它继承自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.     }  



可以看出此线程的目的是为了处理hander消息ServiceHandler

执行完onCreate后就会执行onStartCommand

  1. public int onStartCommand(Intent intent, int flags, int startId)  
  2.    {  
  3.        while (mServiceHandler == null) {  
  4.            synchronized (this) {  
  5.                try {  
  6.                    wait(100);  
  7.                } catch (InterruptedException e) {  
  8.                }  
  9.            }  
  10.        }  
  11.   
  12.        if (intent == null) {  
  13.            Log.e(TAG, ”Intent is null in onStartCommand: ”,  
  14.                new NullPointerException());  
  15.            return Service.START_NOT_STICKY;  
  16.        }  
  17.   
  18.        Message msg = mServiceHandler.obtainMessage();  
  19.        msg.arg1 = startId;  
  20.        msg.obj = intent.getExtras();  
  21.        mServiceHandler.sendMessage(msg);  
  22.   
  23.        // Try again later if we are killed before we can finish scanning.  
  24.        return Service.START_REDELIVER_INTENT;  
  25.    }  



在这里我们通过STEP 1中传入进来的volume字符串就作为了msg.obj通过handler来处理了
  1. private final class ServiceHandler extends Handler  
  2.    {  
  3.        @Override  
  4.        public void handleMessage(Message msg)  
  5.        {  
  6.            Bundle arguments = (Bundle) msg.obj;  
  7.            String filePath = arguments.getString(”filepath”);  
  8.            String folder = arguments.getString(”folder”);  
  9.            try {  
  10.                if (filePath != null) {  
  11.                    IBinder binder = arguments.getIBinder(”listener”);  
  12.                    IMediaScannerListener listener =   
  13.                            (binder == null ? null : IMediaScannerListener.Stub.asInterface(binder));  
  14.                    Uri uri = scanFile(filePath, arguments.getString(”mimetype”));  
  15.                    if (listener != null) {  
  16.                        listener.scanCompleted(filePath, uri);  
  17.                    }  
  18.                } else if(folder != null) {  
  19.                    String volume = arguments.getString(”volume”);  
  20.                    String[] directories = null;  
  21.                    directories = new String[] {  
  22.                        new File(folder).getPath(),  
  23.                    };  
  24.                    if (directories != null) {  
  25.                        if (Config.LOGD) Log.d(TAG, “start scanning volume ” + volume + “ ; path = ” + folder);  
  26.                        scan(directories, volume);  
  27.                        if (Config.LOGD) Log.d(TAG, “done scanning volume ” + volume + “ ; path = ” + folder);  
  28.                    }  
  29.                }<span style=”color:#ff0000;”>else</span> {  
  30.                    String volume = arguments.getString(”volume”);  
  31.                    String[] directories = null;  
  32.                      
  33.                    if (MediaProvider.INTERNAL_VOLUME.equals(volume)) {  
  34.                        // scan internal media storage  
  35.                        directories = new String[] {  
  36.                                Environment.getRootDirectory() + ”/media”,  
  37.                        };  
  38.                    }  
  39.                    else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {  
  40.                        // scan external storage  
  41.                        directories = new String[] {  
  42.                                Environment.getExternalStorageDirectory().getPath(),  
  43.                                };  
  44.                    }  
  45.                      
  46.                    if (directories != null) {  
  47.                        if (Config.LOGD) Log.d(TAG, “start scanning volume ” + volume);  
  48.                        scan(directories, volume);  
  49.                        if (Config.LOGD) Log.d(TAG, “done scanning volume ” + volume);  
  50.                    }  
  51.                }  
  52.            } catch (Exception e) {  
  53.                Log.e(TAG, ”Exception in handleMessage”, e);  
  54.            }  
  55.   
  56.            stopSelf(msg.arg1);  
  57.        }  
  58.    };  


很显然我们会执行到标记为红色的else中,我们是先扫描内置sdcard,很显然directories的值为/system/media ,然后调用 scan(directories, volume);函数,应该是内置sdcard中所有的媒体文件都几种存储在/system/media下面所以只需要扫描这一个路径就行了。STEP2
  1. private void scan(String[] directories, String volumeName) {  
  2.         // don’t sleep while scanning  
  3.         mWakeLock.acquire();  
  4.   
  5.         ContentValues values = new ContentValues();  
  6.         values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);  
  7.         Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values);  
  8.   
  9.         Uri uri = Uri.parse(”file://” + directories[0]);  
  10.         sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));  
  11.           
  12.         try {  
  13.             if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {  
  14.                  openDatabase(volumeName);      
  15.             }  
  16.   
  17.             MediaScanner scanner = createMediaScanner();  
  18.             scanner.scanDirectories(directories, volumeName);  
  19.         } catch (Exception e) {  
  20.             Log.e(TAG, ”exception in MediaScanner.scan()”, e);   
  21.         }  
  22.   
  23.         getContentResolver().delete(scanUri, nullnull);  
  24.   
  25.         sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));  
  26.         mWakeLock.release();  
  27.     }  



开始扫描和结束扫描时都会发送一个全局的广播,第三方应用程序也可以通过注册这两个广播来避开在media 扫描的时候往改扫描文件夹里面写入或删除文件,这个我在项目中就遇到过这种bug。在这一步骤中创建了MediaScanner并调用它的scanDirectories方法   STEP3
  1. public void scanDirectories(String[] directories, String volumeName) {  
  2.       try {  
  3.           long start = System.currentTimeMillis();  
  4.           initialize(volumeName);  
  5.           prescan(null);  
  6.           long prescan = System.currentTimeMillis();  
  7.   
  8.           for (int i = 0; i < directories.length; i++) {  
  9.               processDirectory(directories[i], MediaFile.sFileExtensions, mClient);  
  10.           }  
  11.           long scan = System.currentTimeMillis();  
  12.           postscan(directories);  
  13.           long end = System.currentTimeMillis();  
  14.   
  15.           if (Config.LOGD) {  
  16.               Log.d(TAG, ” prescan time: ” + (prescan - start) + “ms\n”);  
  17.               Log.d(TAG, ”    scan time: ” + (scan - prescan) + “ms\n”);  
  18.               Log.d(TAG, ”postscan time: ” + (end - scan) + “ms\n”);  
  19.               Log.d(TAG, ”   total time: ” + (end - start) + “ms\n”);  
  20.           }  
  21.       } catch (SQLException e) {  
  22.           // this might happen if the SD card is removed while the media scanner is running  
  23.           Log.e(TAG, ”SQLException in MediaScanner.scan()”, e);  
  24.       } catch (UnsupportedOperationException e) {  
  25.           // this might happen if the SD card is removed while the media scanner is running  
  26.           Log.e(TAG, ”UnsupportedOperationException in MediaScanner.scan()”, e);  
  27.       } catch (RemoteException e) {  
  28.           Log.e(TAG, ”RemoteException in MediaScanner.scan()”, e);  
  29.       }  
  30.   }  



其中initialize   prescan   processDirectory  postscan这四个函数都比较重要。STEP 4
  1. private void initialize(String volumeName) {  
  2.        mMediaProvider = mContext.getContentResolver().acquireProvider(”media”);  
  3.   
  4.        mAudioUri = Audio.Media.getContentUri(volumeName);  
  5.        mVideoUri = Video.Media.getContentUri(volumeName);  
  6.        mImagesUri = Images.Media.getContentUri(volumeName);  
  7.        mThumbsUri = Images.Thumbnails.getContentUri(volumeName);  
  8.   
  9.        if (!volumeName.equals(“internal”)) {  
  10.            // we only support playlists on external media  
  11.            mProcessPlaylists = true;  
  12.            mProcessGenres = true;  
  13.            mGenreCache = new HashMap<String, Uri>();  
  14.            mGenresUri = Genres.getContentUri(volumeName);  
  15.            mPlaylistsUri = Playlists.getContentUri(volumeName);  
  16.            // assuming external storage is FAT (case insensitive), except on the simulator.  
  17.            if ( Process.supportsProcesses()) {  
  18.                mCaseInsensitivePaths = true;  
  19.            }  
  20.        }  
  21.    }  



做一些初始化的动作,得到MediaProvider和一些URI实际也就是操作数据库的一些表名。Audio.Media.getContentUri可以在MediaStore.java中找到,此类保存了所有的媒体格式URI等信息,此处获得的mAudioUri的值为“content://media/internal//audio/media”

STEP5

  1.   private void prescan(String filePath) throws RemoteException {  
  2.     。 。 。  
  3. }  


此函数比较长,在此省略代码,有兴趣的可以看源码,这里所做的操作是对于之前有扫描过的,就将数据库中现有的媒体信息放到几个数据结构中临时存储起来。

然后最重要的STEP 6 processDirectory是一个native函数,先注意几个传入参数directories[i]为STEP2中传入的路径/system/media ,MediaFile.sFileExtensions 这个你可以跟到MediaFile中看看这个是如何赋值的,实际就是所有支持的媒体格式后缀以‘,’的方式串在一起的字符串”MP3,M4A,3GA,WAV。。。“最重要的mClient是MyMediaScannerClient的一个实例,此对象将是native层回调函数的接口,所有扫描完后的媒体都会通过此对象来存储到数据库中。

下面进入Native层对应文件是android_media_MediaScanner.cpp       STEP 6

  1. static void  
  2. android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, 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.     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.   
  27.     MyMediaScannerClient myClient(env, client);  
  28.     mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env);  
  29.     env->ReleaseStringUTFChars(path, pathStr);  
  30.     env->ReleaseStringUTFChars(extensions, extensionsStr);  
  31. }  


这里比较重要的一个点时MyMediaScannerClient myClient(env, client);定义了一个客户端,并将java层的client传入进去,很显然,是想通过MyMediaScannerClient 再来回调client。

STEP 7

  1. status_t MediaScanner::processDirectory(  
  2.         const char *path, const char *extensions,  
  3.         MediaScannerClient &client,  
  4.         ExceptionCheck exceptionCheck, void *exceptionEnv) {  
  5.     int pathLength = strlen(path);  
  6.     if (pathLength >= PATH_MAX) {  
  7.         return UNKNOWN_ERROR;  
  8.     }  
  9.     char* pathBuffer = (char *)malloc(PATH_MAX + 1);  
  10.     if (!pathBuffer) {  
  11.         return UNKNOWN_ERROR;  
  12.     }  
  13.   
  14.     int pathRemaining = PATH_MAX - pathLength;  
  15.     strcpy(pathBuffer, path);  
  16.     if (pathLength > 0 && pathBuffer[pathLength - 1] != ‘/’) {  
  17.         pathBuffer[pathLength] = ’/’;  
  18.         pathBuffer[pathLength + 1] = 0;  
  19.         –pathRemaining;  
  20.     }  
  21.   
  22.     client.setLocale(locale());  
  23.   
  24.     status_t result =  
  25.         doProcessDirectory(  
  26.                 pathBuffer, pathRemaining, extensions, client,  
  27.                 exceptionCheck, exceptionEnv);  
  28.   
  29.     free(pathBuffer);  
  30.   
  31.     return result;  
  32. }  



此函数没干什么事,具体工作是在doProcessDirectory中做的 STEP 8
  1. status_t MediaScanner::doProcessDirectory(  
  2.         char *path, int pathRemaining, const char *extensions,  
  3.         MediaScannerClient &client, ExceptionCheck exceptionCheck,  
  4.         void *exceptionEnv) {  
  5.      . . .   
  6. }  



此函数太长,在此不粘出来了,这里首先要解释下这些参数,path - 要扫描文件夹路径以’/’结尾,pathRemaining为路径长度与路径最大长度之间的差值,也就是防止扫描时路径超出范围,extensions 前面已经解释过是后缀,client是是STEP6中实例化的MyMediaScannerClient对象,后面两个参数是一些异常处理不用关心。

大家仔细看这个函数的代码就可以知道,它完成的是遍历文件夹并找到有相应extensions 里面后缀的文件fileMatchesExtension(path, extensions),如果文件大小大于0就调用client.scanFile(path, statbuf.st_mtime, statbuf.st_size);来进行文件读取扫描  注意这里才会读文件的实际内容。

STEP 9

  1. virtual bool scanFile(const char* path, long long lastModified, long long fileSize)  
  2.    {  
  3.        jstring pathStr;  
  4.        if ((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;  
  5.   
  6.        mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);  
  7.   
  8.        mEnv->DeleteLocalRef(pathStr);  
  9.        return (!mEnv->ExceptionCheck());  
  10.    }  


看看,终于用到了mClient,java层传进来的client ,这就是回调到了java 类MyMediaScannerClient里面的STEP 10
  1. public void scanFile(String path, long lastModified, long fileSize) {  
  2.            // This is the callback funtion from native codes.  
  3.            // Log.v(TAG, “scanFile: ”+path);  
  4.            doScanFile(path, null, lastModified, fileSize, false);  
  5.        }  


主要看doScanFile     STEP 11
  1.   public Uri doScanFile(String path, String mimeType, long lastModified, long fileSize, boolean scanAlways) {  
  2.             Uri result = null;  
  3. //            long t1 = System.currentTimeMillis();  
  4.             try {  
  5.                 FileCacheEntry entry = beginFile(path, mimeType, lastModified, fileSize);  
  6.                 // rescan for metadata if file was modified since last scan  
  7.                 if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {  
  8.                     String lowpath = path.toLowerCase();  
  9.                     boolean ringtones = (lowpath.indexOf(RINGTONES_DIR) > 0);  
  10.                     boolean notifications = (lowpath.indexOf(NOTIFICATIONS_DIR) > 0);  
  11.                     boolean alarms = (lowpath.indexOf(ALARMS_DIR) > 0);  
  12.                     boolean podcasts = (lowpath.indexOf(PODCAST_DIR) > 0);  
  13.                     boolean music = (lowpath.indexOf(MUSIC_DIR) > 0) ||  
  14.                         (!ringtones && !notifications && !alarms && !podcasts);  
  15.   
  16.                     if (!MediaFile.isImageFileType(mFileType)) {  
  17.                         processFile(path, mimeType, this);  
  18.                     }  
  19.   
  20.                     result = endFile(entry, ringtones, notifications, alarms, music, podcasts);  
  21.                 }  
  22.             } catch (RemoteException e) {  
  23.                 Log.e(TAG, ”RemoteException in MediaScanner.scanFile()”, e);  
  24.             }  
  25. //            long t2 = System.currentTimeMillis();  
  26. //            Log.v(TAG, “scanFile: ” + path + ” took ” + (t2-t1));  
  27.             return result;  
  28.         }  


此函数里面又有三个比较重要的函数beginFile   processFile   endFile
先看beginFile    STEP 12
  1.   public FileCacheEntry beginFile(String path, String mimeType, long lastModified, long fileSize) {  
  2.      . . .  
  3. }  


构建一个FileCacheEntry对象,存储文件的一些基本信息,并且放入mFileCache HashMap中。

根据此文件是否修改来觉得是否processFile   ,又进入到native中

在此插入一段代码

  1. static void  
  2. android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz)  
  3. {  
  4.     MediaScanner *mp = NULL;  
  5.     char value[PROPERTY_VALUE_MAX];  
  6.     if (property_get(“media.framework.option”, value, NULL) && (!strcmp(value, “1”))){  
  7. #ifndef NO_OPENCORE  
  8.         mp = new PVMediaScanner();  
  9. #else  
  10.         mp = new StagefrightMediaScanner;  
  11. #endif  
  12.     }else{  
  13.         mp = new StagefrightMediaScanner;  
  14.     }  
  15.   
  16.     if (mp == NULL) {  
  17.         jniThrowException(env, ”java/lang/RuntimeException”“Out of memory”);  
  18.         return;  
  19.     }  
  20.   
  21.     env->SetIntField(thiz, fields.context, (int)mp);  
  22. }  



android2.2以上mediascanner使用StagefrightMediaScanner

STEP 13

  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.     mp->processFile(pathStr, mimeTypeStr, myClient);  
  25.     env->ReleaseStringUTFChars(path, pathStr);  
  26.     if (mimeType) {  
  27.         env->ReleaseStringUTFChars(mimeType, mimeTypeStr);  
  28.     }  
  29. }  



不再累述,直接进入 STEP 14
  1. status_t StagefrightMediaScanner::processFile(  
  2.         const char *path, const char *mimeType,  
  3.         MediaScannerClient &client) {  
  4.      . . .   
  5. }  



由于StagefrightMediaScanner又进入到了stagefright 框架,比较复杂,鉴于篇幅限制,在下一篇blog中继续分析STEP 14

document.getElementById("bdshell_js").src = "http://bdimg.share.baidu.com/static/js/shell_v2.js?cdnversion=" + Math.ceil(new Date()/3600000)
    <div id="digg" articleid="52133599">
        <dl id="btnDigg" class="digg digg_enable" onclick="btndigga();">

             <dt>顶</dt>
            <dd>0</dd>
        </dl>


        <dl id="btnBury" class="digg digg_enable" onclick="btnburya();">

              <dt>踩</dt>
            <dd>0</dd>               
        </dl>

    </div>
 <div class="tracking-ad" data-mod="popu_222"><a href="javascript:void(0);">&nbsp;</a>   </div>
<div class="tracking-ad" data-mod="popu_223"> <a href="javascript:void(0);">&nbsp;</a></div>
<script type="text/javascript">
            function btndigga() {
                $(".tracking-ad[data-mod='popu_222'] a").click();
            }
            function btnburya() {
                $(".tracking-ad[data-mod='popu_223'] a").click();
            }
        </script>

<div style="clear:both; height:10px;"></div>


    <div class="similar_article" style="">
            <h4>我的同类文章</h4>
            <div class="similar_c" style="margin:20px 0px 0px 0px">
                <div class="similar_c_t">
                            <label class="similar_cur">
                                <span style="cursor:pointer" onclick="GetCategoryArticles('6348872','itluochen','foot','52133599');">Android开发<em>(109)</em></span>
                            </label>
                </div>

                <div class="similar_wrap tracking-ad" data-mod="popu_141" style="max-height:195px;">
                    <a href="http://blog.csdn.net" style="display:none">http://blog.csdn.net</a>
                    <ul class="similar_list fl"><li><em>•</em><a href="http://blog.csdn.net/itluochen/article/details/53638952" id="foot_aritcle_53638952undefined48144545033574104" target="_blank" title="Android 自定义软键盘">Android 自定义软键盘</a><span>2016-12-14</span><label><i>阅读</i><b>11</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/itluochen/article/details/53421228" id="foot_aritcle_53421228undefined8933926734607667" target="_blank" title="Jsoup解析Html(一)">Jsoup解析Html(一)</a><span>2016-12-01</span><label><i>阅读</i><b>45</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/itluochen/article/details/53321593" id="foot_aritcle_53321593undefined4698589702602476" target="_blank" title="浅谈Android 插件化">浅谈Android 插件化</a><span>2016-11-24</span><label><i>阅读</i><b>40</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/itluochen/article/details/53320871" id="foot_aritcle_53320871undefined6368898467626423" target="_blank" title="浅析Android webview的使用">浅析Android webview的使用</a><span>2016-11-24</span><label><i>阅读</i><b>47</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/itluochen/article/details/53317941" id="foot_aritcle_53317941undefined5141322552226484" target="_blank" title="如何知道一个App的包名呢">如何知道一个App的包名呢</a><span>2016-11-24</span><label><i>阅读</i><b>253</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/itluochen/article/details/53317793" id="foot_aritcle_53317793undefined6544583647046238" target="_blank" title="如何根据包名packageName获取程序启动的主Activity名称?">如何根据包名packageName获取程序启动的主Activity名称?</a><span>2016-11-24</span><label><i>阅读</i><b>111</b></label></li> </ul>

                    <ul class="similar_list fr"><li><em>•</em><a href="http://blog.csdn.net/itluochen/article/details/53535248" id="foot_aritcle_53535248undefined9872061323840171" target="_blank" title="Android Activity横屏、竖屏、全屏">Android Activity横屏、竖屏、全屏</a><span>2016-12-09</span><label><i>阅读</i><b>28</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/itluochen/article/details/53336460" id="foot_aritcle_53336460undefined8950994526967406" target="_blank" title="浅谈Android 浏览器内核">浅谈Android 浏览器内核</a><span>2016-11-25</span><label><i>阅读</i><b>150</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/itluochen/article/details/53321196" id="foot_aritcle_53321196undefined6374679184518754" target="_blank" title="模拟器使用命令安装apk">模拟器使用命令安装apk</a><span>2016-11-24</span><label><i>阅读</i><b>33</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/itluochen/article/details/53318784" id="foot_aritcle_53318784undefined6003570288885385" target="_blank" title="模拟器安装不了apk">模拟器安装不了apk</a><span>2016-11-25</span><label><i>阅读</i><b>326</b></label></li> <li><em>•</em><a href="http://blog.csdn.net/itluochen/article/details/53317900" id="foot_aritcle_53317900undefined7309573402162641" target="_blank" title="MonkeyRunner 之如何获取APP的PackageName和Activity Name">MonkeyRunner 之如何获取APP的PackageName和Activity Name</a><span>2016-11-24</span><label><i>阅读</i><b>38</b></label></li> </ul>
                <a href="http://blog.csdn.net/itluochen/article/category/6348872" class="MoreArticle">更多文章</a></div>
            </div>
        </div>    
<script type="text/javascript">
    $(function () {
        GetCategoryArticles('6348872', 'itluochen','foot','52133599');
    });
</script>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值