Android MediaScanner深入研究



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


MediaScanner分析

  一 MediaScannerService


  多媒体扫描是从MediaScannerService开始的。这是一个单独的package。位于packagesprovidersMediaProvider:含以下java文件

java代码:


  1. MediaProvider.java
  2. MediaScannerReceiver.java
  3. MediaScannerService.java
  4. MediaThumbRequest.java
复制代码


  分析这个目录的Android.mk文件,发现它运行的进程名字就是android.process.media。
application android:process=android.process.media

  1.1 MediaScannerReceiver

  这个类从BroadcastReceiver中派生,用来接收任务的。MediaScannerReceiver extends BroadcastReceiver
在它重载的onRecieve函数内有以下几种走向:

java代码:


  1. if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {

  2. // 收到”启动完毕“广播后,扫描内部存储
  3. scan(context, MediaProvider.INTERNAL_VOLUME);
  4. } else {
  5. if (action.equals(Intent.ACTION_MEDIA_MOUNTED) &&
  6. externalStoragePath.equals(path)) {
  7. /收到MOUNT信息后,扫描外部存储
  8. scan(context, MediaProvider.EXTERNAL_VOLUME);
  9. }
  10. else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) &&
  11. path != null && path.startsWith(externalStoragePath + "/")) {
  12. //收到请求要求扫描某个文件,注意不会扫描内部存储上的文件
  13. scanFile(context, path);

  14. }
复制代码


下面是它调用的scan函数:

java代码:


  1. scan(Context context, String volume)

  2. Bundle args = new Bundle();
  3. args.putString("volume", volume);
  4. //直接启动MediaScannerService了,
  5. 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

java代码:


  1. public void onCreate()

  2. PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
  3. mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
  4. //获得电源锁,防止在扫描过程中休眠
  5. //单独搞一个线程去跑扫描工作,防止ANR
  6. Thread thr = new Thread(null, this, "MediaScannerService");
  7. thr.start();
复制代码


  2. onStartCommand

java代码:


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

  3. //注意这个handler,是在另外一个线程中创建的,往这个handler里sendMessage
  4. //都会在那个线程里边处理
  5. //不明白的可以去查看handler和Looper机制
  6. //这里就是同步机制,等待mServiceHandler在另外那个线程创建完毕

  7. while (mServiceHandler == null) {
  8. synchronized (this) {
  9. try {
  10. wait(100);
  11. } catch (InterruptedException e) {
  12. }
  13. }
  14. }

  15. if (intent == null) {
  16. Log.e(TAG, "Intent is null in onStartCommand: ",new NullPointerException());
  17. return Service.START_NOT_STICKY;
  18. }
  19. Message msg = mServiceHandler.obtainMessage();
  20. msg.arg1 = startId;
  21. msg.obj = intent.getExtras();
  22. //把MediaScannerReceiver发出的消息传递到另外那个线程去处理。

  23. mServiceHandler.sendMessage(msg);
复制代码



     3. run

java代码:


  1. public void run(){

  2. // reduce priority below other background threads to avoid interfering
  3. // with other services at boot time.

  4. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND +Process.THREAD_PRIORITY_LESS_FAVORABLE);

  5. //不明白的去看看Looper和handler的实现
  6. Looper.prepare();
  7. //把这个looper对象设置到线程本地存储
  8. mServiceLooper = Looper.myLooper();
  9. mServiceHandler = new ServiceHandler();
  10. //创建handler,默认会把这个looper
  11. //的消息队列赋值给handler的消息队列,这样往handler中发送消息就是往这个线程的looper发
  12. Looper.loop();
  13. //消息循环,内部会处理消息队列中的消息
  14. //也就是handleMessage函数
  15. }
复制代码


    上面handler中加入了一个扫描请求(假设是外部存储的),所以要分析handleMessage函数。

     4. handleMessage

