欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android中扫描多媒体文件操作详解

程序员文章站 2022-04-12 12:25:16
这篇文章从系统源代码分析,讲述如何将程序创建的多媒体文件加入系统的媒体库,如何从媒体库删除,以及大多数程序开发者经常遇到的无法添加到媒体库的问题等。本人将通过对源代码的分析...

这篇文章从系统源代码分析,讲述如何将程序创建的多媒体文件加入系统的媒体库,如何从媒体库删除,以及大多数程序开发者经常遇到的无法添加到媒体库的问题等。本人将通过对源代码的分析,一一解释这些问题。

android中的多媒体文件扫描机制

android提供了一个很棒的程序来处理将多媒体文件加入的媒体库中。这个程序就是mediaprovider,现在我们简单看以下这个程序。首先看一下它的receiver

复制代码 代码如下:

    <receiver android:name="mediascannerreceiver">
        <intent-filter>
            <action android:name="android.intent.action.boot_completed" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.media_mounted" />
            <data android:scheme="file" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.media_unmounted" />
            <data android:scheme="file" />
        </intent-filter>
        <intent-filter>
            <action android:name="android.intent.action.media_scanner_scan_file" />
            <data android:scheme="file" />
        </intent-filter>
    </receiver>

mediascannerreceiver只接收符合action和数据规则正确的intent。

mediascannerreciever如何处理intent

1.当且仅当接收到action android.intent.action.boot_completed才扫描内部存储(非内置和外置sdcard)
2.除了action为android.intent.action.boot_completed 的以外的intent都必须要有数据传递。
3.当收到 intent.action_media_mounted intent,扫描sdcard
4.当收到 intent.action_media_scanner_scan_file intent,检测没有问题,将扫描单个文件。

mediascannerservice如何工作

实际上mediascannerreceiver并不是真正处理扫描工作,它会启动一个叫做mediascannerservice的服务。我们继续看mediaprovider的manifest中关于service的部分。

复制代码 代码如下:

 <service android:name="mediascannerservice" android:exported="true">
        <intent-filter>
            <action android:name="android.media.imediascannerservice" />
        </intent-filter>
    </service>

mediascannerservice中的scanfile方法

复制代码 代码如下:

private uri scanfile(string path, string mimetype) {
    string volumename = mediaprovider.external_volume;
    opendatabase(volumename);
    mediascanner scanner = createmediascanner();
    return scanner.scansinglefile(path, volumename, mimetype);
}

mediascannerservice中的scan方法

复制代码 代码如下:

private void scan(string[] directories, string volumename) {
    // don't sleep while scanning
    mwakelock.acquire();

    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]);
    sendbroadcast(new intent(intent.action_media_scanner_started, uri));

    try {
        if (volumename.equals(mediaprovider.external_volume)) {
            opendatabase(volumename);
        }

        mediascanner scanner = createmediascanner();
        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();
}

mediascannerservice中的createmediascanner方法

复制代码 代码如下:

private mediascanner createmediascanner() {
        mediascanner scanner = new mediascanner(this);
        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) {
                    scanner.setlocale(language + "_" + country);
                } else {
                    scanner.setlocale(language);
                }
            }
        }

        return scanner;
}

从上面可以发现,真正工作的其实是android.media.mediascanner.java 具体扫描过程就请点击左侧链接查看。

如何扫描一个刚创建的文件

这里介绍两种方式来实现将新创建的文件加入媒体库。

最简单的方式

只需要发送一个正确的intent广播到mediascannerreceiver即可。

复制代码 代码如下:

string saveas = "your_created_file_path"
uri contenturi = uri.fromfile(new file(saveas));
intent mediascanintent = new intent(intent.action_media_scanner_scan_file,contenturi);
getcontext().sendbroadcast(mediascanintent);

上面的极简方法大多数情况下正常工作,但是有些情况下是不会工作的,稍后的部分会介绍。即使你使用上述方法成功了,还是建议你继续阅读稍后的为什么发广播不成功的部分。

使用mediascannerconnection

复制代码 代码如下:

public void mediascan(file file) {
    mediascannerconnection.scanfile(getactivity(),
            new string[] { file.getabsolutepath() }, null,
            new onscancompletedlistener() {
                @override
                public void onscancompleted(string path, uri uri) {
                    log.v("mediascanwork", "file " + path
                            + " was scanned seccessfully: " + uri);
                }
            });
}

