Android media媒體庫分析之:MediaProvider

親們,原創文章轉載時請註明出處,謝謝!
在做Android媒體應用程序時(Audio、Image、Video)需要對Android的媒體提供者(MediaProvider)做詳細的分析,下面記錄一下我的收穫:

[b]一、獲取MediaProvider:[/b]
該工程在系統源碼的packages\providers目錄下,提出並導入Eclipse,便於閱讀;

[img]http://dl2.iteye.com/upload/attachment/0102/7132/57dc6008-9fda-3511-af66-c77e326b1389.png[/img]

圖中可見都很多報錯的,是滴,因爲需要一些系統標準sdk之外的接口,不過不影響我們閱讀代碼。

[b]二、工程結構及內部關係:[/b]
可以從上圖看出包含4個文件:
MediaScannerService.Java:媒體服務,配合廣播實現媒體掃描類的實例化,數據庫的初始化等工作,也向外提供接口;
MediaScannerReceiver.Java:一個廣播接收器,用於接受系統發給媒體服務的廣播並啓動媒體服務;
MediaProvider.Java:媒體數據庫的封裝類,代碼量比較大(四千多行),功能比較複雜,但總的來說就是創建數據庫,對外提供URI以實現對數據庫的增刪改查功能;
MediaThumbRequest.Java:媒體文件縮略圖請求類,與MediaProvider配合使用;
上一個關係圖更直觀一些:

[img]http://dl2.iteye.com/upload/attachment/0102/7136/2f8b730f-1158-3571-bf74-e523b41e9657.png[/img]

上圖不是標準的類圖,只是爲了梳理邏輯關係畫的結構圖。
MediaProvider所處的位置及作用見圖中紅色框中的內容;
上圖還包括其他內容:
1、App層:audio、image、video如何與媒體庫進行交互;
2、框架層(android.media包下):如何實現媒體的掃描;
3、Native層:如何實現正在的媒體文件解析;
4、資源存儲層:sd卡、U盤等介質,DTCM存儲縮略圖;

[b]三、類詳解[/b]

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中使用系統媒體庫;
媒體文件掃描、解析是如何實現的;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章