MediaScanner 分析
一 MediaScannerService
多媒體掃描是從 MediaScannerService 開始的。這是一個單獨(dú)的 package 。位于
packages\providers\MediaProvider :含以下 java 文件
l MediaProvider.java
l MediaScannerReceiver.java
l MediaScannerService.java
l MediaThumbRequest.java
分析這個目錄的 Android.mk 文件,發(fā)現(xiàn)它運(yùn)行的進(jìn)程名字就是 android.process.media 。
application android:process = android.process.media
這個類從 BroadcastReceiver 中派生,用來接收任務(wù)的。
MediaScannerReceiver extends BroadcastReceiver
在它重載的onRecieve 函數(shù)內(nèi)有以下幾種走向:
if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
// 收到” 啟動完畢“廣播后,掃描內(nèi)部存儲
scan(context, MediaProvider.INTERNAL_VOLUME);
} else {
……….
if (action.equals(Intent.ACTION_MEDIA_MOUNTED) &&
externalStoragePath.equals(path)) {
/ 收到MOUNT 信息后,掃描外部存儲
scan(context, MediaProvider.EXTERNAL_VOLUME);
}
else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) &&
path != null && path.startsWith(externalStoragePath + "/")) {
// 收到請求要求掃描某個文件,注意不會掃描內(nèi)部存儲上的文件
scanFile(context, path);
…………………………..
}
…… 下面是它調(diào)用的scan 函數(shù):
scan(Context context, String volume)
Bundle args = new Bundle();
args.putString("volume", volume);
// 直接啟動MediaScannerService 了,
context.startService(
new Intent(context, MediaScannerService.class).putExtras(args));
總結(jié):
MediaScannerReceiver 是用來接收任務(wù)的,它收到廣播后,會啟動 MediaService 進(jìn)行掃描工作。
下面看看 MediaScannerService.
MSS 標(biāo)準(zhǔn)的從 Service 中派生下來,
MediaScannerService extends Service implements Runnable
// 注意:是一個Runnable… ,可能有線程之類的東西存在
下面從 Service 的生命周期的角度來看看它的工作。
1. onCreate
public void onCreate()
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
// 獲得電源鎖,防止在掃描過程中休眠
// 單獨(dú)搞一個線程去跑掃描工作,防止ANR
Thread thr = new Thread(null, this, "MediaScannerService");
thr.start();
2. onStartCommand
@Override
public int onStartCommand(Intent intent, int flags, int startId)
{
// 注意這個handler ,是在另外一個線程中創(chuàng)建的,往這個handler 里sendMessage
// 都會在那個線程里邊處理
// 不明白的可以去查看handler 和Looper 機(jī)制
// 這里就是同步機(jī)制,等待mServiceHandler 在另外那個線程創(chuàng)建完畢
while (mServiceHandler == null) {
synchronized (this) {
try {
wait(100);
} catch (InterruptedException e) {
}
}
}
if (intent == null) {
Log.e(TAG, "Intent is null in onStartCommand: ",
new NullPointerException());
return Service.START_NOT_STICKY;
}
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent.getExtras();
// 把MediaScannerReceiver 發(fā)出的消息傳遞到另外那個線程去處理。
mServiceHandler.sendMessage(msg);
………….
基本上 MSR(MediaScannerReceiver) 發(fā)出的請求都會傳到 onStartCommand 中處理。如果有多個存儲的話,也只能一個一個掃描了。
下面看看那個線程的主函數(shù)
3. run
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 和handler 的實(shí)現(xiàn)
Looper.prepare();// 把這個looper 對象設(shè)置到線程本地存儲
mServiceLooper = Looper.myLooper();
mServiceHandler = new ServiceHandler();// 創(chuàng)建handler ,默認(rèn)會把這個looper
// 的消息隊(duì)列賦值給handler 的消息隊(duì)列,這樣往handler 中發(fā)送消息就是往這個線程的looper 發(fā)
Looper.loop();// 消息循環(huán),內(nèi)部會處理消息隊(duì)列中的消息
// 也就是handleMessage 函數(shù)
}
上面 handler 中加入了一個掃描請求(假設(shè)是外部存儲的),所以要分析 handleMessage 函數(shù)。
4. handleMessage
private final class ServiceHandler extends Handler
{
@Override
public void handleMessage(Message msg)
{
Bundle arguments = (Bundle) msg.obj;
String filePath = arguments.getString("filepath");
try {
……… 這里不講了
} else {
String volume = arguments.getString("volume");
String[] directories = null;
if (MediaProvider.INTERNAL_VOLUME.equals(volume)) {
// 是掃描內(nèi)部存儲的請求?
// scan internal media storage
directories = new String[] {
Environment.getRootDirectory() + "/media",
};
}
else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {
// 是掃描外部存儲的請求? 獲取外部存儲的路徑
directories = new String[] {
Environment.getExternalStorageDirectory().getPath(),
};
}
if (directories != null) {
// 真正的掃描開始了,上面只不過是把存儲路徑取出來罷了.
scan(directories, volume);
…..
// 掃描完了,就把service 停止了
stopSelf(msg.arg1);
}
};
5. scan 函數(shù)
private void scan(String[] directories, String volumeName) {
mWakeLock.acquire();
// 下面這三句話很深奧…
// 從 getContentResolver 獲得一個ContentResover ,然后直接插入
// 根據(jù)AIDL ,這個ContentResover 的另一端是MediaProvider 。只要去看看它的
//insert 函數(shù)就可以了
// 反正這里知道獲得了一個掃描URI 即可。
ContentValues values = new ContentValues();
values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);
Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values);
Uri uri = Uri.parse("file://" + directories[0]);
// 發(fā)送廣播,通知掃描開始了
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));
try {
if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {
openDatabase(volumeName);
}
// 創(chuàng)建真正的掃描器
MediaScanner scanner = createMediaScanner();
// 交給掃描器去掃描文件夾 scanDirectories
scanner.scanDirectories(directories, volumeName);
} catch (Exception e) {
Log.e(TAG, "exception in MediaScanner.scan()", e);
}
// 刪除掃描路徑
getContentResolver().delete(scanUri, null, null);
// 通知掃描完畢
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));
mWakeLock.release();
}
說說上面那個深奧的地方,在 MediaProvider 中重載了 insert 函數(shù), insert 函數(shù)會調(diào)用 insertInternal 函數(shù)。
如下:
private Uri insertInternal(Uri uri, ContentValues initialValues) {
long rowId;
int match = URI_MATCHER.match(uri);
// handle MEDIA_SCANNER before calling getDatabaseForUri()
// 剛才那個insert 只會走下面這個分支,其實(shí)就是獲得一個地址….
// 太繞了?。。。。?/span>
if (match == MEDIA_SCANNER) {
mMediaScannerVolume = initialValues.getAsString(MediaStore.MEDIA_SCANNER_VOLUME);
return MediaStore.getMediaScannerUri();
}
……..
再看看它創(chuàng)建了什么樣的 Scanner ,這就是 MSS 中的 createMediaScanner
private MediaScanner createMediaScanner() {
// 下面這個MediaScanner 在framework/base/ 中,待會再分析
MediaScanner scanner = new MediaScanner(this);
// 設(shè)置當(dāng)前的區(qū)域,這個和字符編碼有重大關(guān)系。
Locale locale = getResources().getConfiguration().locale;
if (locale != null) {
String language = locale.getLanguage();
String country = locale.getCountry();
String localeString = null;
if (language != null) {
if (country != null) {
// 給掃描器設(shè)置當(dāng)前國家和語言。
scanner.setLocale(language + "_" + country);
} else {
scanner.setLocale(language);
}
}
}
return scanner;
}
至此, MSS 的任務(wù)完成了。接下來是 MediaScanner 的工作了。
6. 總結(jié)
MSS 的工作流程如下:
l 1 單獨(dú)啟動一個帶消息循環(huán)的工作線程。
l 2 主線程接收系統(tǒng)發(fā)來的任務(wù),然后發(fā)送給工作線程去處理。
l 3 工作線程接收任務(wù),創(chuàng)建一個 MediaScanner 去掃描。
l 4 MSS 順帶廣播一下掃描工作啟動了,掃描工作完畢了。
二 MediaScanner
MediaScanner 位置在
frameworks\base\media\ 下,包括 jni 和 java 文件。
先看看 java 實(shí)現(xiàn)。
這個類巨復(fù)雜,而且和 MediaProvider 交互頻繁。在分析的時候要時刻回到 MediaProvider 去看看。
1. 初始化
public class MediaScanner
{
static {
//libmedia_jni.so 的加載是在MediaScanner 類中完成的
// 這么重要的so 為何放在如此不起眼的地方加載???
System.loadLibrary("media_jni");
native_init();
}
public MediaScanner(Context c) {
native_setup();// 調(diào)用jni 層的初始化,暫時不用看了,無非就是一些
// 初始化工作,待會在再進(jìn)去看看
……..
}
剛才 MSS 中是調(diào)用 scanDirectories 函數(shù),我們看看這個。
2. scanDirectories
public void scanDirectories(String[] directories, String volumeName) {
try {
long start = System.currentTimeMillis();
initialize(volumeName);// 初始化
prescan(null);// 掃描前的預(yù)處理
long prescan = System.currentTimeMillis();
for (int i = 0; i < directories.length; i++) {
// 掃描文件夾,這里有一個很重要的參數(shù) mClient
// processDirectory 是一個native 函數(shù)
processDirectory(directories[i], MediaFile.sFileExtensions, mClient);
}
long scan = System.currentTimeMillis();
postscan(directories);// 掃描后處理
long end = System.currentTimeMillis();
….. 打印時間,異常處理… 沒了…
下面簡單講講 initialize , preScan 和 postScan 都干嘛了。
private void initialize(String volumeName) {
// 打開MediaProvider ,獲得它的一個實(shí)例
mMediaProvider = mContext.getContentResolver().acquireProvider("media");
// 得到一些uri
mAudioUri = Audio.Media.getContentUri(volumeName);
mVideoUri = Video.Media.getContentUri(volumeName);
mImagesUri = Images.Media.getContentUri(volumeName);
mThumbsUri = Images.Thumbnails.getContentUri(volumeName);
// 外部存儲的話,可以支持播放列表之類的東西,搞了一些個緩存池之類的
// 如mGenreCache 等
if (!volumeName.equals("internal")) {
// we only support playlists on external media
mProcessPlaylists = true;
mGenreCache = new HashMap<String, Uri>();
…
preScan ,這個函數(shù)很復(fù)雜:
大概就是創(chuàng)建一個 FileCache ,用來緩存掃描文件的一些信息,例如 last_modified 等。這個 FileCache 是從 MediaProvider 中已有信息構(gòu)建出來的,也就是歷史信息。后面根據(jù)掃描得到的新信息來對應(yīng)更新歷史信息。
postScan, 這個函數(shù)做一些清除工作,例如以前有 video 生成了一些縮略圖,現(xiàn)在 video 文件被干掉了,則對應(yīng)的縮略圖也要被干掉。
另外還有一個 mClient ,這個是從 MediaScannerClient 派生下來的一個東西,里邊保存了一個文件的一些信息。后續(xù)再分析。
剛才說到,具體掃描工作是在 processDirectory 函數(shù)中完成的。這個是一個 native 函數(shù)。
在 frameworks\base\media\jni\android_media_MediaScanner.cpp 中。
三 MediaScanner JNI 層分析
MediaScanner JNI 層內(nèi)容比較多,單獨(dú)搞一節(jié)分析吧。
先看看 android_media_MediaScanner 這個文件。
1. native_init 函數(shù), jni 對應(yīng)的函數(shù)如下
static void
android_media_MediaScanner_native_init(JNIEnv *env)
{
jclass clazz;
clazz = env->FindClass("android/media/MediaScanner");
// 得都JAVA 類中mNativeContext 這個成員id
fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
// 不熟悉JNI 的自己去學(xué)習(xí)下吧
}
3. native_setup 函數(shù), jni 對應(yīng)函數(shù)如下:
android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz)
{
// 創(chuàng)建MediaScanner 對象
MediaScanner *mp = createMediaScanner();
// 太變態(tài)了,自己不保存這個對象指針.
// 卻把它設(shè)置到java 對象的mNativeContext 去保存
env->SetIntField(thiz, fields.context, (int)mp);
}
// 創(chuàng)建MediaScanner 函數(shù)
static MediaScanner *createMediaScanner() {
#if BUILD_WITH_FULL_STAGEFRIGHT
..
// 使用google 自己的
return new StagefrightMediaScanner;
#endif
#ifndef NO_OPENCORE
// 使用opencore 提供的
….
return new PVMediaScanner();
#endif
4. processDirectories 函數(shù), jni 對應(yīng)如下:
android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, jobject client)
{
MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context);
// 每次都要回調(diào)到JAVA 中去取這個Scanner ?。?/span>
………
const char *pathStr = env->GetStringUTFChars(path, NULL);
const char *extensionsStr = env->GetStringUTFChars(extensions, NULL);
…….
// 又在C++ 這里搞一個client ,然后把java 的client 放到C++Client 中去保存
<>