Android APP's interaction with the media storage service

  • 2020-05-17 06:31:42
  • OfStack

Brief introduction:
This article describes how the developer's APP USES the media storage service in Android (including MediaScanner, MediaProvider, and media information parsing), including how to update new or modified APP files to the media database, how to hide files generated by APP in multimedia applications, how to listen for changes in the media database, and so on.
Android native has a set of media storage service, the process name is android.process.media, which is mainly responsible for saving the file information in the disk to the database for other APP and MTP mode. So APP can quickly find out how much music is on the machine at any time, including the length of the music, the title, the artist, and the album cover. Here's how we developed APP to deal with this media storage service.
Note: the MTP pattern was introduced with Android 3.0, and its data is derived from media storage services.
Hide multimedia files
Application scenario: APP generates a picture/music/video class file and does not want it displayed in the gallery/music player. There are a lot of games out there where the images and sound files are not hidden and appear in the user's gallery/music player, causing disgust. If the user removes it, it may affect the normal operation of APP.

Method 1: make the file hidden. In Linux, the dot before the file is hidden. For example, "file A" is changed to ".file A ". Or remove the file extension so that the media storage service does not scan it as a multimedia file.
Method 2: generate a blank file named ".nomedia "in the folder. All files in the same folder will not be treated as multimedia files.

Add/modify multimedia files
Application scenario: APP creates a new multimedia file or modifies an existing one. For example, APP has downloaded a music file and needs to notify the media storage service so that the user can see the file in the music player. Otherwise, only the next time the media storage service starts scanning the entire disk will it find the new files generated by APP.

Method 1
If you only have one file and don't need to get the results back, just send Intent to the media storage service.


import java.io.File;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
private static void requestScanFile(Context context, File file) {
    Intent i = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    i.setData(Uri.fromFile(file));
    context.sendBroadcast(i);
}
private static void requestScanFile(Context context, String file) {
    Intent i = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    i.setData(Uri.parse("file://" + Uri.encode(file)));
    context.sendBroadcast(i);
}

Note: if Uri.parse () is used to generate Uri from a filename, the filename must first be Uri.encode () to escape the reserved character. For example, if the file name contains "?" , Uri.encode will be taken as a query parameter if it does not escape through uri.getPath (), so the file path obtained by uri.getPath () will be lost. The rest of it.

Method 2
Use the callback function if there is only one file and you need the file uri result to return.


import android.media.MediaScannerConnection;
import android.net.Uri;
private void requestScanFile(Context context, String file) {
    MediaScannerConnection.scanFile(context, new String[] {file}, 
        null, // mime types May not specify 
        mListener);
}
MediaScannerConnection.OnScanCompletedListener mListener = 
        new MediaScannerConnection.OnScanCompletedListener() {
    public void onScanCompleted(String path, Uri uri) {
        // TODO:  Gets the file in the multimedia database  uri Under the, 1 Step action 
    }
};

Note: another method is to insert a record containing the file path into the multimedia database, and then get uri. Then use method 1 to notify the media storage service to scan the file and complete the file information, such as the album name. However, this approach is not recommended because the file information is incomplete when obtaining uri.

Methods 3
If there are more files, send Intent to inform the media storage service to scan the entire disk. This is not particularly good, but no better interface has been found. Third party file management such as "ES file manager" USES this approach.


import android.content.Context;
import android.content.Intent;
import android.net.Uri;
private static void requestScanDisk(Context context) {
    Intent i = new Intent(Intent.ACTION_MEDIA_MOUNTED);
    String path = Environment.getExternalStorageDirectory().getPath();
    i.setData(Uri.parse("file://" + Uri.encode(path)));
    context.sendBroadcast(i);
}

Monitor data changes
Application scenario: the multimedia database has changed, and the APP display interface needs to be refreshed. It is easy to understand that there are new, deleted or modified multimedia files in the disk. The APP interface should reflect these changes in real time and refresh the display interface.

Method 1
Listen for media store related Intent. After receiving Intent, query the database again once. We need to pay attention to the following Intent:
1, Intent.ACTION_MEDIA_SCANNER_FINISHED: the media storage service will send this Intent when it has scanned the entire disk. There may be more files added or deleted.
2, Intent.ACTION_MEDIA_SCANNER_SCAN_FILE: the media storage service scans a single file.


import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
private void registerReceiver(Context context) {
    IntentFilter filter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
    filter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
    filter.addAction(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    filter.addDataScheme("file");
    context.registerReceiver(mReceiver, filter);
}
private BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        Uri uri = intent.getData();
        if (uri != null && uri.getScheme().equals("file")) {
            Log.v("Receiver", "BroadcastReceiver action = " + action + ", uri = " + uri);
            if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action)) {
                String filePath = uri.getPath();
                // TODO: filePath  The file has changed, APP  Refresh the interface 
            } else if (Intent.ACTION_MEDIA_SCANNER_FINISHED.equals(action)) {
                // TODO:  The whole disk scan is done, APP  Refresh the interface 
            }
        }
    }
};

In addition, during the time between Intent.MEDIA_SCANNER_STARTED and Intent.ACTION_MEDIA_SCANNER_FINISHED, the media storage service is scanning the file and the database will change, so the query results will only be accurate after Intent.ACTION_MEDIA_SCANNER_FINISHED is received. If you want to check whether the media storage service is scanning, you can use the following methods:

import android.content.ContentResolver;
import android.database.Cursor;
import android.os.Environment;
import android.provider.MediaStore;
private static boolean isMediaScannerScanning(ContentResolver cr) {
    Cursor cursor = null;
    try {
        cursor = cr.query(MediaStore.getMediaScannerUri(), new String[] {
            MediaStore.MEDIA_SCANNER_VOLUME}, null, null, null);

        if (cursor != null && cursor.getCount() > 0) {
            cursor.moveToFirst();
            return "external".equals(cursor.getString(0));
        }
    } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
    return false;
}

Note: APP may also need to listen for changes to the storage device, such as the SD card being pulled out, the disk being mounted out (USB mass storage mode), etc. These may require the file display screen to be cleared, or the program to be exited. The definition of Intent may vary slightly from phone to phone, but it is basically the following:
1, Intent. ACTION_MEDIA_EJECT: the storage device is normally removed, e.g. uninstall the storage in the Settings.
2. Intent. ACTION_MEDIA_UNMOUNTED: the storage device is normally unloaded, which usually occurs successively with EJECT.
3, Intent. ACTION_MEDIA_BAD_REMOVAL: abnormal removal of storage devices, such as hard plugging SD card.

Method 2
Listen for database changes. If you need to be able to receive notifications in real time when the database changes, you can use ContentObserver.


import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
private ContentObserver mContentObserver = new ContentObserver(null) {
    @Override
    public void onChange(boolean selfChange) { //  Backwards compatible 
        onChange(selfChange, null);
    }

    public void onChange(boolean selfChange, Uri uri) {
        // TODO:  The data has changed, APP  Requery the database and refresh the interface 
    }
};
private void setupCursor(Context context, Cursor c) {
    c.unregisterContentObserver(mContentObserver); // c  Is the data to be displayed 
}

The same can be done with CursorAdapter and the ListView bindings shown. When Cursor content finds a change, ListView will automatically refresh accordingly.


Related articles: