這篇文章從系統原始碼分析,講述如何將程式創建的多媒體檔案加入系統的媒體庫,如何從媒體庫刪除,以及大多數程式開發者經常遇到的無法添加到媒體庫的問題等。本人將透過對原始碼的分析,一一解釋這些問題。
Android中的多媒體檔案掃描機制
Android提供了一個很棒的程式來處理將多媒體檔案加入的媒體庫中。這個程式就是MediaProvider,現在我們簡單看以下這個程式。首先來看看它的Receiver
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。
MediaScannerReciever如何處理Intent
1.當且僅當接收到action android.intent.action.BOOT_COMPLETED才掃描內部儲存(非內建和外置sdcard)
2.除了action為android.intent.action.BOOT_COMPLE 的以外的intent都必須要有資料傳遞。
3.當收到 Intent.ACTION_MEDIA_MOUNTED intent,掃描Sdcard
4.收到 Intent.ACTION_MEDIA_SCANNER_SCAN_FILE intent,偵測沒有問題,將掃描單一檔案。
MediaScannerService如何運作
其實MediaScannerReceiver並不是真正處理掃描工作,它會啟動一個叫做MediaScannerService的服務。我們繼續看MediaProvider的manifest中關於service的部分。
1 2 3 4 5 | <service android:name="MediaScannerService" android:exported="true">
<intent-filter>
<action android:name="android.media.IMediaScannerService" />
</intent-filter>
</service>
|
登入後複製
MediaScannerService中的scanFile方法
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);
}
|
登入後複製
MediaScannerService中的scan方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | private void scan(String[] directories, String volumeName) {
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:
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方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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;
}
|
登入後複製
MediaScannerService中的createMediaScanner方法。庫。
最簡單的方式
只需要發送一個正確的intent廣播到MediaScannerReceiver即可。
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);
|
登入後複製
上面的極簡方法大多數情況下正常工作,但是有些情況下是不會工作的,稍後的部分會介紹。即使你使用上述方法成功了,還是建議你繼續閱讀稍後的為什麼發廣播不成功的部分。
使用MediaScannerConnection
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)開始引入。
建立一個MediaScannerConnection物件然後呼叫scanFile方法
很簡單,參考http://developer.android.com/reference/android/media/MediaScannerConnection.html
如何掃描多個檔案
1.發送多個檔案.ACTION_MEDIA_SCANNER_SCAN_FILE廣播
2.使用MediaScannerConnection,傳入要加入的路徑的陣列。
為什麼發送MEDIA_SCANNER_SCAN_FILE廣播不生效
關於為什麼有些設備上不生效,很多人認為是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 | public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Uri uri = intent.getData();
if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
scan(context, MediaProvider.INTERNAL_VOLUME);
} else {
if (uri.getScheme().equals("file")) {
String path = uri.getPath();
String externalStoragePath = Environment.getExternalStorageDirectory().getPath();
Log.d(TAG, "action: " + action + " path: " + path);
if (action.equals(Intent.ACTION_MEDIA_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:
|
登入後複製
上述輸出分析,你發送的廣播,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 | 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;
}
long lastModifiedSeconds = file.lastModified() / 1000;
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});
}
|
登入後複製
上述程式碼是可以運作的,直接從MediaProvider刪除即可。 具體的刪除代碼請參考Code Snippet for Media on Android
One More Thing
更多Android中掃描多媒體文件操作詳解相關文章請關注PHP中文網!