0
点赞
收藏
分享

微信扫一扫

Scan Media Files in Android

胡桑_b06e 2022-09-05 阅读 24


I once tried to use MediaScanner to resolve problems; however it turned out to be a failure. Now I make it.This post is to write down why I failed and how I work it out now. I think it could be deeper that other posts.

Android Media Scanning Mechanism

Android provides a great application for developers to add created media files to add them into the library. The application is called MediaProvider. Now let’s have a glance of MediaProvider.
The receiver part of its manifest



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>

The MediaScannerReceiver will receive the above intents with right action and data scheme.

How the MediaScannerRecieve handles the intent

  • It will scan internal storage only after receiving theaction android.intent.action.BOOT_COMPLETED
  • All intent but theandroid.intent.action.BOOT_COMPLETEDintent should carry the file scheme data
  • It will scan external storage when receiving theIntent.ACTION_MEDIA_MOUNTEDintent.
  • It will scan the single file when receiving theIntent.ACTION_MEDIA_SCANNER_SCAN_FILEintent.

How the MediaScannerService works

Actually receiver does not do scanning. It will start a service called MediaScannerService. The service part of its manifest

1
2
3
4
5

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

scanFile Method



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);}

scan Method



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 scanningmWakeLock.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();}

Actually the scan code is not really in the MediaScannerService



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;}

It’s using the android.media.MediaScanner ​​https://android.googlesource.com/platform/frameworks/base/+/cd92588/media/java/android/media/MediaScanner.java​​

How To Scan A Created File

Now I am going to introduce two ways to add a created file into the media library.

The Simplest Method

Just send a broadcast, as we have posted above. Just send a broadcast intent to 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);

The above method maybe has been seen thousands of times. Actually it should work. However I failed and failed in sending broadcast intent. In the following section. I will point out why the sending broadcast not works. Even though you get well on with sending broadcast,it’s strongly recommended to read the Section Why Sending MEDIA_SCANNER_SCAN_FILE broadcast not works.

Use 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() {@Overridepublic void onScanCompleted(String path, Uri uri) {Log.v("MediaScanWork", "file " + path+ " was scanned seccessfully: " + uri);}});}

the scanFile method is introduced since API 8

Create an instance and call scanFile (String path, String mimeType)

It’s really easy, just read the post ​​http://developer.android.com/reference/android/media/MediaScannerConnection.html​​

How To Scan Mutiple Files

  • Sending Mutiple Intent.ACTION_MEDIA_SCANNER_SCAN_FILE broadcast intents.
  • Use the second method by filled the second params with an array of paths.

Why Sending MEDIA_SCANNER_SCAN_FILE broadcast not works

Actually someone may think sending ACTION_MEDIA_SCANNER_SCAN_FILE works on some devices but not on other devices. Actually it’s. Is it a API limit?
No, It’s has someting to do with your file path. Take a look at this



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 storagescan(context, MediaProvider.INTERNAL_VOLUME);} else {if (uri.getScheme().equals("file")) {// handle intents related to external storageString 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 mountedscan(context, MediaProvider.EXTERNAL_VOLUME);} else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) &&path != null && path.startsWith(externalStoragePath + "/")) {scanFile(context, path);}}}}

Every part is right except the intent data. I mean the file path. You may hardcode the filepath. This is my example



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);

And this is the output log



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

So in the sending broadcast, your action is right, your data schema OK, your data path not null;but your path /sdcard/1390136305831_add.png does not startswith the externalStoragePath /mnt/sdcard/ And so the scan file is actually not called. In conclusion your hardcoding path results in the failure.

Remove From Media Library

If we deleted a file ,it means that we need to remove the file from the media library.

Simply sending a broadcast?

Can we simple sending a broadcast to the MediaScannerReceiver? I also wish it could. But actually it does not work. Look at this code for the explanation.



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 filepublic 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 filesreturn 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;}}

As the above code points out, It does have a check before the real scanning. Then how should I do?



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});
}

The above code works. Just remove from Media Provider

Special

  • You could check the external.db or internal.db file under /data/data/com.android.providers.media/ for more detailed information.

Others

  • ​​Professional Android 4 Application Development​​
  • Scan Media Files in Android_android

  • ​​Android Hacker’s Handbook​​
  • Scan Media Files in Android_ide_02

举报

相关推荐

0 条评论