• Android media媒体库分析之:MediaProvider


    在做Android媒体应用程序时(Audio、Image、Video)需要对Android的媒体提供者(MediaProvider)做详细的分析,下面记录一下我的收获:

    一、获取MediaProvider: 
    该工程在系统源码的packagesproviders目录下,提出并导入Eclipse,便于阅读;

    图中可见都很多报错的,是滴,因为需要一些系统标准sdk之外的接口,不过不影响我们阅读代码。

    二、工程结构及内部关系: 
    可以从上图看出包含4个文件: 
    MediaScannerService.Java:媒体服务,配合广播实现媒体扫描类的实例化,数据库的初始化等工作,也向外提供接口; 
    MediaScannerReceiver.Java:一个广播接收器,用于接受系统发给媒体服务的广播并启动媒体服务; 
    MediaProvider.Java:媒体数据库的封装类,代码量比较大(四千多行),功能比较复杂,但总的来说就是创建数据库,对外提供URI以实现对数据库的增删改查功能; 
    MediaThumbRequest.Java:媒体文件缩略图请求类,与MediaProvider配合使用; 
    上一个关系图更直观一些:

    上图不是标准的类图,只是为了梳理逻辑关系画的结构图。 
    MediaProvider所处的位置及作用见图中红色框中的内容; 
    上图还包括其他内容: 
    1、App层:audio、image、video如何与媒体库进行交互; 
    2、框架层(android.media包下):如何实现媒体的扫描; 
    3、Native层:如何实现正在的媒体文件解析; 
    4、资源存储层:sd卡、U盘等介质,DTCM存储缩略图;

    三、类详解

    1、MediaScannerReceiver:

     public void onReceive(Context context, Intent intent) {
      String action = intent.getAction();
      Uri uri = intent.getData();
      // String externalStoragePath =
      // Environment.getExternalStorageDirectory().getPath();
      String externalSDStoragePath = Environment
        .getExternalSDStorageDirectory().getPath();
      String externalUDiskStoragePath = Environment
        .getExternalUDiskStorageDirectory().getPath();
      String externalExtSDStoragePath = Environment
        .getExternalExtSDStorageDirectory().getPath();

      if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
       // scan internal storage
       scan(context, MediaProvider.INTERNAL_VOLUME);
      } else {
       if (uri.getScheme().equals("file")) {
        // handle intents related to external storage
        String path = uri.getPath();
        if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
         if (externalSDStoragePath.equals(path))
          scan(context, MediaProvider.EXTERNAL_VOLUME_SD);
         else if (externalUDiskStoragePath.equals(path))
          scan(context, MediaProvider.EXTERNAL_VOLUME_UDISK);
         else if (externalExtSDStoragePath.equals(path))
          scan(context, MediaProvider.EXTERNAL_VOLUME_EXTSD);
         else
          Slog.w(TAG, "unknown volume path " + path);
        } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
          && path != null
          && (path.startsWith(externalSDStoragePath + "/")
            || path.startsWith(externalExtSDStoragePath
              + "/") || path
             .startsWith(externalUDiskStoragePath + "/"))) {
         scanFile(context, path);
        }
       }
      }
     }

    三类情况需要启动扫描服务: 
    a、系统启动完成; 
    b、媒体挂载(EXTERNAL_VOLUME_SD、EXTERNAL_VOLUME_UDISK、EXTERNAL_VOLUME_EXTSD); 
    c、媒体文件扫描广播(ACTION_MEDIA_SCANNER_SCAN_FILE);

    scanFile和scan方法很简单,只是启动媒体服务即可:

     private void scan(Context context, String volume) {
      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();
      Slog.i(TAG, "Start scanFile.");
      args.putString("filepath", path);
      context.startService(new Intent(context, MediaScannerService.class)
        .putExtras(args));
     }

    2、MediaScannerService: 
    第一步:启动一个线程

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

      mServiceLooper = Looper.myLooper();
      mServiceHandler = new ServiceHandler();

      Looper.loop();
     }

    在线程中拿到当前的消息队列,使用handler处理消息;

    第二部:启动ServiceHandler处理消息 
    ServiceHandler中还是处理两种,一种是扫描,第二种是具体媒体文件的解析; 
    看一下第二种是如何实现的:

         IBinder binder = arguments.getIBinder("listener");
         IMediaScannerListener listener = (binder == null ? null
           : IMediaScannerListener.Stub.asInterface(binder));
         Uri uri = scanFile(filePath,
           arguments.getString("mimetype"));
         if (listener != null) {
          listener.scanCompleted(filePath, uri);
         }

    代码

     private final IMediaScannerService.Stub mBinder = new IMediaScannerService.Stub() {
      public void requestScanFile(String path, String mimeType,
        IMediaScannerListener listener) {
       if (Config.LOGD) {
        Log.d(TAG, "IMediaScannerService.scanFile: " + path
          + " mimeType: " + mimeType);
       }
       Bundle args = new Bundle();
       args.putString("filepath", path);
       args.putString("mimetype", mimeType);
       if (listener != null) {
        args.putIBinder("listener", listener.asBinder());
       }
       startService(new Intent(MediaScannerService.this,
         MediaScannerService.class).putExtras(args));
      }

      public void scanFile(String path, String mimeType) {
       requestScanFile(path, mimeType, null);
      }
     };

    那么问题来了:如果我们在App中想让系统媒体库解析具体某一个文件,应该怎么做呢? 
    从上面代码可以看到,MediaScannerService给我们提供的绑定接口,我们只需要传递filepath和一个IMediaScannerListener listener即可,媒体库在解析完之后会回调scanCompleted方法告诉我们解析结果;

    第三步:创建MediaScanner对象,完成扫描和解析; 
    可见具体扫描、解析工作也不是MediaScannerService做的,MediaScannerService是只在调用sacn、acanfile方法时创建了MediaScanner对象并交给他处理; 
    MediaScanner在android.media.MediaScanner系统framework里面,这儿就不做讨论了;

    MediaScannerService基本就这些内容了;

    3、MediaProvider: 
    MediaProvider就是创建数据库,对外提供URI以实现对数据库的增删改查功能;

    4、MediaThumbRequest: 
    Audio、Image、Video文件都是有缩略图的,缩略图路径存储在DB中,其真实文件存储在sd卡的DICM文件夹下,MediaThumbRequest只是提供给MediaProvider类操作数据库使用。 
    主要的就两个方法,一个新建缩略图方法:execute,一个更新缩略图方法:updateDatabase 
    新技能get:应用中获取缩略图,期待下一篇文章;

    至此,MediaProvider结构分析清楚了,后续计划补两片文章: 
    APP中使用系统媒体库; 
    媒体文件扫描、解析是如何实现的;

    见:

    1. http://www.linuxidc.com/Linux/2015-03/114754.htm
    2. http://www.linuxidc.com/Linux/2015-03/114756.htm

    更多Android相关信息见Android 专题页面 http://www.linuxidc.com/topicnews.aspx?tid=11

    本文永久更新链接地址http://www.linuxidc.com/Linux/2015-03/114755.htm

    linux

  • 相关阅读:
    软件性能中几个主要的术语
    (转)性能测试指标
    不同角度关注的软件性能
    《跃迁:从技术到管理的硅谷路径》读书笔记
    Mysql基础知识—索引
    Python学习笔记系列——高阶函数(map/reduce)
    Python学习笔记系列——高阶函数(filter/sorted)
    (转)Python学习笔记系列——Python是一种纯粹的语言
    Python学习笔记系列——读写文件以及敏感词过滤器的实现
    双向链表(Double-Linked List)
  • 原文地址:https://www.cnblogs.com/senior-engineer/p/5115272.html
Copyright © 2020-2023  润新知