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

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

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(不同系统略有不同)文件可以了解更多的信息。

(0)

相关推荐

  • Android SD卡上文件操作及记录日志操作实例分析

    本文实例讲述了Android SD卡上文件操作及记录日志操作的方法.分享给大家供大家参考,具体如下: // SD卡是否存在 private boolean checkSDCardStatus() { boolean SDCardStatus = false; String sDStateString = android.os.Environment.getExternalStorageState(); if (sDStateString.equals(android.os.Environment

  • Android开发之文件操作详解

    本文实例讲述了Android开发之文件操作.分享给大家供大家参考,具体如下: 目前,几乎所有的设备都会涉及到文件的操作,例如什么电脑,手机等设备.Android的文件操作和电脑是比较类似的,既可以存储在手机内置的存储器里也可以是sd卡.在这篇文章里主要介绍在手机内置存储器里的文件操作. 一. 开发流程 (1)界面的设计 (2)设计android的业务层 (3)单元测试 (4)设置android的控制器层 二. 开发步骤 (1)设计软件界面 <?xml version="1.0"

  • Android中使用pull解析器操作xml文件的解决办法

    一.使用Pull解析器读取XML文件 除了可以使用SAX或DOM解析XML文件之外,大家也可以使用Android内置的Pull解析器解析XML文件. Pull解析器是一个开源的java项目,既可以用于android,也可以用于JavaEE.如果用在javaEE需要把其jar文件放入类路径中,因为Android已经集成进了Pull解析器,所以无需添加任何jar文件.android系统本身使用到的各种xml文件,其内部也是采用Pull解析器进行解析的. Pull解析器的运行方式与SAX 解析器相似.

  • Android对sdcard扩展卡文件操作实例详解

    Android对sdcard扩展卡文件的操作其实就是普通的文件操作,但是仍然有些地方需要注意.比如: 1.加入sdcard操作权限: 2.确认sdcard的存在: 3.不能直接在非sdcard的根目录创建文件,而是需要先创建目录,再创建文件: 实例如下: (1)在AndroidManifest.xml添加sdcard操作权限 <!-- sdcard权限 --> <uses-permission android:name="android.permission.WRITE_EXT

  • Android 文件操作方法

    数据存储与访问常用方式:文件SharedPreferences(偏好参数设置)SQLite数据库内容提供者(Content provider)网络 Activity(Context)Context.getCacheDir()方法用于获取/data/data/<package name>/cache目录Context.getFilesDir()方法用于获取/data/data/<package name>/files目录 Activity(Context)提供了openFileOut

  • Android开发之文件操作模式深入理解

    一.基本概念 复制代码 代码如下: // 上下文对象 private Context context; public FileService(Context context) { super(); this.context = context; } // 保存文件方法 public void save(String filename, String fileContent) throws Exception { FileOutputStream fos = context.openFileOut

  • Android编程之在SD卡上进行文件读写操作实例详解

    本文实例讲述了Android编程之在SD卡上进行文件读写操作的方法.分享给大家供大家参考,具体如下: 很多知识只有真正理解掌握之后才能运用自如,举一反三.对Java中的文件操作和android系统SD卡里面的文件操作,你觉得有区别吗,显然没有本质区别,如果勉强说有,那也是不足为道滴,但我们在实际运用中却要注意如下几点,不然问题会缠上你. 1.首先想要对android系统SD卡里文件操作需要添加使用权限: android系统是不会让外来程序随意动自己内存的,如果没有许可证,不好意思,不准你动我地盘

  • Android操作存放在assets文件夹下SQLite数据库的方法

    本文实例讲述了Android操作存放在assets文件夹下SQLite数据库的方法.分享给大家供大家参考.具体如下: 因为这次的项目需要自带数据,所以就就把数据都放到一个SQLite的数据库文件中了,之后把该文件放到了assets文件夹下面.一开始打算每次都从assets文件夹下面把该文件夹拷贝到手机的SD卡或者手机自身的存储上之后再使用,后来考虑到每次都拷贝的话效率不高,并且如果涉及到对数据库的修改操作的话拷贝之后数据就被恢复了. 因此就写了该封装,该封装只是在第一次使用数据库文件的时候把该文

  • Android中文件读写(输入流和输出流)操作小结

    1. Android中文件读写的原理: (1).所有文件的储存都是字节的储存. (2).在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘. (3).在读取文件(特别是文本文件)时,也是一个字节一个字节的读取以形成字节序列. 2. 字节流和字符流的区别: (1).字节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,字符流就可以. (2).字节流转换成字符流可以用InputStreamReader,OutputStreamWriter. 一般我们在

  • Android 文件操作详解及简单实例

     Android 文件操作详解 Android 的文件操作说白了就是Java的文件操作的处理.所以如果对Java的io文件操作比较熟悉的话,android的文件操作就是小菜一碟了.好了,话不多说,开始今天的正题吧. 先从一个小项目入门吧 首先是一个布局文件,这一点比较的简单,那就直接上代码吧. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="htt

  • Android编程之文件读写操作与技巧总结【经典收藏】

    本文实例总结了Android文件读写操作.分享给大家供大家参考,具体如下: 在Android中的文件放在不同位置,它们的读取方式也有一些不同. 本文对android中对资源文件的读取.数据区文件的读取.SD卡文件的读取及RandomAccessFile的方式和方法进行了整理.供参考. 一.资源文件的读取: 1) 从resource的raw中读取文件数据: String res = ""; try{ //得到资源中的Raw数据流 InputStream in = getResources

  • android文件操作——读取assets和raw文件下的内容

    来自Resources和Assets 中的文件只可以读取而不能进行写的操作. assets文件夹里面的文件都是保持原始的文件格式,需要用AssetManager以字节流的形式读取文件. 1. 先在Activity里面调用getAssets() 来获取AssetManager引用. 2. 再用AssetManager的open(String fileName, int accessMode) 方法则指定读取的文件以及访问模式就能得到输入流InputStream. 3. 然后就是用已经open fi

随机推荐