Architecture
Figure 2-1As Figure 2-1. MediaScannerReciver start up at anytime where receive intent ACTION_BOOT_COMPLETED, ACTION_MEDIA_MOUNTED or ACTION_MEDIA_SCANNER_SCAN_FILE. Cause on that spend long time to process the media metadata, so that MediaScannerReceiver will call up MediaScannerService.MediaScannerService invoke a public class which named MediaScanner to do the scan work, MediaScanner handle the media database with a public class which named MediaProviderMediaScannerReciver support two types of the folder: 1.internal volume, point to $(ANDROID_ROOT)/media. 2.External volume, point $(EXTERNAL_STORAGE).Scanner Action
ACTION_BOOT_COMPLETEDpublic static final String ACTION_BOOT_COMPLETEDBroadcast Action: This is broadcast once, after the system has finished booting. It can be used to perform application-specific initialization, such as installing alarms. You must hold theRECEIVE_BOOT_COMPLETED permission in order to receive this broadcast.This is a protected intent that can only be sent by the system.Constant Value: "android.intent.action.BOOT_COMPLETED"ACTION_MEDIA_MOUNTEDpublic static final String ACTION_MEDIA_MOUNTEDBroadcast Action: External media is present and mounted at its mount point. The path to the mount point for the removed media is contained in the Intent.mData field. The Intent contains an extra with name "read-only" and Boolean value to indicate if the media was mounted read only.Constant Value: "android.intent.action.MEDIA_MOUNTED"ACTION_MEDIA_SCANNER_SCAN_FILE.public static final String ACTION_MEDIA_SCANNER_SCAN_FILEBroadcast Action: Request the media scanner to scan a file and add it to the media database. The path to the file is contained in the Intent.mData field.Constant Value: "android.intent.action.MEDIA_SCANNER_SCAN_FILE"Android Media Scanner Receiver
We can find the source file with path anydroid/packages/providers/MediaProvider/src/com/android/providers/media/MediaScannerReceiver.javapublic class MediaScannerReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { ......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(); if (action.equals(Intent.ACTION_MEDIA_MOUNTED) && externalStoragePath.equals(path)) { scan(context, MediaProvider.EXTERNAL_VOLUME); } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) && path != null && path.startsWith(externalStoragePath + "/")) { scanFile(context, path); } } } } private void scan(Context context, String volume) { Bundle args = new Bundle(); args.putString("volume", volume); context.startService( new Intent(context, MediaScannerService.class).putExtras(args)); } private void scanFile(Context context, String path) { Bundle args = new Bundle(); args.putString("filepath", path); context.startService( new Intent(context, MediaScannerService.class).putExtras(args)); } }
Figure 4-1As the source codes, and Figure 4-1, there are two different media database in the system, one is the internal storage , the other is the external storage. Finally, they call the method startService() to start the MediaScannerService.Android Media Scanner ServiceWe can find the source file with path /android/packages/providers/MediaProvider/src/com/android/providers/media/MediaScannerService.java .public void onCreate() { ......Thread thr = new Thread(null, this, "MediaScannerService"); thr.start(); } public int onStartCommand(Intent intent, int flags, int startId) { ......Message msg = mServiceHandler.obtainMessage(); ......mServiceHandler.sendMessage(msg); return Service.START_REDELIVER_INTENT; } public void run() { ......mServiceHandler = new ServiceHandler(); ......}private final class ServiceHandler extends Handler {......@Override public void handleMessage(Message msg) {......if (filePath != null) { ......Uri uri = scanFile(filePath, arguments.getString("mimetype")); ...... } else { ......scan(directories, volume); ......} }}private void scan(String[] directories, String volumeName) {......MediaScanner scanner = createMediaScanner(); scanner.scanDirectories(directories, volumeName); ......}private Uri scanFile(String path, String mimeType) { ...... MediaScanner scanner = createMediaScanner(); return scanner.scanSingleFile(path, volumeName, mimeType); }
Figure 5-1 Android application maybe block with invoking service, generic create a thread to run at the backend. First, media service call onCreate() to start the service if it is not exist, then create a thread and run thread.start() to call the runnable method which has implemented with the run(). In the run(), invoke a internal class named ServiceHandler to scan file. In the method scan() and scanFile(), they all invoke a public class named MediaScanner whom has method named createMediaScanner() to process metadata and media dabase. Android Media ScannerWe can find the relate files with path android/frameworks/base/media/java/android/media,
Figure 6-1 java codesfile://android/frameworks/base/media/java/android/media/MediaScanner.javapublic void scanDirectories(String[] directories, String volumeName) { ......for (int i = 0; i < directories.length; i++) { processDirectory(directories[i], MediaFile.sFileExtensions, mClient); } ......} } public void scanFile(String path, long lastModified, long fileSize) { // This is the callback funtion from native codes. // Log.v(TAG, "scanFile: "+path); doScanFile(path, null, lastModified, fileSize, false); } public void scanFile(String path, String mimeType, long lastModified, long fileSize) { doScanFile(path, mimeType, lastModified, fileSize, false); } public Uri doScanFile(String path, String mimeType, long lastModified, long fileSize, boolean scanAlways) {......if( isMetadataSupported(mFileType) ) { processFile(path, mimeType, this); } else if (MediaFile.isImageFileType(mFileType)) { // we used to compute the width and height but it's not worth it } result = endFile(entry, ringtones, notifications, alarms, music, podcasts); ......} private Uri endFile(FileCacheEntry entry, boolean ringtones, boolean notifications, boolean alarms, boolean music, boolean podcasts) throws RemoteException { ......mMediaProvider.insert(...) // mMediaProvider.update(...)......} private native void processDirectory(String path, String extensions, MediaScannerClient client);private native void processFile(String path, String mimeType, MediaScannerClient client); c++ codesfile://android/frameworks/base/media/jni/android_media_MediaScanner.cppstatic void android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, jobject client) { MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context); ......MyMediaScannerClient myClient(env, client); mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env); ......} static void android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client) { MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context); ......MyMediaScannerClient myClient(env, client); mp->processFile(pathStr, mimeTypeStr, myClient); ......} file://android/external/opencore/android/mediascanner.cppstatus_t MediaScanner::processDirectory(const char *path, const char* extensions, MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv) { ......result = doProcessDirectory(pathBuffer, pathRemaining, extensions, client, exceptionCheck, exceptionEnv);......} status_t MediaScanner::doProcessDirectory(char *path, int pathRemaining, const char* extensions, MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv) { ......client.scanFile(path, statbuf.st_mtime, statbuf.st_size);......} status_t MediaScanner::processFile(const char *path, const char* mimeType, MediaScannerClient& client) { status_t result = PVMFSuccess; int error = 0; InitializeForThread(); OSCL_TRY(error, client.setLocale(mLocale); client.beginFile(); //LOGD("processFile %s mimeType: %s/n", path, mimeType); const char* extension = strrchr(path, '.'); if (extension && (strcasecmp(extension, ".mp3") == 0 || strcasecmp(extension, ".aac") == 0)) { // Both mp3 and aac files use ID3 tags to hold metadata result = parseID3Tag(path, client); } else if (extension && (strcasecmp(extension, ".mp4") == 0 || strcasecmp(extension, ".m4a") == 0 || strcasecmp(extension, ".3gp") == 0 || strcasecmp(extension, ".3gpp") == 0 || strcasecmp(extension, ".3g2") == 0 || strcasecmp(extension, ".m4b") == 0 || strcasecmp(extension, ".3gpp2") == 0)) { result = parseMP4(path, client); } else if (extension && strcasecmp(extension, ".ogg") == 0) { result = parseOgg(path, client); } else if (extension && (strcasecmp(extension, ".mid") == 0 || strcasecmp(extension, ".smf") == 0 || strcasecmp(extension, ".imy") == 0)) { result = parseMidi(path, client); } else if (extension && (strcasecmp(extension, ".wma") == 0 || strcasecmp(extension, ".wmv") == 0 || strcasecmp(extension, ".asf") == 0 || strcasecmp(extension, ".amr") == 0 || strcasecmp(extension, ".wav") == 0 || strcasecmp(extension, ".awb") == 0)) { result = parseASF(path, client); } else { result = PVMFFailure; } client.endFile(); ); OSCL_FIRST_CATCH_ANY( error,LOGV("OSCL_LEAVE happened in processFile Exit with failure");return PVMFFailure); return result; } As Figure 6-1, If scanDirectory() has called by MediaScannerService, it will invoke c++ llibrary libmedia_jni method processDiretorys() by JNI mechanism, then processDiretorys() invoke JAVA class MyMediaScannerClient by JNI, Finaly, endFile() use to insert or update the database.