歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Android media媒體庫分析之:MediaProvider

在做Android媒體應用程序時(Audio、Image、Video)需要對Android的媒體提供者(MediaProvider)做詳細的分析,下面記錄一下我的收獲:

一、獲取MediaProvider:
該工程在系統源碼的packages\providers目錄下,提出並導入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

Copyright © Linux教程網 All Rights Reserved