• 鍍金池/ 教程/ Android/ 第10章 深入理解MediaScanner
    第4章 ?深入理解 Zygote
    第10章 深入理解MediaScanner
    第3章 ?深入理解init
    第8章 ?深入理解Surface系統
    第5章 深入理解常見(jiàn)類(lèi)
    第7章 ?深入理解Audio系統
    第一章 ?閱讀前的準備工作
    <span>第6章 深入理解Binder</span>
    第9章 ?深入理解Vold和Rild
    第2章? 深入理解JNI

    第10章 深入理解MediaScanner

    本章主要內容

    ·? 介紹多媒體系統中媒體文件掃描的工作原理。

    本章涉及的源代碼文件名及位置

    下面是本章分析的源碼文件名及其位置。

    ·? MediaProvider.java

    packages/providers/MediaProvider/MediaProvider.java

    ·? MediaScannerReceiver.java

    packages/providers/MediaProvider/MediaScannerReceiver.java

    ·? MediaScannerService.java

    packages/providers/MediaProvider/MediaScannerService.java

    ·? MediaScanner.java

    framework/base/media/java/com/android/media/MediaScanner.java

    ·? MediaThumbRequest.java

    packages/providers/MediaProvider/MediaThumbRequest.java

    ·? android_media_MediaScanner.cpp

    framework/base/media/jni/android_media_MediaScanner.cpp

    ·? MediaScanner.cpp

    framework/base/media/libmedia/MediaScanner.cpp

    ·? PVMediasScanner.cpp

    external/opencore/android/PVMediasScanner.cpp

    10.1 ?概述

    多媒體系統,是Android平臺中非常龐大的一個(gè)系統。不過(guò)由于篇幅所限,本章只介紹多媒體系統中的重要一員MediaScanner。MediaScanner有什么用呢?可能有些讀者還不是很清楚。MediaScanner和媒體文件掃描有關(guān),例如,在Music應用程序中見(jiàn)到的歌曲專(zhuān)輯名、歌曲時(shí)長(cháng)等信息,都是通過(guò)它掃描對應的歌曲而得到的。另外,通過(guò)MediaStore接口查詢(xún)媒體數據庫,從而得到系統中所有媒體文件的相關(guān)信息也和MediaScanner有關(guān),因為數據庫的內容就是由MediaScanner添加的。所以MediaScanner是多媒體系統中很重要的一部分。

    伴隨著(zhù)Android的成長(cháng),多媒體系統也發(fā)生了非常大的變化。這對開(kāi)發(fā)者來(lái)說(shuō),一個(gè)非常好的消息,就是從Android 2.3開(kāi)始那個(gè)令人極度郁悶的OpenCore,終于有被干掉的可能了。從此,也迎來(lái)了Stagefright時(shí)代。但Android 2.2在很長(cháng)一段時(shí)間內還會(huì )存在,所以希望以后能有機會(huì )深入地剖析這個(gè)OpenCore。

    下面,就來(lái)分析媒體文件掃描的工作原理。

    10.2 ?android.process.media的分析

    多媒體系統的媒體掃描功能,是通過(guò)一個(gè)APK應用程序提供的,它位于package/providers/MediaProvider目錄下。通過(guò)分析APK的Android.mk文件可知,該APK運行時(shí)指定了一個(gè)進(jìn)程名,如下所示:

    application android:process=android.process.media

    原來(lái),通過(guò)ps命令經(jīng)??吹降倪M(jìn)程就是它??!另外,從這個(gè)APK程序所處的package\providers目錄也可知道,它還是一個(gè)ContentProvider。事實(shí)上從Android應用程序的四大組件來(lái)看,它使用了其中的三個(gè)組件:

    ·? MediaScannerService(從Service派生)模塊負責掃描媒體文件,然后將掃描得到的信息插入到媒體數據庫中。

    ·? MediaProvider(從ContentProvider派生)模塊負責處理針對這些媒體文件的數據庫操作請求,例如查詢(xún)、刪除、更新等。

    ·? MediaScannerReceiver(從BroadcastReceiver派生)模塊負責接收外界發(fā)來(lái)的掃描請求。也就是MS對外提供的接口。

    除了支持通過(guò)廣播發(fā)送掃描請求外,MediaScannerService也支持利用Binder機制跨進(jìn)程調用掃描函數。這部分內容,將在本章的拓展部分中介紹。

    本章僅關(guān)注android.process.media進(jìn)程中的MediaScannerService和MediaScannerReceiver模塊,為書(shū)寫(xiě)方便起見(jiàn),將這兩個(gè)模塊簡(jiǎn)稱(chēng)為MSS和MSR,另外將MediaScanner簡(jiǎn)稱(chēng)MS,將MediaProvider簡(jiǎn)稱(chēng)MP。

    下面,開(kāi)始分析android.process.media中和媒體文件掃描相關(guān)的工作流程。

    10.2.1 ?MSR模塊的分析

    MSR模塊的核心類(lèi)MediaScannerReceiver從BroadcastReceiver派生,它是專(zhuān)門(mén)用來(lái)接收廣播的,那么它感興趣的廣播有哪幾種呢?其代碼如下所示:

    [-->MediaScannerReceiver.java]

    public class MediaScannerReceiver extendsBroadcastReceiver

    {

    private final static String TAG ="MediaScannerReceiver";

    ???@Override? //MSR在onReceive函數中處理廣播

    ??? publicvoid onReceive(Context context, Intent intent) {

    ???????String action = intent.getAction();

    ???????Uri uri = intent.getData();

    ??????? //一般手機外部存儲的路徑是/mnt/sdcard

    ???????String externalStoragePath =

    ????????????????????? Environment.getExternalStorageDirectory().getPath();

    ????????

    ??????? //為了簡(jiǎn)化書(shū)寫(xiě),所有Intent的ACTION_XXX_YYY字串都會(huì )簡(jiǎn)寫(xiě)為XXX_YYY。

    ??????? if(action.equals(Intent.ACTION_BOOT_COMPLETED)) {

    ????????????//如果收到BOOT_COMPLETED廣播,則啟動(dòng)內部存儲區的掃描工作,內部存儲區

    ???????????//實(shí)際上掃描的是/system/media目錄,這里存儲了系統自帶的鈴聲等媒體文件。

    ????????????scan(context, MediaProvider.INTERNAL_VOLUME);

    ??????? }else {

    ???????????if (uri.getScheme().equals("file")) {

    ????????????????String path = uri.getPath();

    ?????????????/*

    注意下面這個(gè)判斷,如果收到MEDIA_MOUNTED消息,并且外部存儲掛載的路徑

    ???????????????和“/mnt/sdcard“一樣,則啟動(dòng)外部存儲也就是SD卡的掃描工作

    ???????????????*/

    ???????????????if (action.equals(Intent.ACTION_MEDIA_MOUNTED) &&

    ???????????????????????externalStoragePath.equals(path)) {

    ??????????????????? scan(context,MediaProvider.EXTERNAL_VOLUME);

    ????? ??????????} else if(action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)

    && path != null

    && path.startsWith(externalStoragePath +"/")) {

    ??????????????????? /*

    外部應用可以發(fā)送MEDIA_SCANNER_SCAN_FILE廣播讓MSR啟動(dòng)單個(gè)文件

    的掃描工作。注意這個(gè)文件必須位于SD卡上。

    */

    ??????????????????? scanFile(context, path);

    ???????????????}

    ???????????}

    ??????? }

    ??? }

    從上面代碼中發(fā)現MSR接收的三種請求,也就是說(shuō),它對外提供三個(gè)接口函數:

    ·? 接收BOOT_COMPLETED請求,這樣MSR會(huì )啟動(dòng)內部存儲區的掃描工作,注意這個(gè)內部存儲區實(shí)際上是/system/media這個(gè)目錄。

    ·? 接收MEDIA_MOUNTED請求,并且該請求攜帶的外部存儲掛載點(diǎn)路徑必須是/mnt/sdcard,通過(guò)這種方式MSR會(huì )啟動(dòng)外部存儲區也就是SD卡的掃描工作,掃描目標是文件夾/mnt/sdcard。

    ·? 接收MEDIA_SCANNER_SCAN_FILE請求,并且該請求必須是SD卡上的一個(gè)文件,即文件路徑須以/mnt/sdcard開(kāi)頭,這樣,MSR會(huì )啟動(dòng)針對這個(gè)文件的掃描工作。

    讀者是否注意到,MSR和跨Binder調用的接口(在本章拓展內容中將介紹)都不支持對目錄的掃描(除了SD卡的根目錄外)。實(shí)現這個(gè)功能并不復雜,有興趣的讀者可自行完成該功能,如果方便,請將自己實(shí)現的代碼與大家共享。

    大部分的媒體文件都已放在SD卡上了,那么來(lái)看收到MEDIA_MOUNTED請求后MSR的工作。還記得第9章中對Vold的分析嗎?這個(gè)MEDIA_MOUNTED廣播就是由MountService發(fā)送的,一旦有SD卡被掛載,MSR就會(huì )被這個(gè)廣播喚醒,接著(zhù)SD卡的媒體文件就會(huì )被掃描了。真是一氣呵成!

    SD卡根目錄掃描時(shí)調用的函數scan的代碼如下:

    [-->MediaScannerReceiver.java]

    private void scan(Context context, Stringvolume) {

    ???????//volume的值為/mnt/sdcard

    ??????? Bundleargs = new Bundle();

    ???????args.putString("volume", volume);

    ??????? //啟動(dòng)MSS。

    ???????context.startService(

    ???????????????new Intent(context, MediaScannerService.class).putExtras(args));

    ??? }?

    scan將啟動(dòng)MSS服務(wù)。下面來(lái)看MSS的工作。

    10.2.2 ?MSS模塊的分析

    MSS從Service派生,并且實(shí)現了Runnable接口。下面是它的定義:

    [-->MediaScannerService.java]

    MediaScannerService extends Service implementsRunnable

    //MSS實(shí)現了Runnable接口,這表明它可能會(huì )創(chuàng )建工作線(xiàn)程

    根據SDK中對Service生命周期的描述,Service剛創(chuàng )建時(shí)會(huì )調用onCreate函數,接著(zhù)就是onStartCommand函數,之后外界每調用一次startService都會(huì )觸發(fā)onStartCommand函數。接下來(lái)去了解一下onCreate函數及onStartCommand函數。

    1. onCreate的分析

    onCreate函數的代碼如下所示:(這是MSS被系統創(chuàng )建時(shí)調用的,在它的整個(gè)生命周期內僅調用一次。)

    [-->MediaScannerService.java]

    public void onCreate(){

    ?? //獲得電源鎖,防止在掃描過(guò)程中休眠

    ??PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);

    ??mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);

    //掃描工作是一個(gè)漫長(cháng)的工程,所以這里單獨創(chuàng )建一個(gè)工作線(xiàn)程,線(xiàn)程函數就是

    //MSS實(shí)現的Run函數

    ??? Threadthr = new Thread(null, this, "MediaScannerService");

    ???thr.start();

    |

    onCreate將創(chuàng )建一個(gè)工作線(xiàn)程:

    ?publicvoid run()

    ??? {

    ????? ??/*

    設置本線(xiàn)程的優(yōu)先級,這個(gè)函數的調用有很重要的作用,因為媒體掃描可能會(huì )耗費很長(cháng)

    ????????? 時(shí)間,如果不調低優(yōu)先級的話(huà),CPU將一直被MSS占用,導致用戶(hù)感覺(jué)系統變得很慢

    ????????*/

    ???????Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND +

    ????????????????????????????????Process.THREAD_PRIORITY_LESS_FAVORABLE);

    ???? ???Looper.prepare();

    ?

    ???????mServiceLooper = Looper.myLooper();

    ??????? /*

    創(chuàng )建一個(gè)Handler,以后發(fā)送給這個(gè)Handler的消息都會(huì )由工作線(xiàn)程處理。

    這一部分內容,已在第5章Handler中分析過(guò)了。

    */

    ???????mServiceHandler = new ServiceHandler();

    ?

    ???????Looper.loop();

    }

    onCreate后,MSS將會(huì )創(chuàng )建一個(gè)帶消息處理機制的工作線(xiàn)程,那么消息是怎么投遞到這個(gè)線(xiàn)程中的呢?

    2. onStartCommand的分析

    還記得MSR的scan函數嗎?如下所示:

    [-->MediaScannerReceiver.java::scan函數]

    context.startService(

    ???????????????new Intent(context, MediaScannerService.class).putExtras(args));

    其中Intent包含了目錄掃描請求的目標/mnt/sdcard。這個(gè)Intent發(fā)出后,最終由MSS的onStartCommand收到并處理,其代碼如下所示:

    [-->MediaScannerService.java]

    @Override

    ?publicint onStartCommand(Intent intent, int flags, int startId)

    ?{

    ???? /*

    等待mServiceHandler被創(chuàng )建。耕耘這段代碼的碼農難道不知道

    HandlerThread這個(gè)類(lèi)嗎?不熟悉它的讀者請再閱讀第5章的5.4節。

    ???? */

    ???? while(mServiceHandler == null) {

    ???????????synchronized (this) {

    ???????????????try {

    ??????????????????? wait(100);

    ???????????????} catch (InterruptedException e) {

    ???????????????}

    ???????????}

    ??????? }

    ?????? ......

    ???????Message msg = mServiceHandler.obtainMessage();

    ???????msg.arg1 = startId;

    ???????msg.obj = intent.getExtras();

    //往這個(gè)Handler投遞消息,最終由工作線(xiàn)程處理。

    ???????mServiceHandler.sendMessage(msg);

    ?? ????? ......

    }

    onStartCommand將把掃描請求信息投遞到工作線(xiàn)程去處理。

    3. 處理掃描請求

    掃描請求由ServiceHandler的handleMessage函數處理,其代碼如下所示:

    [-->MediaScannerService.java]

    private final class ServiceHandler extendsHandler

    {

    ???? @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)) {

    ???????????????????? //如果是掃描內部存儲的話(huà),實(shí)際上掃描的目錄是/system/media??

    ????????????????????? directories = newString[] {

    ???????????????????????????????Environment.getRootDirectory() + "/media",

    ??????????????????????? };

    ??????????????????? }

    ??????????????????? else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)){

    ????????????????????? //掃描外部存儲,設置掃描目標位/mnt/sdcard?

    ???????????????? ??????directories = new String[]{

    ?Environment.getExternalStorageDirectory().getPath()};

    ??????????????????? }

    ??????????????????? if (directories != null) {

    /*

    調用scan函數開(kāi)展文件夾掃描工作,可以一次為這個(gè)函數設置多個(gè)目標文件夾,

    不過(guò)這里只有/mnt/sdcard一個(gè)目錄

    */

    ??????????????????? scan(directories, volume);

    ???????????????????? ......

    ??????? ????????????stopSelf(msg.arg1);

    ??????? ???????}

    }

    下面,單獨用一小節來(lái)分析這個(gè)scan函數。

    4. MSS的scan函數分析

    scan的代碼如下所示:

    [-->MediaScannerService.java]

    private void scan(String[] directories, StringvolumeName) {

    ??? mWakeLock.acquire();

    ?

    ? ContentValuesvalues = new ContentValues();

    ??values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);

    ?? //MSS通過(guò)insert特殊Uri讓MediaProvider做一些準備工作

    ?? UriscanUri = getContentResolver().insert(

    MediaStore.getMediaScannerUri(), values);

    ?

    ?? Uri uri= Uri.parse("file://" + directories[0]);

    ?? //向系統發(fā)送一個(gè)MEDIA_SCANNER_STARTED廣播。

    ??sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));

    ???????try {

    ??????????//openDatabase函數也是通過(guò)insert特殊Uri讓MediaProvider打開(kāi)數據庫

    ???????????if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {

    ????????????????openDatabase(volumeName);???

    ???????????}

    ??????? //創(chuàng )建媒體掃描器,并調用它的scanDirectories函數掃描目標文件夾

    ???????MediaScanner scanner = createMediaScanner();

    ? ????????scanner.scanDirectories(directories,volumeName);

    ??????? }

    ?????????......

    //通過(guò)特殊Uri讓MediaProvider做一些清理工作

    ???????getContentResolver().delete(scanUri, null, null);

    //向系統發(fā)送MEDIA_SCANNER_FINISHED廣播

    ???????sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));

    ?

    ???????mWakeLock.release();

    }

    上面代碼中,比較復雜的是MSS和MP的交互。除了后文中即將看到的正常數據庫操作外,MSS還經(jīng)常會(huì )使用一些特殊的Uri來(lái)做數據庫操作,而MP針對這些Uri會(huì )做一些特殊處理,例如打開(kāi)數據庫文件等。

    本章不擬對MediaProvider做過(guò)多的討論,這部分知識對那些讀完前9章的讀者來(lái)說(shuō),應該不是什么難題。如有可能,請讀者自己整理MediaProvider的工作流程,然后提供給大家一起學(xué)習,探討。

    看MSS中創(chuàng )建媒體掃描器的函數createMediaScanner:

    private MediaScanner createMediaScanner() {

    //下面這個(gè)MediaScanner是在framework/base/中,稍后再分析

    ???????MediaScanner scanner = new MediaScanner(this);

    //獲取當前系統使用的區域信息,掃描的時(shí)候將把媒體文件中的信息轉換成當前系統使用的語(yǔ)言

    ???????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) {

    //為掃描器設置當前系統使用的國家和語(yǔ)言。

    ??????????????????? scanner.setLocale(language+ "_" + country);

    ???????????????} else {

    ???????????????????scanner.setLocale(language);

    ???????????????}

    ???????????}???

    ??????? }

    ???????return scanner;

    }

    MSS模塊掃描的工作就到此為止了,下面輪到主角MediaScanner登場(chǎng)了。在介紹主角之前,不妨先總結一下本節的內容。

    10.2.3 ?android.process.media媒體掃描工作的流程總結

    媒體掃描工作流程涉及MSR和MSS的交互,來(lái)總結一下相關(guān)的流程:

    ·? MSR接收外部發(fā)來(lái)的掃描請求,并通過(guò)startService方式啟動(dòng)MSS處理。

    ·? MSS的主線(xiàn)程接收MSR所收到的請求,然后投遞給工作線(xiàn)程去處理。

    ·? 工作線(xiàn)程做一些前期處理工作后(例如向系統廣播掃描開(kāi)始的消息),就創(chuàng )建媒體掃描器MediaScanner來(lái)處理掃描目標。

    ·? MS掃描完成后,工作線(xiàn)程再做一些后期處理,然后向系統發(fā)送掃描完畢的廣播。

    ?

    10.3 ?MediaScanner的分析

    現在分析媒體掃描器MediaScanner的工作原理,它將縱跨Java層、JNI層,以及Native層。先看它在Java層中的內容。

    10.3.1 ?Java層的分析

    1. 創(chuàng )建MediaScanner

    認識一下MediaScanner,它的代碼如下所示:

    [-->MediaScanner.java]

    public class MediaScanner

    {

    static {

    ?????? /*

    加載libmedia_jni.so,這么重要的庫竟然放在如此不起眼的MediaScanner類(lèi)中加載。

    個(gè)人覺(jué)得,可能是因為開(kāi)機后多媒體系統中最先啟動(dòng)的就是媒體掃描工作吧。

    ?????? */

    ?????? System.loadLibrary("media_jni");

    ???????native_init();

    }

    //創(chuàng )建媒體掃描器

    public MediaScanner(Context c) {

    ???????native_setup();//調用JNI層的函數做一些初始化工作

    ???????......

    }

    在上面的MS中,比較重要的幾個(gè)調用函數是:

    ·? native_init和native_setup,關(guān)于它們的故事,在分析JNI層時(shí)再做介紹。

    MS創(chuàng )建好后,MSS將調用它的scanDirectories開(kāi)展掃描工作,下面來(lái)看這個(gè)函數。

    2. scanDirectories的分析

    scanDirectories的代碼如下所示:

    [-->MediaScanner.java]

    public void scanDirectories(String[]directories, String volumeName) {

    ? try {

    ???????long start = System.currentTimeMillis();

    ????????initialize(volumeName);//①初始化

    ? ????????prescan(null);//②掃描前的預處理

    ????????long prescan = System.currentTimeMillis();

    ?

    ???????? for(int i = 0; i < directories.length; i++) {

    /*

    ③ processDirectory是一個(gè)native函數,調用它來(lái)對目標文件夾進(jìn)行掃描,

    ? 其中MediaFile.sFileExtensions是一個(gè)字符串,包含了當前多媒體系統所支持的

    媒體文件的后綴名,例如.MP3、.MP4等。mClient為MyMediaScannerClient類(lèi)型,

    它是從MediaScannerClient類(lèi)派生的。它的作用我們后面再做分析。

    ?

    */

    ???????????processDirectory(directories[i], MediaFile.sFileExtensions,

    ?mClient);

    ????????? ?}

    ???????????long scan = System.currentTimeMillis();

    ???????????postscan(directories);//④掃描后處理

    ???????????long end = System.currentTimeMillis();

    ????????? ......//統計掃描時(shí)間等

    ?}

    上面一共列出了四個(gè)關(guān)鍵點(diǎn),下面逐一對其分析。

    (1)initialize的分析

    initialize主要是初始化一些Uri,因為掃描時(shí)需把文件的信息插入媒體數據庫中,而媒體數據庫針對Video、Audio、Image文件等都有對應的表,這些表的地址則由Uri表示。下面是initialize的代碼:

    [-->MediaScanner.java]

    private void initialize(String volumeName) {

    //得到IMediaProvider對象,通過(guò)這個(gè)對象可以對媒體數據庫進(jìn)行操作。

    ? mMediaProvider=

    ?mContext.getContentResolver().acquireProvider("media");

    //初始化Uri,下面分別介紹一下。

    //音頻表的地址,也就是數據庫中的audio_meta表。

    ???? ?mAudioUri =Audio.Media.getContentUri(volumeName);

    ????? //視頻表地址,也就是數據庫中的video表。

    ?????mVideoUri = Video.Media.getContentUri(volumeName);

    ????? //圖片表地址,也就是數據庫中的images表。

    ?????mImagesUri = Images.Media.getContentUri(volumeName);

    ????? //縮略圖表地址,也就是數據庫中的thumbs表。

    ?????mThumbsUri = Images.Thumbnails.getContentUri(volumeName);

    ????? //如果掃描的是外部存儲,則支持播放列表、音樂(lè )的流派等內容。

    ???? ??if(!volumeName.equals("internal")) {

    ???????????mProcessPlaylists = true;

    ???????????mProcessGenres = true;

    ???????????mGenreCache = new HashMap<String, Uri>();

    ???????????mGenresUri = Genres.getContentUri(volumeName);

    ???????????mPlaylistsUri = Playlists.getContentUri(volumeName);

    ???????????if ( Process.supportsProcesses()) {

    ???????????????//SD卡存儲區域一般使用FAT文件系統,所以文件名與大小寫(xiě)無(wú)關(guān)

    ???????????????mCaseInsensitivePaths = true;

    ???????????}

    ??????? }

    }

    下面看第二個(gè)關(guān)鍵函數prescan。

    (2)prescan的分析

    在媒體掃描過(guò)程中,有個(gè)令人頭疼的問(wèn)題,來(lái)舉個(gè)例子,這個(gè)例子會(huì )貫穿在對這個(gè)問(wèn)題整體分析的過(guò)程中。例子:假設某次掃描之前SD卡中有100個(gè)媒體文件,數據庫中有100條關(guān)于這些文件的記錄,現因某種原因刪除了其中的50個(gè)媒體文件,那么媒體數據庫什么時(shí)候會(huì )被更新呢?

    讀者別小瞧這個(gè)問(wèn)題?,F在有很多文件管理器支持刪除文件和文件夾,它們用起來(lái)很方便,卻沒(méi)有對應地更新數據庫,這導致了查詢(xún)數據庫時(shí)還能得到這些媒體文件信息,但這個(gè)文件實(shí)際上已不存在了,而且后面所有和此文件有關(guān)的操作都會(huì )因此而失敗。

    其實(shí),MS已經(jīng)考慮到這一點(diǎn)了,prescan函數的主要作用是在掃描之前把數據庫中和文件相關(guān)的信息取出并保存起來(lái),這些信息主要是媒體文件的路徑,所屬表的Uri。就上面這個(gè)例子來(lái)說(shuō),它會(huì )從數據庫中取出100個(gè)文件的文件信息。

    prescan的代碼如下所示:

    [-->MediaScanner.java]

    ?privatevoid prescan(String filePath) throws RemoteException {

    ???????Cursor c = null;

    ???????String where = null;

    ???????String[] selectionArgs = null;

    ??????? //mFileCache保存從數據庫中獲取的文件信息。

    ??????? if(mFileCache == null) {

    ???????????mFileCache = new HashMap<String, FileCacheEntry>();

    ??????? }else {

    ???????????mFileCache.clear();

    ??????? }

    ??????? ......

    ?????? try {

    ???????????//從Audio表中查詢(xún)其中和音頻文件相關(guān)的文件信息。

    ???????????if (filePath != null) {

    ???????????????where = MediaStore.Audio.Media.DATA + "=?";

    ???????????????selectionArgs = new String[] { filePath };

    ???????????}

    ???????????//查詢(xún)數據庫的Audio表,獲取對應的音頻文件信息。

    ???????????c = mMediaProvider.query(mAudioUri, AUDIO_PROJECTION, where,

    ?selectionArgs,null);

    ???????? ???if (c != null) {

    ???????????????try {

    ??????????????????? while (c.moveToNext()) {

    ??????????????????????? long rowId =c.getLong(ID_AUDIO_COLUMN_INDEX);

    ??????????????????????? //音頻文件的路徑

    ??????????????????????? String path =c.getString(PATH_AUDIO_COLUMN_INDEX);

    ??????????????????????? long lastModified =

    ?c.getLong(DATE_MODIFIED_AUDIO_COLUMN_INDEX);

    ?

    ???????????????????????? if(path.startsWith("/")) {

    ??????????????????????????? String key = path;

    ??????????????????????????? if(mCaseInsensitivePaths) {

    ??????????????????????????????? key =path.toLowerCase();

    ??????????????????????????? }

    ?????????????????????????? //把文件信息存到mFileCache中

    ??????????????????????????? mFileCache.put(key,

    new FileCacheEntry(mAudioUri, rowId, path,

    ?????????????????????????????????lastModified));

    ??????????????????????? }

    ??????????????????? }

    ???????????????} finally {

    ??????????????????? c.close();

    ??????????????????? c = null;

    ???????????????}

    ???????????}

    ???????? ......//查詢(xún)其他表,取出數據中關(guān)于視頻,圖像等文件的信息并存入到mFileCache中。

    ???????finally {

    ???????????if (c != null) {

    ???????????????c.close();

    ???????????}

    ?????? ?}

    ??? }

    懂了前面的例子,在閱讀prescan函數時(shí)可能就比較輕松了。prescan函數執行完后,mFileCache保存了掃描前所有媒體文件的信息,這些信息是從數據庫中查詢(xún)得來(lái)的,也就是舊有的信息。

    接下來(lái),看最后兩個(gè)關(guān)鍵函數。

    (3)processDirectory和postscan的分析

    processDirectory是一個(gè)native函數,其具體功能放到JNI層再分析,這里先簡(jiǎn)單介紹,它在解決上一節那個(gè)例子中提出的問(wèn)題時(shí),所做的工作。答案是:

    processDirectory將掃描SD卡,每掃描一個(gè)文件,都會(huì )設置mFileCache中對應文件的一個(gè)叫mSeenInFileSystem的變量為true。這個(gè)值表示這個(gè)文件目前還存在于SD卡上。這樣,待整個(gè)SD卡掃描完后,mFileCache的那100個(gè)文件中就會(huì )有50個(gè)文件的mSeenInFileSystem為true,而剩下的另50個(gè)文件則為初始值false。

    看到上面的內容,可以知道postscan的作用了吧?就是它把不存在于SD卡的文件信息從數據庫中刪除,而使數據庫得以徹底更新的。來(lái)看postscan函數是否是這樣處理的:

    [-->MediaScanner.java]

    private void postscan(String[] directories)throws RemoteException {

    ?

    Iterator<FileCacheEntry> iterator =mFileCache.values().iterator();

    ? while(iterator.hasNext()) {

    ???????????FileCacheEntry entry = iterator.next();

    ???????????String path = entry.mPath;

    ?

    ???????????boolean fileMissing = false;

    ???????????if (!entry.mSeenInFileSystem) {

    ???????????????if (inScanDirectory(path, directories)) {

    ??????????????????? fileMissing = true; //這個(gè)文件確實(shí)丟失了

    ???????????????} else {

    ??????????????????? File testFile = newFile(path);

    ??????????????????? if (!testFile.exists()) {

    ??????????????????????? fileMissing = true;

    ??????????????????? }

    ???????????????}

    ???????????}

    ??????? //如果文件確實(shí)丟失,則需要把數據庫中和它相關(guān)的信息刪除。

    ??????? if(fileMissing) {

    ??????????MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);

    ??????????int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);

    ????????? if(MediaFile.isPlayListFileType(fileType)) {

    ??????????????????? ?......//處理丟失文件是播放列表的情況

    ????????? ??} else {

    ??????????????/*

    由于文件信息中還攜帶了它在數據庫中的相關(guān)信息,所以從數據庫中刪除對應的信息會(huì )

    非???。

    ??????????????*/

    ??????????????mMediaProvider.delete(ContentUris.withAppendedId(

    entry.mTableUri, entry.mRowId), null, null);

    ????????????iterator.remove();

    ????????????}

    ????????? }

    ???? }

    ??? ......//刪除縮略圖文件等工作

    }

    Java層中的四個(gè)關(guān)鍵點(diǎn),至此已介紹了三個(gè),另外一個(gè)processDirectory是媒體掃描的關(guān)鍵函數,由于它是一個(gè)native函數,所以下面將轉戰到JNI層來(lái)進(jìn)行分析。

    ?

    10.3.2 ?JNI層的分析

    現在分析MS的JNI層。在Java層中,有三個(gè)函數涉及JNI層,它們是:

    ·? native_init,這個(gè)函數由MediaScanner類(lèi)的static塊調用。

    ·? native_setup,這個(gè)函數由MediaScanner的構造函數調用。

    ·? processDirectory,這個(gè)函數由MS掃描文件夾時(shí)調用。

    分別來(lái)分析它們。

    1. native_init函數的分析

    下面是native_init對應的JNI函數,其代碼如下所示:

    [-->android_media_MediaScanner.cpp]

    static void

    android_media_MediaScanner_native_init(JNIEnv*env)

    {

    ????jclass clazz;

    clazz =env->FindClass("android/media/MediaScanner");

    //取得Java中MS類(lèi)的mNativeContext信息。待會(huì )創(chuàng )建Native對象的指針會(huì )保存

    //到JavaMS對象的mNativeContext變量中。

    ??? ?fields.context = env->GetFieldID(clazz,"mNativeContext", "I");

    ?? ??......

    }

    native_init函數沒(méi)什么新意,這種把Native對象的指針保存到Java對象中的做法,已經(jīng)屢見(jiàn)不鮮。下面看第二個(gè)函數native_setup。

    2. native_setup函數的分析

    native_setup對應的JNI函數如下所示:

    [-->android_media_MediaScanner.cpp]

    android_media_MediaScanner_native_setup(JNIEnv*env, jobject thiz)

    {

    //創(chuàng )建Native層的MediaScanner對象

    MediaScanner*mp = createMediaScanner();

    ......

    //把mp的指針保存到Java MS對象的mNativeContext中去

    env->SetIntField(thiz,fields.context, (int)mp);

    }

    //下面的createMediaScanner這個(gè)函數將創(chuàng )建一個(gè)Native的MS對象

    static MediaScanner *createMediaScanner() {

    #if BUILD_WITH_FULL_STAGEFRIGHT

    ??? charvalue[PROPERTY_VALUE_MAX];

    ??? if(property_get("media.stagefright.enable-scan", value, NULL)

    ???????&& (!strcmp(value, "1") || !strcasecmp(value,"true"))) {

    ???????return new StagefrightMediaScanner; //使用Stagefright的MS

    ??? }

    #endif

    #ifndef NO_OPENCORE

    ??? returnnew PVMediaScanner(); //使用Opencore的MS,我們會(huì )分析這個(gè)

    #endif

    ??? returnNULL;

    }

    native_setup函數將創(chuàng )建一個(gè)Native層的MS對象,不過(guò)可惜的是,它使用的還是Opencore提供的PVMediaScanner,所以后面還不可避免地會(huì )和Opencore“正面交鋒”。

    4. processDirectory函數的分析

    看processDirectories函數,它對應的JNI函數代碼如下所示:

    [-->android_media_MediaScanner.cpp]

    android_media_MediaScanner_processDirectory(JNIEnv*env, jobject thiz,

    jstring path, jstring extensions, jobject client)

    {

    ?? /*

    注意上面傳入的參數,path為目標文件夾的路徑,extensions為MS支持的媒體文件后綴名集合,

    client為Java中的MediaScannerClient對象。

    */

    ?

    MediaScanner *mp = (MediaScanner*)env->GetIntField(thiz, fields.context);

    ?

    ??? constchar *pathStr = env->GetStringUTFChars(path, NULL);

    constchar *extensionsStr = env->GetStringUTFChars(extensions, NULL);

    ......

    ??

    ?? //構造一個(gè)Native層的MyMediaScannerClient,并使用Java那個(gè)Client對象做參數。

    ?? //這個(gè)Native層的Client簡(jiǎn)稱(chēng)為MyMSC。

    MyMediaScannerClient myClient(env, client);

    //調用Native的MS掃描文件夾,并且把Native的MyMSC傳進(jìn)去。

    mp->processDirectory(pathStr,extensionsStr, myClient,

    ExceptionCheck, env);

    ??? ......

    ???env->ReleaseStringUTFChars(path, pathStr);

    env->ReleaseStringUTFChars(extensions,extensionsStr);

    ......

    }

    processDirectory函數本身倒不難,但又冒出了幾個(gè)我們之前沒(méi)有接觸過(guò)的類(lèi)型,下面先來(lái)認識一下它們。

    5. 到底有多少種對象?

    圖10-1展示了MediaScanner所涉及的相關(guān)類(lèi)和它們之間的關(guān)系:

    http://wiki.jikexueyuan.com/project/deep-android-v1/images/chapter10/image001.png" alt="image" />

    圖10-1? MS相關(guān)類(lèi)示意圖

    為了便于理解,便將Java和Native層的對象都畫(huà)于圖中。從上圖可知:

    ·? Java MS對象通過(guò)mNativeContext指向Native的MS對象。

    ·? Native的MyMSC對象通過(guò)mClient保存Java層的MyMSC對象。

    ·? Native的MS對象調用processDirectory函數的時(shí)候會(huì )使用Native的MyMSC對象。

    ·? 另外,圖中Native MS類(lèi)的processFile是一個(gè)虛函數,需要派生類(lèi)來(lái)實(shí)現。

    其中比較費解的是MyMSC對象。它們有什么用呢?這個(gè)問(wèn)題真是一言難盡。下面通過(guò)processDirectory來(lái)探尋其中原因,這回得進(jìn)入PVMediaScanner的領(lǐng)地了。

    10.3.3 ?PVMediaScanner的分析

    1. PVMS的processDirectory分析

    來(lái)看PVMediaScanner(以后簡(jiǎn)稱(chēng)為PVMS,它就是Native層的MS)的processDirectory函數。這個(gè)函數是由它的基類(lèi)MS實(shí)現的。注意,源碼中有兩個(gè)MediaScanner.cpp,它們的位置分別是:

    ·? framework/base/media/libmedia

    ·? external/opencore/android/

    看libmedia下的那個(gè)MediaScanner.cpp,其中processDirectory函數的代碼如下所示:

    [-->MediaScanner.cpp]

    status_t MediaScanner::processDirectory(constchar *path,

    const char *extensions, MediaScannerClient&client,

    ??????? ??????????????????ExceptionCheckexceptionCheck, void *exceptionEnv) {

    ??? ?

    ???......//做一些準備工作

    ???client.setLocale(locale()); //給Native的MyMSC設置locale信息

    ?? //調用doProcessDirectory函數掃描文件夾

    status_tresult =? doProcessDirectory(pathBuffer,pathRemaining,

    extensions, client,exceptionCheck, exceptionEnv);

    ?

    ???free(pathBuffer);

    ?

    ??? returnresult;

    }

    //下面直接看這個(gè)doProcessDirectory函數

    status_t MediaScanner::doProcessDirectory(char*path, int pathRemaining,

    const char *extensions,MediaScannerClient&client,

    ExceptionCheck exceptionCheck,void *exceptionEnv) {

    ???

    ?? ......//忽略.nomedia文件夾

    ?

    ??? DIR*dir = opendir(path);

    ??? ......

    ?

    while((entry = readdir(dir))) {

    ??? //枚舉目錄中的文件和子文件夾信息

    ???????const char* name = entry->d_name;

    ??????? ......

    ???????int type = entry->d_type;

    ??????? ?......

    ??????? if(type == DT_REG || type == DT_DIR) {

    ???????????int nameLength = strlen(name);

    ???????????bool isDirectory = (type == DT_DIR);

    ??????????......

    ???????????strcpy(fileSpot, name);

    ???????????if (isDirectory) {

    ???????????????......

    ????????????????//如果是子文件夾,則遞歸調用doProcessDirectory

    ???????????????int err = doProcessDirectory(path, pathRemaining - nameLength - 1,

    extensions, client, exceptionCheck, exceptionEnv);

    ???????????????......

    ???????????} else if (fileMatchesExtension(path, extensions)) {

    ???????????????//如果該文件是MS支持的類(lèi)型(根據文件的后綴名來(lái)判斷)

    ????????? ??????struct stat statbuf;

    ???????????????stat(path, &statbuf); //取出文件的修改時(shí)間和文件的大小

    ???????????????if (statbuf.st_size > 0) {

    ??????????????????? //如果該文件大小非零,則調用MyMSC的scanFile函數?。??

    ??????????????????? client.scanFile(path,statbuf.st_mtime, statbuf.st_size);

    ???????????????}

    ???????????????if (exceptionCheck && exceptionCheck(exceptionEnv)) gotofailure;

    ???????????}

    ??????? }

    ??? }

    ......

    }

    假設正在掃描的媒體文件的類(lèi)型是屬于MS支持的,那么,上面代碼中最不可思議的是,它竟然調用了MSC的scanFile來(lái)處理這個(gè)文件,也就是說(shuō),MediaScanner調用MediaScannerClient的scanFile函數。這是為什么呢?還是來(lái)看看這個(gè)MSC的scanFile吧。

    2. MyMSC的scanFile分析

    (1)JNI層的scanFile

    其實(shí),在調用processDirectory時(shí),所傳入的MSC對象的真實(shí)類(lèi)型是MyMediaScannerClient,下面來(lái)看它的scanFile函數,代碼如下所示:

    [-->android_media_MediaScanner.cpp]

    virtual bool scanFile(const char* path, longlong lastModified,

    long long fileSize)

    ??? {

    ???????jstring pathStr;

    ??????? if((pathStr = mEnv->NewStringUTF(path)) == NULL) return false;

    ???????//mClient是Java層的那個(gè)MyMSC對象,這里調用它的scanFile函數

    ???????mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr,

    lastModified, fileSize);

    ?

    ???????mEnv->DeleteLocalRef(pathStr);

    ???????return (!mEnv->ExceptionCheck());

    }

    太沒(méi)有天理了!Native的MyMSCscanFile主要的工作就是調用Java層MyMSC的scanFile函數。這又是為什么呢?

    (2)Java層的scanFile

    現在只能來(lái)看Java層的這個(gè)MyMSC對象了,它的scanFile代碼如下所示:

    [-->MediaScanner.java]

    public void scanFile(String path, longlastModified, long fileSize) {

    ???????????......

    ???????????//調用doScanFile函數

    ???????????doScanFile(path, null, lastModified, fileSize, false);

    ??????? }

    //直接來(lái)看doScanFile函數

    ?publicUri doScanFile(String path, String mimeType, long lastModified,

    long fileSize, boolean scanAlways) {

    ? /*

    上面參數中的scanAlways用于控制是否強制掃描,有時(shí)候一些文件在前后兩次掃描過(guò)程中沒(méi)有

    發(fā)生變化,這時(shí)候MS可以不處理這些文件。如果scanAlways為true,則這些沒(méi)有變化

    的文件也要掃描。

    ? */

    ?? Uriresult = null;

    long t1 = System.currentTimeMillis();

    try{

    ???? /*

    ????? beginFile的主要工作,就是將保存在mFileCache中的對應文件信息的

    mSeenInFileSystem設為true。如果這個(gè)文件之前沒(méi)有在mFileCache中保存,

    則會(huì )創(chuàng )建一個(gè)新項添加到mFileCache中。另外它還會(huì )根據傳入的lastModified值

    做一些處理,以判斷這個(gè)文件是否在前后兩次掃描的這個(gè)時(shí)間段內被修改,如果有修改,則

    需要重新掃描

    */

    ????????? FileCacheEntryentry = beginFile(path, mimeType,

    lastModified, fileSize);

    ? ???????if(entry != null && (entry.mLastModifiedChanged || scanAlways)) {

    ?????????????String lowpath = path.toLowerCase();

    ?????????????......

    ?

    ?????????????if (!MediaFile.isImageFileType(mFileType)) {

    //如果不是圖片,則調用processFile進(jìn)行掃描,而圖片不需要掃描就可以處理

    //注意在調用processFile時(shí)把這個(gè)Java的MyMSC對象又傳了進(jìn)去。

    ???????????????processFile(path, mimeType, this);

    ?????????????}

    //掃描完后,需要把新的信息插入數據庫,或者要將原有的信息更新,而endFile就是做這項工作的。

    ????????????result = endFile(entry, ringtones, notifications,

    alarms, music, podcasts);

    ?????? ?????????}

    ???????????} ......

    ???????????return result;

    ??????? }

    下面看這個(gè)processFile,這又是一個(gè)native的函數。

    上面代碼中的beginFile和endFile函數比較簡(jiǎn)單,讀者可以自行研究。

    (3)JNI層的processFile分析

    MediaScanner的代碼有點(diǎn)繞,是不是?總感覺(jué)我們像追兵一樣,追著(zhù)MS在赤水來(lái)回地繞,現在應該是二渡赤水了。來(lái)看這個(gè)processFile函數,代碼如下所示:

    [-->android_media_MediaScanner.cpp]

    android_media_MediaScanner_processFile(JNIEnv*env, jobject thiz,

    jstring path, jstring mimeType, jobject client)

    {

    ???//Native的MS還是那個(gè)MS,其真實(shí)類(lèi)型是PVMS。

    ???MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz,fields.context);

    ? //又構造了一個(gè)新的Native的MyMSC,不過(guò)它指向的Java層的MyMSC沒(méi)有變化。

    MyMediaScannerClient myClient(env, client);

    //調用PVMS的processFile處理這個(gè)文件。

    mp->processFile(pathStr,mimeTypeStr, myClient);

    }

    草莓视频在线观看视频6_免费草莓视频_草莓视频在线下载免费官网_草莓视频黄色在线观看