转载:http://droidyue.com/blog/2014/07/12/scan-media-files-in-android-chinese-edition/html
这篇文章从系统源代码分析,讲述如何将程序建立的多媒体文件加入系统的媒体库,如何从媒体库删除,以及大多数程序开发者常常遇到的没法添加到媒体库的问题等。本人将经过对源代码的分析,一一解释这些问题。java
Android提供了一个很棒的程序来处理将多媒体文件加入的媒体库中。这个程序就是MediaProvider,如今咱们简单看如下这个程序。首先看一下它的Receiverandroid
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<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。数组
实际上MediaScannerReceiver并非真正处理扫描工做,它会启动一个叫作MediaScannerService的服务。咱们继续看MediaProvider的manifest中关于service的部分。bash
1
2 3 4 5 |
<service android:name="MediaScannerService" android:exported="true"> <intent-filter> <action android:name="android.media.IMediaScannerService" /> </intent-filter> </service> |
1
2 3 4 5 6 |
private Uri scanFile(String path, String mimeType) { String volumeName = MediaProvider.EXTERNAL_VOLUME; openDatabase(volumeName); MediaScanner scanner = createMediaScanner(); return scanner.scanSingleFile(path, volumeName, mimeType); } |
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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(); } |
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
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 具体扫描过程就请点击左侧连接查看。ide
这里介绍两种方式来实现将新建立的文件加入媒体库。ui
只须要发送一个正确的intent广播到MediaScannerReceiver便可。this
1
2 3 4 |
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); |
上面的极简方法大多数状况下正常工做,可是有些状况下是不会工做的,稍后的部分会介绍。即便你使用上述方法成功了,仍是建议你继续阅读稍后的为何发广播不成功的部分。google
1
2 3 4 5 6 7 8 9 10 11 |
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)开始引入。编码
很简单,参考http://developer.android.com/reference/android/media/MediaScannerConnection.html
关于为何有些设备上不生效,不少人认为是API缘由,其实不是的,这其实和你传入的文件路径有关系。看一下接收者Receiver的onReceive代码。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
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 + "/")
,这里我举一个简单的小例子。
1
2 3 4 5 6 7 8 9 10 |
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); |
咱们看一下输出日志,分析缘由。
1
|
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/开头。因此扫描操做没有开始,致使文件没有加入到媒体库。因此,请检查文件的路径。
若是咱们删除一个多媒体文件的话,也就意味咱们还须要将这个文件从媒体库中删除掉。
仅仅发一个广播能解决问题么?我却是但愿能够,可是其实是不工做的,查看以下代码便可明白。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// 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; } } |
正如上述代码,会对文件是否存在进行检查,若是文件不存在,直接中止向下执行。因此这样是不行的。那怎么办呢?
1
2 3 4 5 6 7 8 |
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}); } |