Android中掃描多媒體文件操作詳解
這篇文章從系統(tǒng)源代碼分析,講述如何將程序創(chuàng)建的多媒體文件加入系統(tǒng)的媒體庫(kù),如何從媒體庫(kù)刪除,以及大多數(shù)程序開(kāi)發(fā)者經(jīng)常遇到的無(wú)法添加到媒體庫(kù)的問(wèn)題等。本人將通過(guò)對(duì)源代碼的分析,一一解釋這些問(wèn)題。
Android中的多媒體文件掃描機(jī)制
Android提供了一個(gè)很棒的程序來(lái)處理將多媒體文件加入的媒體庫(kù)中。這個(gè)程序就是MediaProvider,現(xiàn)在我們簡(jiǎn)單看以下這個(gè)程序。首先看一下它的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和數(shù)據(jù)規(guī)則正確的intent。
MediaScannerReciever如何處理Intent
1.當(dāng)且僅當(dāng)接收到action android.intent.action.BOOT_COMPLETED才掃描內(nèi)部存儲(chǔ)(非內(nèi)置和外置sdcard)
2.除了action為android.intent.action.BOOT_COMPLETED 的以外的intent都必須要有數(shù)據(jù)傳遞。
3.當(dāng)收到 Intent.ACTION_MEDIA_MOUNTED intent,掃描Sdcard
4.當(dāng)收到 Intent.ACTION_MEDIA_SCANNER_SCAN_FILE intent,檢測(cè)沒(méi)有問(wèn)題,將掃描單個(gè)文件。
MediaScannerService如何工作
實(shí)際上MediaScannerReceiver并不是真正處理掃描工作,它會(huì)啟動(dòng)一個(gè)叫做MediaScannerService的服務(wù)。我們繼續(xù)看MediaProvider的manifest中關(guān)于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;
}
從上面可以發(fā)現(xiàn),真正工作的其實(shí)是android.media.MediaScanner.java 具體掃描過(guò)程就請(qǐng)點(diǎn)擊左側(cè)鏈接查看。
如何掃描一個(gè)剛創(chuàng)建的文件
這里介紹兩種方式來(lái)實(shí)現(xiàn)將新創(chuàng)建的文件加入媒體庫(kù)。
最簡(jiǎn)單的方式
只需要發(fā)送一個(gè)正確的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);
上面的極簡(jiǎn)方法大多數(shù)情況下正常工作,但是有些情況下是不會(huì)工作的,稍后的部分會(huì)介紹。即使你使用上述方法成功了,還是建議你繼續(xù)閱讀稍后的為什么發(fā)廣播不成功的部分。
使用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)開(kāi)始引入。
創(chuàng)建一個(gè)MediaScannerConnection對(duì)象然后調(diào)用scanFile方法
很簡(jiǎn)單,參考http://developer.android.com/reference/android/media/MediaScannerConnection.html
如何掃描多個(gè)文件
1.發(fā)送多個(gè)Intent.ACTION_MEDIA_SCANNER_SCAN_FILE廣播
2.使用MediaScannerConnection,傳入要加入的路徑的數(shù)組。
為什么發(fā)送MEDIA_SCANNER_SCAN_FILE廣播不生效
關(guān)于為什么有些設(shè)備上不生效,很多人認(rèn)為是API原因,其實(shí)不是的,這其實(shí)和你傳入的文件路徑有關(guān)系??匆幌陆邮照逺eceiver的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);
}
}
}
}
所有的部分都正確除了傳入的路徑。因?yàn)槟憧赡苡簿幋a了文件路徑。因?yàn)橛幸粋€(gè)這樣的判斷path.startsWith(externalStoragePath + "/"),這里我舉一個(gè)簡(jiǎn)單的小例子。
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
上述輸出分析,你發(fā)送的廣播,action是正確的,數(shù)據(jù)規(guī)則也是正確的,而且你的文件路徑也是存在的,但是,文件的路徑/sdcard/1390136305831_add.png并不是以外部存儲(chǔ)根路徑/mnt/sdcard/開(kāi)頭。所以掃描操作沒(méi)有開(kāi)始,導(dǎo)致文件沒(méi)有加入到媒體庫(kù)。所以,請(qǐng)檢查文件的路徑。
如何從多媒體庫(kù)中移除
如果我們刪除一個(gè)多媒體文件的話,也就意味我們還需要將這個(gè)文件從媒體庫(kù)中刪除掉。
能不能簡(jiǎn)簡(jiǎn)單單發(fā)廣播?
僅僅發(fā)一個(gè)廣播能解決問(wèn)題么?我倒是希望可以,但是實(shí)際上是不工作的,查看如下代碼即可明白。
// 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;
}
}
正如上述代碼,會(huì)對(duì)文件是否存在進(jìn)行檢查,如果文件不存在,直接停止向下執(zhí)行。所以這樣是不行的。那怎么辦呢?
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刪除即可。 具體的刪除代碼請(qǐng)參考Code Snippet for Media on Android
One More Thing
你可以通過(guò)查看/data/data/com.android.providers.media/databases/external.db(不同系統(tǒng)略有不同)文件可以了解更多的信息。
- Android實(shí)現(xiàn)二維碼掃描和生成的簡(jiǎn)單方法
- Android開(kāi)發(fā)框架之自定義ZXing二維碼掃描界面并解決取景框拉伸問(wèn)題
- Android音樂(lè)播放器制作 掃描本地音樂(lè)顯示在手機(jī)(一)
- Android設(shè)備獲取掃碼槍掃描的內(nèi)容與可能遇到的問(wèn)題解決
- Android編程實(shí)現(xiàn)wifi掃描及連接的方法
- Android studio 實(shí)現(xiàn)手機(jī)掃描二維碼功能
- Android應(yīng)用中使用ContentProvider掃描本地圖片并顯示
- Android 二維碼掃描和生成二維碼功能
- 詳解Android 掃描條形碼(Zxing插件)
- Android自定義View實(shí)現(xiàn)掃描效果
相關(guān)文章
Android自定義View實(shí)現(xiàn)比賽時(shí)間閃動(dòng)效果
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)比賽時(shí)間閃動(dòng)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-03-03android: targetSdkVersion升級(jí)中Only fullscreen activities can r
這篇文章主要給大家介紹了關(guān)于Android target SDK和build tool版本升級(jí)中遇到Only fullscreen activities can request orientation問(wèn)題的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2018-09-09Android RecyclerView自定義上拉和下拉刷新效果
這篇文章主要為大家詳細(xì)介紹了Android RecyclerView自定義上拉和下拉刷新效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02Android基于ViewPager+Fragment實(shí)現(xiàn)左右滑屏效果的方法
這篇文章主要介紹了Android基于ViewPager+Fragment實(shí)現(xiàn)左右滑屏效果的方法,結(jié)合實(shí)例形式分析了Android實(shí)現(xiàn)滑屏效果的布局與滑動(dòng)功能相關(guān)操作技巧,需要的朋友可以參考下2017-07-07Android藍(lán)牙服務(wù)啟動(dòng)流程分析探索
這篇文章主要介紹了Android藍(lán)牙服務(wù)啟動(dòng)流程,了解內(nèi)部原理是為了幫助我們做擴(kuò)展,同時(shí)也是驗(yàn)證了一個(gè)人的學(xué)習(xí)能力,如果你想讓自己的職業(yè)道路更上一層樓,這些底層的東西你是必須要會(huì)的2023-01-01如何利用Flutter實(shí)現(xiàn)酷狗流暢Tabbar效果
這篇文章主要給大家介紹了關(guān)于如何利用Flutter實(shí)現(xiàn)酷狗流暢Tabbar效果的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-02-02Android高仿京東垂直循環(huán)滾動(dòng)新聞欄
通過(guò)自定義的LinearLayout,并且textView能夠循環(huán)垂直滾動(dòng),而且條目可以點(diǎn)擊,顯示區(qū)域最多顯示2個(gè)條目,并且還有交替的屬性垂直移動(dòng)的動(dòng)畫(huà)效果,通過(guò)線程來(lái)控制滾動(dòng)的實(shí)現(xiàn)2016-03-03Android通過(guò)SharedPreferences實(shí)現(xiàn)自動(dòng)登錄記住用戶名和密碼功能
最近使用SharedPreferences實(shí)現(xiàn)了一個(gè)android自動(dòng)登錄功能,特此分享到腳本之家平臺(tái)供大家參考2017-07-07