SoFunction
Updated on 2025-03-09

Interaction between Android APP and media storage services

Introduction:
This article introduces how to use media storage services for developers' APPs in Android (including MediaScanner, MediaProvider, and media information parsing, etc.), including how to update the newly added or modified files of the APP to the media database, how to hide files generated by the APP in multimedia applications, how to monitor changes in the media database, etc.
Android has a set of media storage services natively, with the process name , which is mainly responsible for saving file information on disk to the database for use by other APPs and MTP mode. Therefore, the APP can quickly check how much music there is on the machine at any time, and the length of the music, title, artist, and album cover can be obtained. The following will introduce how the APP we developed deals with this media storage service.
Note: The MTP mode was introduced in Android 3.0, and its data comes from media storage services.
Hide multimedia files
Application scenario: The APP has generated image/music/video files and does not want them to be displayed in the gallery/music player. There are many games on the market, and their pictures and sound effects files are not hidden, and they appear in the user's gallery/music player, causing users to be disgusted. If the user deletes it, it may affect the normal operation of the APP.

Method 1: Set the file to hide. Adding a dot before a file in Linux is to hide it, for example, changing "File A" to ".File A". Or remove the file extension so that the media storage service will not treat it as a multimedia file when scanning.
Method 2: Generate a blank file named ".nomedia" in the folder. In this way, all files in the same folder will not be treated as multimedia files.

Add/modify multimedia files
Application scenario: The APP creates a new multimedia file, or modifys an existing multimedia file. For example, the APP downloads a music file and needs to notify the media storage service, so that the user can see the file in the music player. Otherwise, the next time the media storage service starts scanning the entire disk, new files generated by the APP will be found.

Method 1
If there is only one file and you do not need to get the result returned, just send an Intent to notify the media storage service.

Copy the codeThe code is as follows:

import ;
import ;
import ;
import ;

private static void requestScanFile(Context context, File file) {
    Intent i = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    ((file));
    (i);
}

private static void requestScanFile(Context context, String file) {
    Intent i = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    (("file://" + (file)));
    (i);
}


Note: If you use () to generate Uri from a file name, the file name must first (), which is used to escape the reserved characters. For example, if the file name contains "?", it will be regarded as a query parameter without being escaped, so that the file path obtained by () will lose the part after "?".

Method 2
If there is only one file and the file uri result is required to return, the callback function is used.

Copy the codeThe code is as follows:

import ;
import ;

private void requestScanFile(Context context, String file) {
    (context, new String[] {file},
null, // mime types, can not be specified
        mListener);
}

mListener =
        new () {
    public void onScanCompleted(String path, Uri uri) {
// TODO: Get the uri of the file in the multimedia database and perform the next action
    }
};


Note: There is another method, which can first insert a record containing the file path into the multimedia database, and after inserting it, you can get its uri; then use the method one to notify the media storage service to scan the file and add the file information (such as the album name). However, this method is not recommended because the file information is not complete when obtaining the uri.

Method 3
If there are many files, send an Intent notification to the Media Storage Service to scan the entire disk. This approach is not particularly good, but no other better interfaces are found. Third-party file management such as "ES File Manager" uses this method.

Copy the codeThe code is as follows:

import ;
import ;
import ;

private static void requestScanDisk(Context context) {
    Intent i = new Intent(Intent.ACTION_MEDIA_MOUNTED);
    String path = ().getPath();
    (("file://" + (path)));
    (i);
}

Listen to data changes
Application scenario: There are changes in the multimedia database, and the APP display interface needs to be refreshed. It is easier to understand. There are new, deleted or modified multimedia files on disk. The APP interface should reflect these changes in real time and refresh the display interface.

Method 1
Listen to media storage related intents. After receiving the Intent, re-query the database. The main Intents we need to pay attention to are the following:
1. Intent.ACTION_MEDIA_SCANNER_FINISHED: This Intent will be sent after the media storage service scans the entire disk. There may be more new files or deleted.
2. Intent.ACTION_MEDIA_SCANNER_SCAN_FILE: The media storage service scans a single file.

Copy the codeThe code is as follows:

import ;
import ;
import ;
import ;
import ;

private void registerReceiver(Context context) {
    IntentFilter filter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED);
    (Intent.ACTION_MEDIA_SCANNER_FINISHED);
    (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    ("file");
    (mReceiver, filter);
}

private BroadcastReceiver mReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = ();
        Uri uri = ();
        if (uri != null && ().equals("file")) {
            ("Receiver", "BroadcastReceiver action = " + action + ", uri = " + uri);
            if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action)) {
                String filePath = ();
// TODO: filePath file has been changed, APP refreshes the interface
            } else if (Intent.ACTION_MEDIA_SCANNER_FINISHED.equals(action)) {
// TODO: The entire disk scan is completed, the APP refreshes the interface
            }
        }
    }
};


In addition, the media storage service is scanning files during the time between Intent.MEDIA_SCANNER_STARTED and Intent.ACTION_MEDIA_SCANNER_FINISHED, the media storage service is scanning files and the database will change, so the query results are only accurate after receiving Intent.ACTION_MEDIA_SCANNER_FINISHED. If you want to detect whether the media storage service is scanning, you can use the following methods:
Copy the codeThe code is as follows:

import ;
import ;
import ;
import ;

private static boolean isMediaScannerScanning(ContentResolver cr) {
    Cursor cursor = null;
    try {
        cursor = ((), new String[] {
            MediaStore.MEDIA_SCANNER_VOLUME}, null, null, null);

        if (cursor != null && () > 0) {
            ();
            return "external".equals((0));
        }
    } finally {
        if (cursor != null) {
            ();
        }
    }

    return false;
}


Note: The APP may also need to monitor changes in storage devices, such as unplugging the SD card, mounting the disk (USB mass storage mode), etc. In these cases, the file display interface may be cleared or exited the program. Each phone may have a slightly different definition for each Intent, but it is basically the following:
1. Intent.ACTION_MEDIA_EJECT: The storage device is removed normally, such as uninstalling the memory in the settings.
2. Intent.ACTION_MEDIA_UNMOUNTED: The storage device is uninstalled normally, usually with EJECT.
3. Intent.ACTION_MEDIA_BAD_REMOVAL: Unusually removes storage devices, such as hard plug-in and unplug SD cards.

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

Copy the codeThe code is as follows:

import ;
import ;
import ;
import ;

private ContentObserver mContentObserver = new ContentObserver(null) {
    @Override
public void onChange(boolean selfChange) { // Backward compatibility
        onChange(selfChange, null);
    }

    public void onChange(boolean selfChange, Uri uri) {
// TODO: The data has changed, the APP requerys the database and refresh the interface
    }
};

private void setupCursor(Context context, Cursor c) {
(mContentObserver); // c is the data to be displayed
}


In addition, the same purpose can be achieved by using CursorAdapter and displayed ListView binding. When the Cursor content changes, the ListView will also automatically refresh accordingly.