java代码:


  1. private final class ServiceHandler extends Handler{

  2. @Override
  3. public void handleMessage(Message msg){

  4. Bundle arguments = (Bundle) msg.obj;
  5. String filePath = arguments.getString("filepath");
  6. try {

  7. 这里不讲了
  8. } else {
  9. String volume = arguments.getString("volume");
  10. String[] directories = null;
  11. if (MediaProvider.INTERNAL_VOLUME.equals(volume)) {
  12. //是扫描内部存储的请求?
  13. // scan internal media storage
  14. directories = new String[] {

  15. Environment.getRootDirectory() + "/media",
  16. };
  17. }

  18. else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {
  19. //是扫描外部存储的请求?获取外部存储的路径
  20. directories = new String[] {
  21. Environment.getExternalStorageDirectory().getPath(),
  22. };
  23. }
  24. if (directories != null) {
  25. //真正的扫描开始了,上面只不过是把存储路径取出来罢了.
  26. scan(directories, volume);
  27. //扫描完了,就把service停止了
  28. stopSelf(msg.arg1);
  29. }

  30. };
复制代码


    5. scan函数

java代码:


  1. private void scan(String[] directories, String volumeName) {
  2. mWakeLock.acquire();
  3. //下面这三句话很深奥…
  4. //从 getContentResolver获得一个ContentResover,然后直接插入
  5. //根据AIDL,这个ContentResover的另一端是MediaProvider。只要去看看它的
  6. //insert函数就可以了
  7. //反正这里知道获得了一个扫描URI即可。
  8. ContentValues values = new ContentValues();
  9. values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);
  10. Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values);
  11. Uri uri = Uri.parse("file://" + directories[0]);
  12. //发送广播,通知扫描开始了
  13. sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));

  14. try {
  15. if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {
  16. openDatabase(volumeName);
  17. }
  18. //创建真正的扫描器
  19. MediaScanner scanner = createMediaScanner();
  20. //交给扫描器去扫描文件夹 scanDirectories
  21. scanner.scanDirectories(directories, volumeName);
  22. } catch (Exception e) {
  23. Log.e(TAG, "exception in MediaScanner.scan()", e);
  24. }
  25. //删除扫描路径
  26. getContentResolver().delete(scanUri, null, null);
  27. //通知扫描完毕
  28. sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));
  29. mWakeLock.release();
  30. }
复制代码


  说说上面那个深奥的地方,在MediaProvider中重载了insert函数,insert函数会调用insertInternal函数。
  如下:

