Android MediaScanner:(三)MediaScannerService
對MediaScannerService的類結(jié)構(gòu)進行靜態(tài)分析,對創(chuàng)建時和啟動時的工作進行動態(tài)分析,分析過程中來看MediaScannerService如何處理MediaScannerReceiver所接收到的各種掃描請求。
本文是筆者的分析歸納,并用UML圖(ClassDiagram/Sequence Diagram)來呈現(xiàn)。雖然來源于對Android源碼的分析,但文中不會占用大量篇幅羅列源碼,所以讀者在閱讀本文時,手頭最好有Android源碼,結(jié)合源碼來解讀。本文對MediaScannerService的類結(jié)構(gòu)進行靜態(tài)分析,對創(chuàng)建時和啟動時的工作進行動態(tài)分析,分析過程中來看MediaScannerService如何處理MediaScannerReceiver所接收到的各種掃描請求。
一、MediaScannerService的靜態(tài)結(jié)構(gòu)分析

- MediaScannerService是一個Service,并實現(xiàn)Runnable,實現(xiàn)工作線程。
- MediaScannerService通過ServiceHandler這個Handler把主線程需要大量計算的工作放到工作線程中去做。
在Runnable.run()中執(zhí)行消息循環(huán),把通過Handler發(fā)送過來的消息在工作線程中執(zhí)行。
二、MediaScannerService的動態(tài)分析
在MediaScannerService被通過startService啟動的過程中,其實包含了創(chuàng)建時的工作。下面分別分析創(chuàng)建時和啟動時所完成的工作。
2.1 創(chuàng)建之時#0nCreate()
Service對象在被創(chuàng)建的時候,onCreate()會被調(diào)用,看一下MediaScannerService的onCreate()里面做了什么:
- PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
- mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,TAG);
-
- Thread thr = new Thread(null, this, “MediaScannerService”);
- Thr.start();
PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,TAG);
Thread thr = new Thread(null, this, “MediaScannerService”);
Thr.start();
MediaScannerService創(chuàng)建就是為了掃描Media的,這一過程是非常費時費力的。所以:
- 為了防止在媒體掃描過程中,CPU睡死過去,用PowerManager的WakeLock告訴PowerManager,我這邊還在忙,別睡死了[Line#1,2];
- 在Android的主線程中要快速返回,大量的計算任務(wù)交給工作線程去做,這里啟了一個工作線程,而這個線程的執(zhí)行體就是MediaScannerService所實現(xiàn)Runnable的run()方法,用Handler發(fā)消息之前,一定要先啟動該線程的[Line#4,5]。
2.2 Service啟動時#onStartCommand()
Service對象在被啟動的時候,onStartCommand()會被調(diào)用,看一下MediaScannerService的onStartCommand()里面做了什么。
下圖是MediaScannerService#onStartCommand()中完成的工作:

三、小結(jié)
本文對MediaScannerService的類結(jié)構(gòu)進行了靜態(tài)分析,對創(chuàng)建時和啟動時的工作進行了動態(tài)分析,分析過程中看MediaScannerService如何響應(yīng)MediaScannerReceiver所接收到的各種掃描請求。
與其他文章的關(guān)系:
Android MediaScanner:(四)MediaScanner之scanSingleFile
本文從MediaScannerService的scanFile入口開始,詳細分析MediaScanner和MediaScannerClient對單個媒體文件的掃描處理過程。
本文分析MediaScanner對單個文件的掃描過程。單個文件的掃描是MediaScanner的基礎(chǔ),對路徑的掃描也要用到對Media文件的掃描。本文從MediaScannerService的scanFile入口開始,詳細分析了MediaScanner和MediaScannerClient對單個媒體文件的掃描處理過程。
一、MediaScannerService.scanFile()
上文對MediaScannerService的分析,知道對單個文件的掃描是調(diào)用MediaScannerService.scanFile()完成的。下面看scanFile()的實現(xiàn):

scanFile()中判斷如果是外部媒體文件(只掃描外部媒體文件),創(chuàng)建MediaScanner(定義在frameworks/base/media/java/android/media)實例,并設(shè)置locale信息,然后調(diào)用MediaScanner的scanSingleFile()開始掃描。
二、MediaScanner.scanSingleFile()
MediaScanner.scanSingleFile()是具體的實現(xiàn)??此瓿傻墓ぷ鳎?/p>

順序執(zhí)行了
- initialize(); 3.1節(jié)中講解該方法的工作;
- prescan(); 3.2節(jié)中講解該方法的工作;
- MyMediaScannerClient.doScanFile() 3.3節(jié)中講解該方法的工作。
下面分章節(jié)著重講解這些方法里面都做了哪些工作。
三、MediaScanner
位于/framework/base/media/java/android/media/。
3.1 MediaScanner.initialize(volumeName: String)
initialize()對MediaScanner的MediaProvider/Audio/Video/Image等媒體庫的URI進行初始化獲取,要獲取的屬性有下面這些:

另外,如果掃描的是外部volume,要處理playlist和genre,所以mProcessPlaylists和mProcessGenres被設(shè)置為true;創(chuàng)建mGenreCache;獲取Genres和playlists的URI。
3.2 MediaScanner.prescan()
- 從Audio、Video和Image各自的數(shù)據(jù)庫中分別讀取出這些媒體文件信息(ID,Path, Modified time),寫入mFileCache: HashMap<String,FileCacheEntry>中;
- 從Playlist數(shù)據(jù)庫中讀取出播放列表文件信息(ID,Path, Modified time),寫入mFileCache: HashMap<String,FileCacheEntry>中;
注意:新創(chuàng)建的FileCacheEntry的mSeenInFileSystem缺省值為false。
3.3 MyMediaScannerClient.doScanFile()
MyMediaScannerClient是MediaScanner的內(nèi)部類,實現(xiàn)了MediaScannerClient。

MyMediaScannerClient提供了doScanFile()方法供外部調(diào)用。另外實現(xiàn)了MediaScannerClient這個Interface,這個Interface在JNI實現(xiàn)中非常重要,講到那里時再詳細闡述。
- doScanFile()中調(diào)用beginFile(),獲取FileType和MimeType信息,并初始化內(nèi)部結(jié)構(gòu);
- 如果返回了FileCacheEntry,并且不是Image文件,調(diào)用JNI方法processFile()解析到具體的文件信息在MyMediaScannerClient中;
- 調(diào)用endFile()做后續(xù)的媒體庫更新等處理。
3.3.1 beginFile()
參數(shù):path:String; mimeType: String; lastModified: long, fileSize: long
- 用MediaFile獲取FileType和MimeType,并分別賦值給mFileType和mMimeType;
- 從mFileCache中獲?。ㄎ募呀?jīng)在數(shù)據(jù)庫中了:3.2preScan()中已經(jīng)把數(shù)據(jù)庫中的文件都寫入到mFileCache),或生成新的entry:FileCacheEntry,加入到mFileCache中;
- 設(shè)置entry:FileCacheEntry的屬性mSeenInFileSystem為 true【掃描到了該文件】;
- 如果是播放列表文件,加入到mPlaylists:ArrayList<FileCacheEntry>中,并直接返回null;
- 為MyMediaScannerClient中的各個屬性(mArtist/ mAlbum / Artist / mAlbum / mTitle / mComposer / mGenre / mTrack / mYear /mDuration / mWriter / mCompliation)賦初值,為mPath / mLastModified賦值。返回 2)生成的FileCacheEntry的實例。
3.3.2 processFile()
JNI調(diào)用。詳細分析在文//TODO中。
3.3.3 endFile(entry: FileCacheEntry, …)
- 1) 根據(jù)mFileType(Audio/Video/Images),選擇不同的數(shù)據(jù)庫,并把相應(yīng)的URI賦值給entry.mTableUri;
- 2) 寫入values:ContentValues信息
- ①分別用MediaScannerClient中的下面各個屬性值,初始化values:
- MediaStore.MediaColumns.DATA: mPath
- MediaStore.MediaColumns.TITLE: mTitle
- MediaStore.MediaColumns.DATE_MODIFIED: mLastModifed
- MediaStore.MediaColumns.SIZE: mFileSize
- MediaStore.MediaColumns.MIME_TYPE: mMimeType
- 對于Video文件
o MediaStore.MediaColumns.ARTIST: mArtist/MediaStore.UNKNOWN_STRING
o MediaStore.MediaColumns.ALBUM: mAlbum/ MediaStore.UNKNOWN_STRING
o MediaStore.MediaColumns.DURATION: mDuration
o MediaStore.MediaColumns.ARTIST: mArtist/MediaStore.UNKNOWN_STRING
o MediaStore.MediaColumns.ALBUM_ARTIST: mAlbumArtist/MediaStore.UNKNOWN_STRING
o MediaStore.MediaColumns.ALBUM: mAlbum/ MediaStore.UNKNOWN_STRING
o MediaStore.MediaColumns.COMPOSER: mComposer
o MediaStore.MediaColumns.YEAR: mYear
o MediaStore.MediaColumns.TRACK: mTrack
o MediaStore.MediaColumns.DURATION: mDuration
o MediaStore.MediaColumns.COMPILATION: mCompilation
- ②對Title和Album做特殊處理
- 如果Title為空,從MediaStore.MediaColumns.DATA:mPath 中分離出文件名(不含路徑和后綴名),賦到value的MediaStore.MediaColumns.TITLE里;
- 如果Album為MediaStore.UNKNOWN_STRING也就是文件中不含有Album信息,把MediaStore.MediaColumns.DATA:mPath 的父目錄名賦到value的MediaStore.MediaColumns.ALBUM里。
新加入的Audio,寫入下列信息:
- MediaStore.Audio.Media.IS_RINGTONE: ringtones
- MediaStore.Audio.Media.IS_NOTIFICATION: notifications
- MediaStore.Audio.Media.IS_ALARM: alarms
- MediaStore.Audio.Media.IS_MUSIC: music
- MediaStore.Audio.Media.IS_PODCAST: podcasts
對于JPG文件,用android.media.ExifInterface獲得下列信息:
- MediaStore.Images.Media.LATITUDE;
- MediaStore.Images.Media.LONGTITUDE;
- MediaStore.Images.Media.DATE_TAKEN: gps dateTime;
- MediaStore.Images.Media.ORIENTATION。
- 3) 用2)中的values添加或更新1) 中URI指向的Audio/Video/Images數(shù)據(jù)庫。
- 4) 如果要處理Genres(判斷標志mProcessGenres,外部媒體都要處理),
- 用Genre的名字mGenre查詢Genres的數(shù)據(jù)庫,更新或插入一條由把mGenre賦給MediaStore.Audio.Genres.NAME字段組成的記錄到MediaStore.Audio.Genres數(shù)據(jù)庫中,并返回URI;
- 把 {genre, MediaStore.Audio.Genres.Members.CONTENT_DIRECTORY}加入mGenreCache:HashMap<String, Uri>中;
- 把當前文件所在媒體庫中的rowId賦值給MediaStore.Audio.Genres.Members.AUDIO_ID字段,組成一條記錄插入到MediaStore.Audio.Genres.Members數(shù)據(jù)庫中。
更新一個媒體文件所屬的Genre,需要更新Genres數(shù)據(jù)庫,和Genres數(shù)據(jù)庫對應(yīng)的記錄所在的Genres.Members數(shù)據(jù)庫。
- 5) 對notification/ringtone/alarm進行處理
如果是notification/ringtone/alarm,并且還未設(shè)置notification/ringtone/alarm,那么設(shè)置Settings.System的Settings.System.NOTIFICATION_SOUND/ RINGTONE / ALARM_ALERT為媒體數(shù)據(jù)庫的Uri形式。
- 6) 返回媒體數(shù)據(jù)庫的Uri。
|