在做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中使用系統媒體庫;
媒體文件掃描、解析是如何實現的;
見:
更多Android相關信息見Android 專題頁面 http://www.linuxidc.com/topicnews.aspx?tid=11