java代码:


  1. private Uri insertInternal(Uri uri, ContentValues initialValues) {
  2. long rowId;

  3. int match = URI_MATCHER.match(uri);
  4. // handle MEDIA_SCANNER before calling getDatabaseForUri()
  5. //刚才那个insert只会走下面这个分支,其实就是获得一个地址….
  6. //太绕了

  7. if (match == MEDIA_SCANNER) {
  8. mMediaScannerVolume = initialValues.getAsString(MediaStore.MEDIA_SCANNER_VOLUME);
  9. return MediaStore.getMediaScannerUri();
  10. }
复制代码


   再看看它创建了什么样的Scanner,这就是MSS中的createMediaScanner

java代码:


  1. private MediaScanner createMediaScanner() {

  2. //下面这个MediaScanner在framework/base/中,待会再分析
  3. MediaScanner scanner = new MediaScanner(this);
  4. //设置当前的区域,这个和字符编码有重大关系。
  5. Locale locale = getResources().getConfiguration().locale;

  6. if (locale != null) {
  7. String language = locale.getLanguage();
  8. String country = locale.getCountry();
  9. String localeString = null;
  10. if (language != null) {
  11. if (country != null) {
  12. //给扫描器设置当前国家和语言。
  13. scanner.setLocale(language + "_" + country);
  14. } else {
  15. scanner.setLocale(language);
  16. }
  17. }
  18. }
  19. return scanner;

  20. }
复制代码


至此,MSS的任务完成了。接下来是MediaScanner的工作了。
6. 总结

       MSS的工作流程如下:

  1 单独启动一个带消息循环的工作线程。
  2 主线程接收系统发来的任务,然后发送给工作线程去处理。
  3 工作线程接收任务,创建一个MediaScanner去扫描。
  4 MSS顺带广播一下扫描工作启动了,扫描工作完毕了。

       二 MediaScanner

  MediaScanner位置在
  frameworks asemedia下,包括jni和java文件。
  先看看java实现。
  这个类巨复杂,而且和MediaProvider交互频繁。在分析的时候要时刻回到MediaProvider去看看。
  1. 初始化

java代码:


  1. public class MediaScanner{

  2. static {
  3. //libmedia_jni.so的加载是在MediaScanner类中完成的
  4. //这么重要的so为何放在如此不起眼的地方加载???

  5. System.loadLibrary("media_jni");
  6. native_init();
  7. }

  8. public MediaScanner(Context c) {
  9. native_setup();//调用jni层的初始化,暂时不用看了,无非就是一些
  10. //初始化工作,待会在再进去看看

  11. }
复制代码



  刚才MSS中是调用scanDirectories函数,我们看看这个。

       2. scanDirectories

java代码:


  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. for (int i = 0; i < directories.length; i++) {

  8. //扫描文件夹,这里有一个很重要的参数 mClient
  9. // processDirectory是一个native函数
  10. processDirectory(directories[i], MediaFile.sFileExtensions, mClient);
  11. }

  12. long scan = System.currentTimeMillis();
  13. postscan(directories);//扫描后处理
  14. long end = System.currentTimeMillis();

  15. 打印时间,异常处理没了
  16. 下面简单讲讲initialize ,preScan和postScan都干嘛了。

  17. private void initialize(String volumeName) {

  18. //打开MediaProvider,获得它的一个实例

  19. mMediaProvider = mContext.getContentResolver().acquireProvider("media");
  20. //得到一些uri
  21. mAudioUri = Audio.Media.getContentUri(volumeName);
  22. mVideoUri = Video.Media.getContentUri(volumeName);
  23. mImagesUri = Images.Media.getContentUri(volumeName);
  24. mThumbsUri = Images.Thumbnails.getContentUri(volumeName);
  25. //外部存储的话,可以支持播放列表之类的东西,搞了一些个缓存池之类的
  26. //如mGenreCache等
  27. if (!volumeName.equals("internal")) {
  28. // we only support playlists on external media
  29. mProcessPlaylists = true;
  30. mGenreCache = new HashMap();
复制代码


  2. scanDirectories

java代码:


  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. for (int i = 0; i < directories.length; i++) {
  8. //扫描文件夹,这里有一个很重要的参数 mClient
  9. // processDirectory是一个native函数

  10. processDirectory(directories[i], MediaFile.sFileExtensions, mClient);
  11. }

  12. long scan = System.currentTimeMillis();
  13. postscan(directories);//扫描后处理
  14. long end = System.currentTimeMillis();
  15. …..打印时间,异常处理…没了…

  16. 下面简单讲讲initialize ,preScan和postScan都干嘛了。

  17. private void initialize(String volumeName) {
  18. //打开MediaProvider,获得它的一个实例

  19. mMediaProvider = mContext.getContentResolver().acquireProvider("media");
  20. //得到一些uri
  21. mAudioUri = Audio.Media.getContentUri(volumeName);
  22. mVideoUri = Video.Media.getContentUri(volumeName);
  23. mImagesUri = Images.Media.getContentUri(volumeName);
  24. mThumbsUri = Images.Thumbnails.getContentUri(volumeName);

  25. //外部存储的话,可以支持播放列表之类的东西,搞了一些个缓存池之类的
  26. //如mGenreCache等
  27. if (!volumeName.equals("internal")) {

  28. mProcessPlaylists = true;
  29. mGenreCache = new HashMap();
复制代码


  preScan,这个函数很复杂:

  大概就是创建一个FileCache,用来缓存扫描文件的一些信息,例如last_modified等。这个FileCache是从MediaProvider中已有信息构建出来的,也就是历史信息。后面根据扫描得到的新信息来对应更新历史信息。

     postScan,这个函数做一些清除工作,例如以前有video生成了一些缩略图,现在video文件被干掉了,则对应的缩略图也要被干掉。

  另外还有一个mClient,这个是从MediaScannerClient派生下来的一个东西,里边保存了一个文件的一些信息。后续再分析。

       在frameworks asemediajniandroid_media_MediaScanner.cpp中。

  刚才说到,具体扫描工作是在processDirectory函数中完成的。这个是一个native函数。
     三 MediaScanner JNI层分析

  MediaScanner JNI层内容比较多,单独搞一节分析吧。先看看android_media_MediaScanner这个文件。

  1. native_init函数,jni对应的函数如下

java代码:


  1. static void

  2. android_media_MediaScanner_native_init(JNIEnv *env){

  3. jclass clazz;
  4. clazz = env->FindClass("android/media/MediaScanner");
  5. //得都JAVA类中mNativeContext这个成员id

  6. fields.context = env->GetFieldID(clazz, "mNativeContext", "I");

  7. //不熟悉JNI的自己去学习下吧
  8. }
复制代码


2. native_setup函数,jni对应函数如下:

java代码:


  1. android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz){

  2. //创建MediaScanner对象
  3. MediaScanner *mp = createMediaScanner();
  4. //太变态了,自己不保存这个对象指针.
  5. //却把它设置到java对象的mNativeContext去保存
  6. env->SetIntField(thiz, fields.context, (int)mp);
  7. }

  8. //创建MediaScanner函数
  9. static MediaScanner *createMediaScanner() {

  10. #if BUILD_WITH_FULL_STAGEFRIGHT

  11. //使用google自己的

  12. return new StagefrightMediaScanner;

  13. #endif
  14. #ifndef NO_OPENCORE
  15. //使用opencore提供的.
  16. return new PVMediaScanner();
复制代码


  3. processDirectories函数,jni对应如下:

java代码:


  1. android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, jobject client){

  2. MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context);
  3. //每次都要回调到JAVA中去取这个Scanner!!

  4. const char *pathStr = env->GetStringUTFChars(path, NULL);
  5. const char *extensionsStr = env->GetStringUTFChars(extensions, NULL);

  6. //又在C++这里搞一个client,然后把java的client放到C++Client中去保存
  7. //而且还是栈上的临时变量..
  8. MyMediaScannerClient myClient(env, client);
  9. //scanner扫描文件夹,用得是C++的client

  10. mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env);
  11. env->ReleaseStringUTFChars(path, pathStr);
  12. env->ReleaseStringUTFChars(extensions, extensionsStr);
  13. }
复制代码


到这里似乎就没有了,那么扫描后的数据库是怎么更新的呢?为什么要传入一个client进去呢?看来必须得trace到scanner中去才知道了
四 PVMediaScanner

  这个在externalopencoreandroidmediascanner.cpp中。

  1. processDirectory

java代码:


  1. status_t MediaScanner::processDirectory(const char *path, const char* extensions,
  2. MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv)
  3. {

  4. InitializeForThread();
  5. int error = 0;
  6. status_t result = PVMFSuccess;
  7. //调用client的设置区域函数
  8. client.setLocale(mLocale);
  9. //扫描文件夹,咋还没开始??
  10. result = doProcessDirectory(pathBuffer, pathRemaining, extensions, client, exceptionCheck, exceptionEnv);
复制代码


   2. doProcessDirectory

java代码:


  1. status_t MediaScanner::doProcessDirectory(char *path, int pathRemaining, const char* extensions,
  2. MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv)
  3. {

  4. //终于看到点希望了
  5. //打开这个文件夹,枚举其中的内容。
  6. //题外话,这个时候FileManager肯定删不掉这个文件夹!!

  7. DIR* dir = opendir(path);

  8. while ((entry = readdir(dir))) {
  9. const char* name = entry->d_name;
  10. //不处理.和..文件夹


  11. if (isDirectory) {
  12. //不处理.开头的文件夹。如果是文件夹,递归调用doProcessDirectory
  13. //深度优先啊!
  14. int err = doProcessDirectory(path, pathRemaining - nameLength - 1, extensions, client, exceptionCheck, exceptionEnv);

  15. if (err) {
  16. LOGE("Error processing '%s' - skipping ", path);
  17. continue;
  18. }

  19. } else if (fileMatchesExtension(path, extensions)) {
  20. //是一个可以处理的文件,交给client处理
  21. //彻底疯掉了….这是干嘛呢???
  22. client.scanFile(path, statbuf.st_mtime, statbuf.st_size);
复制代码


这里要解释下,刚才createMediaScanner中,明明创建的是PVMediaScanner,为何这里看得是MediaScanner代码呢?
  因为PVMediaScannerMediaScanner中派生下来的,而且没有重载processDirectory函数Eclaire没有PVMediaScanner这样的东西,估计是froyo又改了点啥吧。

       FT,processDirctory无非是列举一个目录内容,然后又反回去调用client的scanFile处理了。为何搞这么复杂?只有回去看看C++的client干什么了。

       MediaScannerClient---JNI层

  JNI中的这个类是这样的:

java代码:


  1. class MyMediaScannerClient : public MediaScannerClient
  2. //这是它的scanFile实现

  3. virtual bool scanFile(const char* path, long long lastModified, long long fileSize){

  4. //再次崩溃了,C++的client调用了刚才传进去的java Client的
  5. //scanFile函数…不过这次还传进去了该文件的路径,最后修改时间和文件大小。

  6. mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);
  7. //想死,,,
  8. }
复制代码


  没办法了,只能再去看看MediaScanner.java传进去的那个client了。

         MediaScannerClient----JAVA层
         这个类在MediaScanner.java中实现。

java代码:


  1. private class MyMediaScannerClient implements MediaScannerClient:

  2. public void scanFile(String path, long lastModified, long fileSize) {
  3. //调用doScanFile..很烦..

  4. doScanFile(path, null, lastModified, fileSize, false);
  5. //下面是doScanFile

  6. public Uri doScanFile(String path, String mimeType, long lastModified, long fileSize, boolean scanAlways) {

  7. //预处理,看看之前创建的文件缓存中有没有这个文件
  8. FileCacheEntry entry = beginFile(path, mimeType, lastModified, fileSize);

  9. // rescan for metadata if file was modified since last scan
  10. if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {

  11. //如果事先有这个文件的信息,则需要修改一些信息,如长度,最后修改时间等
  12. //真正的扫描文件

  13. processFile(path, mimeType, this);
  14. //扫描完了,做最后处理
  15. endFile(entry, ringtones, notifications, alarms, music, podcasts);
  16. //processFile又是jni层的。
  17. //对应android_media_MediaScanner_processFile函数

  18. android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client){

  19. MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context);
  20. //无语了,又搞一个 MyMediaScannerClient
  21. MyMediaScannerClient myClient(env, client);
  22. mp->processFile(pathStr, mimeTypeStr, myClient);
  23. }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值