mediascannerconnection的scanfile方法从2.2(api 8)开始引入。

创建一个mediascannerconnection对象然后调用scanfile方法

很简单,参考http://developer.android.com/reference/android/media/mediascannerconnection.html

如何扫描多个文件

1.发送多个intent.action_media_scanner_scan_file广播
2.使用mediascannerconnection,传入要加入的路径的数组。

为什么发送media_scanner_scan_file广播不生效

关于为什么有些设备上不生效,很多人认为是api原因,其实不是的,这其实和你传入的文件路径有关系。看一下接收者receiver的onreceive代码。

复制代码 代码如下:

public void onreceive(context context, intent intent) {
    string action = intent.getaction();
    uri uri = intent.getdata();
    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();
            string externalstoragepath = environment.getexternalstoragedirectory().getpath();

            log.d(tag, "action: " + action + " path: " + path);
            if (action.equals(intent.action_media_mounted)) {
                // scan whenever any volume is mounted
                scan(context, mediaprovider.external_volume);
            } else if (action.equals(intent.action_media_scanner_scan_file) &&
                    path != null && path.startswith(externalstoragepath + "/")) {
                scanfile(context, path);
            }
        }
    }
}

所有的部分都正确除了传入的路径。因为你可能硬编码了文件路径。因为有一个这样的判断path.startswith(externalstoragepath + "/"),这里我举一个简单的小例子。

复制代码 代码如下:

final string saveas = "/sdcard/" + system.currenttimemillis() + "_add.png";
uri contenturi = uri.fromfile(new file(saveas));
intent mediascanintent = new intent(intent.action_media_scanner_scan_file,contenturi);
getcontext().sendbroadcast(mediascanintent);
uri uri = mediascanintent.getdata();
string path = uri.getpath();
string externalstoragepath = environment.getexternalstoragedirectory().getpath();
log.i("logtag", "androidyue onreceive intent= " + mediascanintent
                        + ";path=" + path + ";externalstoragepath=" +
                        externalstoragepath);

我们看一下输出日志,分析原因。

复制代码 代码如下:

logtag androidyue onreceive intent= intent { act=android.intent.action.media_scanner_scan_file dat=file:///sdcard/1390136305831_add.png };path=/sdcard/1390136305831_add.png;externalstoragepath=/mnt/sdcard

上述输出分析,你发送的广播,action是正确的,数据规则也是正确的,而且你的文件路径也是存在的,但是,文件的路径/sdcard/1390136305831_add.png并不是以外部存储根路径/mnt/sdcard/开头。所以扫描操作没有开始,导致文件没有加入到媒体库。所以,请检查文件的路径。

如何从多媒体库中移除

如果我们删除一个多媒体文件的话,也就意味我们还需要将这个文件从媒体库中删除掉。

能不能简简单单发广播?

仅仅发一个广播能解决问题么?我倒是希望可以,但是实际上是不工作的,查看如下代码即可明白。

复制代码 代码如下:

// this function is used to scan a single file
public uri scansinglefile(string path, string volumename, string mimetype) {
    try {
        initialize(volumename);
        prescan(path, true);

        file file = new file(path);
        if (!file.exists()) {
            return null;
        }

        // lastmodified is in milliseconds on files.
        long lastmodifiedseconds = file.lastmodified() / 1000;

        // always scan the file, so we can return the content://media uri for existing files
        return mclient.doscanfile(path, mimetype, lastmodifiedseconds, file.length(),
                false, true, mediascanner.isnomediapath(path));
    } catch (remoteexception e) {
        log.e(tag, "remoteexception in mediascanner.scanfile()", e);
        return null;
    }
}

正如上述代码,会对文件是否存在进行检查,如果文件不存在,直接停止向下执行。所以这样是不行的。那怎么办呢?

复制代码 代码如下:

public void testdeletefile() {
    string existingfilepath = "/mnt/sdcard/1390116362913_add.png";
    file  existingfile = new file(existingfilepath);
    existingfile.delete();
    contentresolver resolver = getactivity().getcontentresolver();
    resolver.delete(images.media.external_content_uri, images.media.data + "=?", new string[]{existingfilepath});

}

上述代码是可以工作的,直接从mediaprovider删除即可。 具体的删除代码请参考code snippet for media on android

one more thing

你可以通过查看/data/data/com.android.providers.media/databases/external.db(不同系统略有不同)文件可以了解更多的信息。