http://www.apkbus.com/android-5376-1-1.html
MediaScanner分析 一 MediaScannerService 多媒體掃描是從MediaScannerService開始的。這是一個(gè)單獨(dú)的package。位于packagesprovidersMediaProvider:含以下java文件
java代碼:- MediaProvider.java
- MediaScannerReceiver.java
- MediaScannerService.java
- MediaThumbRequest.java
復(fù)制代碼
分析這個(gè)目錄的Android.mk文件,發(fā)現(xiàn)它運(yùn)行的進(jìn)程名字就是android.process.media。
application android:process=android.process.media
1.1 MediaScannerReceiver 這個(gè)類從BroadcastReceiver中派生,用來接收任務(wù)的。MediaScannerReceiver extends BroadcastReceiver
在它重載的onRecieve函數(shù)內(nèi)有以下幾種走向:
java代碼:- if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
- // 收到”啟動(dòng)完畢“廣播后,掃描內(nèi)部存儲(chǔ)
- scan(context, MediaProvider.INTERNAL_VOLUME);
- } else {
- if (action.equals(Intent.ACTION_MEDIA_MOUNTED) &&
- externalStoragePath.equals(path)) {
- /收到MOUNT信息后,掃描外部存儲(chǔ)
- scan(context, MediaProvider.EXTERNAL_VOLUME);
- }
- else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) &&
- path != null && path.startsWith(externalStoragePath + "/")) {
- //收到請(qǐng)求要求掃描某個(gè)文件,注意不會(huì)掃描內(nèi)部存儲(chǔ)上的文件
- scanFile(context, path);
- }
復(fù)制代碼下面是它調(diào)用的scan函數(shù):
java代碼:- scan(Context context, String volume)
- Bundle args = new Bundle();
- args.putString("volume", volume);
- //直接啟動(dòng)MediaScannerService了,
- context.startService(new Intent(context, MediaScannerService.class).putExtras(args));
復(fù)制代碼
總結(jié): MediaScannerReceiver是用來接收任務(wù)的,它收到廣播后,會(huì)啟動(dòng)MediaService進(jìn)行掃描工作。
下面看看MediaScannerService.
1.2 MediaScannerService MSS標(biāo)準(zhǔn)的從Service中派生下來,
MediaScannerService extends Service implements Runnable
//注意:是一個(gè)Runnable…,可能有線程之類的東西存在
下面從Service的生命周期的角度來看看它的工作。
1. onCreate
java代碼:- public void onCreate()
- PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
- //獲得電源鎖,防止在掃描過程中休眠
- //單獨(dú)搞一個(gè)線程去跑掃描工作,防止ANR
- Thread thr = new Thread(null, this, "MediaScannerService");
- thr.start();
復(fù)制代碼 2. onStartCommandjava代碼:- @Override
- public int onStartCommand(Intent intent, int flags, int startId){
- //注意這個(gè)handler,是在另外一個(gè)線程中創(chuàng)建的,往這個(gè)handler里sendMessage
- //都會(huì)在那個(gè)線程里邊處理
- //不明白的可以去查看handler和Looper機(jī)制
- //這里就是同步機(jī)制,等待mServiceHandler在另外那個(gè)線程創(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ā)出的消息傳遞到另外那個(gè)線程去處理。
- mServiceHandler.sendMessage(msg);
復(fù)制代碼 3. runjava代碼:- 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();
- //把這個(gè)looper對(duì)象設(shè)置到線程本地存儲(chǔ)
- mServiceLooper = Looper.myLooper();
- mServiceHandler = new ServiceHandler();
- //創(chuàng)建handler,默認(rèn)會(huì)把這個(gè)looper
- //的消息隊(duì)列賦值給handler的消息隊(duì)列,這樣往handler中發(fā)送消息就是往這個(gè)線程的looper發(fā)
- Looper.loop();
- //消息循環(huán),內(nèi)部會(huì)處理消息隊(duì)列中的消息
- //也就是handleMessage函數(shù)
- }
復(fù)制代碼
上面handler中加入了一個(gè)掃描請(qǐng)求(假設(shè)是外部存儲(chǔ)的),所以要分析handleMessage函數(shù)。
4. handleMessagejava代碼:- 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)部存儲(chǔ)的請(qǐng)求?
- // scan internal media storage
- directories = new String[] {
- Environment.getRootDirectory() + "/media",
- };
- }
- else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {
- //是掃描外部存儲(chǔ)的請(qǐng)求?獲取外部存儲(chǔ)的路徑
- directories = new String[] {
- Environment.getExternalStorageDirectory().getPath(),
- };
- }
- if (directories != null) {
- //真正的掃描開始了,上面只不過是把存儲(chǔ)路徑取出來罷了.
- scan(directories, volume);
- //掃描完了,就把service停止了
- stopSelf(msg.arg1);
- }
- };
復(fù)制代碼
5. scan函數(shù)java代碼:- private void scan(String[] directories, String volumeName) {
- mWakeLock.acquire();
- //下面這三句話很深?yuàn)W…
- //從 getContentResolver獲得一個(gè)ContentResover,然后直接插入
- //根據(jù)AIDL,這個(gè)ContentResover的另一端是MediaProvider。只要去看看它的
- //insert函數(shù)就可以了
- //反正這里知道獲得了一個(gè)掃描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();
- }
復(fù)制代碼 說說上面那個(gè)深?yuàn)W的地方,在MediaProvider中重載了insert函數(shù),insert函數(shù)會(huì)調(diào)用insertInternal函數(shù)。
如下:
java代碼:- private Uri insertInternal(Uri uri, ContentValues initialValues) {
- long rowId;
- int match = URI_MATCHER.match(uri);
- // handle MEDIA_SCANNER before calling getDatabaseForUri()
- //剛才那個(gè)insert只會(huì)走下面這個(gè)分支,其實(shí)就是獲得一個(gè)地址….
- //太繞了
- if (match == MEDIA_SCANNER) {
- mMediaScannerVolume = initialValues.getAsString(MediaStore.MEDIA_SCANNER_VOLUME);
- return MediaStore.getMediaScannerUri();
- }
復(fù)制代碼 再看看它創(chuàng)建了什么樣的Scanner,這就是MSS中的createMediaScanner
java代碼:- private MediaScanner createMediaScanner() {
- //下面這個(gè)MediaScanner在framework/base/中,待會(huì)再分析
- MediaScanner scanner = new MediaScanner(this);
- //設(shè)置當(dāng)前的區(qū)域,這個(gè)和字符編碼有重大關(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)前國(guó)家和語言。
- scanner.setLocale(language + "_" + country);
- } else {
- scanner.setLocale(language);
- }
- }
- }
- return scanner;
- }
復(fù)制代碼 至此,MSS的任務(wù)完成了。接下來是MediaScanner的工作了。
6. 總結(jié) MSS的工作流程如下:
1 單獨(dú)啟動(dòng)一個(gè)帶消息循環(huán)的工作線程。
2 主線程接收系統(tǒng)發(fā)來的任務(wù),然后發(fā)送給工作線程去處理。
3 工作線程接收任務(wù),創(chuàng)建一個(gè)MediaScanner去掃描。
4 MSS順帶廣播一下掃描工作啟動(dòng)了,掃描工作完畢了。
二 MediaScanner MediaScanner位置在
frameworks asemedia下,包括jni和java文件。
先看看java實(shí)現(xiàn)。
這個(gè)類巨復(fù)雜,而且和MediaProvider交互頻繁。在分析的時(shí)候要時(shí)刻回到MediaProvider去看看。
1. 初始化
java代碼:- public class MediaScanner{
- static {
- //libmedia_jni.so的加載是在MediaScanner類中完成的
- //這么重要的so為何放在如此不起眼的地方加載???
- System.loadLibrary("media_jni");
- native_init();
- }
- public MediaScanner(Context c) {
- native_setup();//調(diào)用jni層的初始化,暫時(shí)不用看了,無非就是一些
- //初始化工作,待會(huì)在再進(jìn)去看看
- }
復(fù)制代碼
剛才MSS中是調(diào)用scanDirectories函數(shù),我們看看這個(gè)。
2. scanDirectories
java代碼:- 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++) {
- //掃描文件夾,這里有一個(gè)很重要的參數(shù) mClient
- // processDirectory是一個(gè)native函數(shù)
- processDirectory(directories[i], MediaFile.sFileExtensions, mClient);
- }
- long scan = System.currentTimeMillis();
- postscan(directories);//掃描后處理
- long end = System.currentTimeMillis();
- 打印時(shí)間,異常處理沒了
- 下面簡(jiǎn)單講講initialize ,preScan和postScan都干嘛了。
- private void initialize(String volumeName) {
- //打開MediaProvider,獲得它的一個(gè)實(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);
- //外部存儲(chǔ)的話,可以支持播放列表之類的東西,搞了一些個(gè)緩存池之類的
- //如mGenreCache等
- if (!volumeName.equals("internal")) {
- // we only support playlists on external media
- mProcessPlaylists = true;
- mGenreCache = new HashMap();
preScan,這個(gè)函數(shù)很復(fù)雜: 大概就是創(chuàng)建一個(gè)FileCache,用來緩存掃描文件的一些信息,例如last_modified等。這個(gè)FileCache是從MediaProvider中已有信息構(gòu)建出來的,也就是歷史信息。后面根據(jù)掃描得到的新信息來對(duì)應(yīng)更新歷史信息。
postScan,這個(gè)函數(shù)做一些清除工作,例如以前有video生成了一些縮略圖,現(xiàn)在video文件被干掉了,則對(duì)應(yīng)的縮略圖也要被干掉。
另外還有一個(gè)mClient,這個(gè)是從MediaScannerClient派生下來的一個(gè)東西,里邊保存了一個(gè)文件的一些信息。后續(xù)再分析。
在frameworks asemediajniandroid_media_MediaScanner.cpp中。
剛才說到,具體掃描工作是在processDirectory函數(shù)中完成的。這個(gè)是一個(gè)native函數(shù)。
三 MediaScanner JNI層分析 MediaScanner JNI層內(nèi)容比較多,單獨(dú)搞一節(jié)分析吧。先看看android_media_MediaScanner這個(gè)文件。
1. native_init函數(shù),jni對(duì)應(yīng)的函數(shù)如下
java代碼:- static void
- android_media_MediaScanner_native_init(JNIEnv *env){
- jclass clazz;
- clazz = env->FindClass("android/media/MediaScanner");
- //得都JAVA類中mNativeContext這個(gè)成員id
- fields.context = env->GetFieldID(clazz, "mNativeContext", "I");
- //不熟悉JNI的自己去學(xué)習(xí)下吧
- }
復(fù)制代碼 2. native_setup函數(shù),jni對(duì)應(yīng)函數(shù)如下:
java代碼:- android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz){
- //創(chuàng)建MediaScanner對(duì)象
- MediaScanner *mp = createMediaScanner();
- //太變態(tài)了,自己不保存這個(gè)對(duì)象指針.
- //卻把它設(shè)置到j(luò)ava對(duì)象的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();
復(fù)制代碼
3. processDirectories函數(shù),jni對(duì)應(yīng)如下:
java代碼:- 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中去取這個(gè)Scanner!!
- const char *pathStr = env->GetStringUTFChars(path, NULL);
- const char *extensionsStr = env->GetStringUTFChars(extensions, NULL);
- //又在C++這里搞一個(gè)client,然后把java的client放到C++Client中去保存
- //而且還是棧上的臨時(shí)變量..
- MyMediaScannerClient myClient(env, client);
- //scanner掃描文件夾,用得是C++的client
- mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env);
- env->ReleaseStringUTFChars(path, pathStr);
- env->ReleaseStringUTFChars(extensions, extensionsStr);
- }
復(fù)制代碼
到這里似乎就沒有了,那么掃描后的數(shù)據(jù)庫(kù)是怎么更新的呢?為什么要傳入一個(gè)client進(jìn)去呢?看來必須得trace到scanner中去才知道了
四 PVMediaScanner 這個(gè)在externalopencoreandroidmediascanner.cpp中。
1. processDirectoryjava代碼:- status_t MediaScanner::processDirectory(const char *path, const char* extensions,
- MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv)
- {
- InitializeForThread();
- int error = 0;
- status_t result = PVMFSuccess;
- //調(diào)用client的設(shè)置區(qū)域函數(shù)
- client.setLocale(mLocale);
- //掃描文件夾,咋還沒開始??
- result = doProcessDirectory(pathBuffer, pathRemaining, extensions, client, exceptionCheck, exceptionEnv);
復(fù)制代碼
2. doProcessDirectory
java代碼:- status_t MediaScanner::doProcessDirectory(char *path, int pathRemaining, const char* extensions,
- MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv)
- {
- //終于看到點(diǎn)希望了
- //打開這個(gè)文件夾,枚舉其中的內(nèi)容。
- //題外話,這個(gè)時(shí)候FileManager肯定刪不掉這個(gè)文件夾!!
- DIR* dir = opendir(path);
- while ((entry = readdir(dir))) {
- const char* name = entry->d_name;
- //不處理.和..文件夾
- if (isDirectory) {
- //不處理.開頭的文件夾。如果是文件夾,遞歸調(diào)用doProcessDirectory
- //深度優(yōu)先啊!
- int err = doProcessDirectory(path, pathRemaining - nameLength - 1, extensions, client, exceptionCheck, exceptionEnv);
- if (err) {
- LOGE("Error processing '%s' - skipping ", path);
- continue;
- }
- } else if (fileMatchesExtension(path, extensions)) {
- //是一個(gè)可以處理的文件,交給client處理
- //徹底瘋掉了….這是干嘛呢???
- client.scanFile(path, statbuf.st_mtime, statbuf.st_size);
復(fù)制代碼
這里要解釋下,剛才createMediaScanner中,明明創(chuàng)建的是PVMediaScanner,為何這里看得是MediaScanner代碼呢?
因?yàn)?strong>PVMediaScanner從
MediaScanner中派生下來的,而且沒有重載processDirectory函數(shù)Eclaire沒有PVMediaScanner這樣的東西,估計(jì)是froyo又改了點(diǎn)啥吧。
FT,
processDirctory無非是列舉一個(gè)目錄內(nèi)容,然后又反回去調(diào)用client的scanFile處理了。為何搞這么復(fù)雜?只有回去看看C++的client干什么了。
MediaScannerClient---JNI層 JNI中的這個(gè)類是這樣的:
java代碼:- class MyMediaScannerClient : public MediaScannerClient
- //這是它的scanFile實(shí)現(xiàn)
- virtual bool scanFile(const char* path, long long lastModified, long long fileSize){
- //再次崩潰了,C++的client調(diào)用了剛才傳進(jìn)去的java Client的
- //scanFile函數(shù)…不過這次還傳進(jìn)去了該文件的路徑,最后修改時(shí)間和文件大小。
- mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);
- //想死,,,
- }
沒辦法了,只能再去看看MediaScanner.java傳進(jìn)去的那個(gè)client了。
MediaScannerClient----JAVA層 這個(gè)類在MediaScanner.java中實(shí)現(xiàn)。
java代碼:- private class MyMediaScannerClient implements MediaScannerClient:
- public void scanFile(String path, long lastModified, long fileSize) {
- //調(diào)用doScanFile..很煩..
- doScanFile(path, null, lastModified, fileSize, false);
- //下面是doScanFile
- public Uri doScanFile(String path, String mimeType, long lastModified, long fileSize, boolean scanAlways) {
- //預(yù)處理,看看之前創(chuàng)建的文件緩存中有沒有這個(gè)文件
- FileCacheEntry entry = beginFile(path, mimeType, lastModified, fileSize);
- // rescan for metadata if file was modified since last scan
- if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
- //如果事先有這個(gè)文件的信息,則需要修改一些信息,如長(zhǎng)度,最后修改時(shí)間等
- //真正的掃描文件
- processFile(path, mimeType, this);
- //掃描完了,做最后處理
- endFile(entry, ringtones, notifications, alarms, music, podcasts);
- //processFile又是jni層的。
- //對(duì)應(yīng)android_media_MediaScanner_processFile函數(shù)
- android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client){
- MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context);
- //無語了,又搞一個(gè) MyMediaScannerClient
- MyMediaScannerClient myClient(env, client);
- mp->processFile(pathStr, mimeTypeStr, myClient);
- }