插入u盘后,系统会发送广播android.intent.action.MEDIA_MOUNTED
位于
packages/providers/MediaProvider/src/com/android/providers/media/MediaScannerService.java
MediaScannerService可以接收开机广播和u盘插入广播,收到广播后执行scan方法,将文件或文件夹路径存储到Bundle,并启动MediaScannerService,我们看MediaScannerService中ServiceHandler的代码

private final class ServiceHandler extends Handler    {        @Override        public void handleMessage(Message msg)        {            Bundle arguments = (Bundle) msg.obj;            String filePath = arguments.getString("filepath");//单个文件            String path = arguments.getString("path");//文件目录            try {                if (filePath != null) {                    IBinder binder = arguments.getIBinder("listener");                    IMediaScannerListener listener =                             (binder == null ? null : IMediaScannerListener.Stub.asInterface(binder));                    Uri uri = null;                    try {                        uri = scanFile(filePath, arguments.getString("mimetype"));//扫描单个文件                    } catch (Exception e) {                        Log.e(TAG, "Exception scanning file", e);                    }                    if (listener != null) {                        listener.scanCompleted(filePath, uri);                    }                } else {                    String volume = arguments.getString("volume");                    String[] directories = null;                    if (MediaProvider.INTERNAL_VOLUME.equals(volume)) {                        // scan internal media storage                        directories = new String[] {                                Environment.getRootDirectory() + "/media",                        };                    }                    else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {                        // scan external storage volumes            if (path == null) {                          directories = mExternalStoragePaths;            } else if (path.startsWith("/mnt/usb_storage")) {                directories = new String[] {path};//{"/mnt/usb_storage"};                        } else {                            directories = new String[] {path};            }                    }                    if (directories != null) {                        if (true) Log.d(TAG, "start scanning volume " + volume + ": "                                + Arrays.toString(directories));                        scan(directories, volume);//扫描文件列表                        if (true) Log.d(TAG, "done scanning volume " + volume);                    }                }            } catch (Exception e) {                Log.e(TAG, "Exception in handleMessage", e);            }            stopSelf(msg.arg1);        }    };

可以看到代码会调用scan或者scanFile扫描

    private void scan(String[] directories, String volumeName) {        Uri uri = Uri.parse("file://" + directories[0]);        // don't sleep while scanning        mWakeLock.acquire();        try {            ContentValues values = new ContentValues();            values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);            Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values);            sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));            try {                if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {                    openDatabase(volumeName);                }            MediaScanner scanner = createMediaScanner();        if(directories!=null){              scanner.scanDirectories(directories, volumeName);        }        } catch (Exception e) {            Log.e(TAG, "exception in MediaScanner.scan()", e);        }            getContentResolver().delete(scanUri, null, null);        } finally {            sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));            mWakeLock.release();        }    }
    private Uri scanFile(String path, String mimeType) {        String volumeName = MediaProvider.EXTERNAL_VOLUME;        openDatabase(volumeName);        MediaScanner scanner = createMediaScanner();        try {            // make sure the file path is in canonical form            String canonicalPath = new File(path).getCanonicalPath();            return scanner.scanSingleFile(canonicalPath, volumeName, mimeType);        } catch (Exception e) {            Log.e(TAG, "bad path " + path + " in scanFile()", e);            return null;        }    }

下一步进入到MediaScanner.java,文件在

frameworks/base/media/java/android/media/MediaScanner.java

下面是扫描单个文件的代码

    // 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;        }    }//初始化默认的uriprivate void initialize(String volumeName) {        mMediaProvider = mContext.getContentResolver().acquireProvider("media");        mAudioUri = Audio.Media.getContentUri(volumeName);        mVideoUri = Video.Media.getContentUri(volumeName);        mImagesUri = Images.Media.getContentUri(volumeName);        mThumbsUri = Images.Thumbnails.getContentUri(volumeName);        mFilesUri = Files.getContentUri(volumeName);        mFilesUriNoNotify = mFilesUri.buildUpon().appendQueryParameter("nonotify", "1").build();        if (!volumeName.equals("internal")) {            // we only support playlists on external media            mProcessPlaylists = true;            mProcessGenres = true;            mPlaylistsUri = Playlists.getContentUri(volumeName);            mCaseInsensitivePaths = true;        }    }//扫描预处理,暂时不知道做什么用private void prescan(String filePath, boolean prescanFiles) throws RemoteException {        Cursor c = null;        String where = null;        String[] selectionArgs = null;        if (mPlayLists == null) {            mPlayLists = new ArrayList();        } else {            mPlayLists.clear();        }        if (filePath != null) {            // query for only one file            where = MediaStore.Files.FileColumns._ID + ">?" +                " AND " + Files.FileColumns.DATA + " like '"+filePath+"%' ";            selectionArgs = new String[] { "" };        } else {            where = MediaStore.Files.FileColumns._ID + ">?";            selectionArgs = new String[] { "" };        }        if (DEBUG_MEDIASCANNER)            Log.d(TAG,"-----------enter prescan,filePath = "+filePath+"where = "+where);        // Tell the provider to not delete the file.        // If the file is truly gone the delete is unnecessary, and we want to avoid        // accidentally deleting files that are really there (this may happen if the        // filesystem is mounted and unmounted while the scanner is running).        Uri.Builder builder = mFilesUri.buildUpon();        builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false");        MediaBulkDeleter deleter = new MediaBulkDeleter(mMediaProvider, mPackageName,                builder.build());        // Build the list of files from the content provider        try {            if (prescanFiles) {                // First read existing files from the files table.                // Because we'll be deleting entries for missing files as we go,                // we need to query the database in small batches, to avoid problems                // with CursorWindow positioning.                long lastId = Long.MIN_VALUE;                Uri limitUri = mFilesUri.buildUpon().appendQueryParameter("limit", "1000").build();                mWasEmptyPriorToScan = true;                while (true) {                    selectionArgs[0] = "" + lastId;                    if (c != null) {                        c.close();                        c = null;                    }                    c = mMediaProvider.query(mPackageName, limitUri, FILES_PRESCAN_PROJECTION,                            where, selectionArgs, MediaStore.Files.FileColumns._ID, null);                    if (c == null) {                        break;                    }                    int num = c.getCount();                    if (num == 0) {                        break;                    }                    mWasEmptyPriorToScan = false;                    while (c.moveToNext()) {                        long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX);                        String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX);                        int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX);                        long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX);                        lastId = rowId;                        // Only consider entries with absolute path names.                        // This allows storing URIs in the database without the                        // media scanner removing them.                        if (path != null && path.startsWith("/")) {                            boolean exists = false;                            try {                                exists = Libcore.os.access(path, libcore.io.OsConstants.F_OK);                            } catch (ErrnoException e1) {                            }                            if (!exists && !MtpConstants.isAbstractObject(format)) {                                // do not delete missing playlists, since they may have been                                // modified by the user.                                // The user can delete them in the media player instead.                                // instead, clear the path and lastModified fields in the row                                MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path);                                int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType);                                if (!MediaFile.isPlayListFileType(fileType)) {                                    deleter.delete(rowId);                                    if (path.toLowerCase(Locale.US).endsWith("/.nomedia")) {                                        deleter.flush();                                        String parent = new File(path).getParent();                                        mMediaProvider.call(mPackageName, MediaStore.UNHIDE_CALL,                                                parent, null);                                    }                                }                            }                        }                    }                }            }        }        finally {            if (c != null) {                c.close();            }            deleter.flush();        }        // compute original size of images        mOriginalCount = 0;        c = mMediaProvider.query(mPackageName, mImagesUri, ID_PROJECTION, null, null, null, null);        if (c != null) {            mOriginalCount = c.getCount();            c.close();        }    }

下面是真正的扫描实现

public Uri doScanFile(String path, String mimeType, long lastModified,                long fileSize, boolean isDirectory, boolean scanAlways, boolean noMedia) {            Uri result = null;//            long t1 = System.currentTimeMillis();            try {                FileEntry entry = beginFile(path, mimeType, lastModified,                        fileSize, isDirectory, noMedia);                if (path.startsWith("/mnt/usb_storage"))                    mIsUsbStorage =true;                else                    mIsUsbStorage =false;                // if this file was just inserted via mtp, set the rowid to zero                // (even though it already exists in the database), to trigger                // the correct code path for updating its entry                if (mMtpObjectHandle != 0) {                    entry.mRowId = 0;                }                // rescan for metadata if file was modified since last scan                if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {                    if (noMedia) {                        result = endFile(entry, false, false, false, false, false);                    } else {                        String lowpath = path.toLowerCase(Locale.ROOT);                        boolean ringtones = (lowpath.indexOf(RINGTONES_DIR) > 0);                        boolean notifications = (lowpath.indexOf(NOTIFICATIONS_DIR) > 0);                        boolean alarms = (lowpath.indexOf(ALARMS_DIR) > 0);                        boolean podcasts = (lowpath.indexOf(PODCAST_DIR) > 0);                        boolean music = (lowpath.indexOf(MUSIC_DIR) > 0) ||                            (!ringtones && !notifications && !alarms && !podcasts);                        boolean isaudio = MediaFile.isAudioFileType(mFileType);                        boolean isvideo = MediaFile.isVideoFileType(mFileType);                        boolean isimage = MediaFile.isImageFileType(mFileType);                        if (isaudio || isvideo || isimage) {                            if (mExternalIsEmulated && path.startsWith(mExternalStoragePath)) {                                // try to rewrite the path to bypass the sd card fuse layer                                String directPath = Environment.getMediaStorageDirectory() +                                        path.substring(mExternalStoragePath.length());                                File f = new File(directPath);                                if (f.exists()) {                                    path = directPath;                                }                            }                        }                        // we only extract metadata for audio and video files                        if (isaudio /*|| isvideo */)                        {                        //jni方法调用                            processFile(path, mimeType, this);                        }                        //if (isimage) {                        //    processImageFile(path);                        //}                        result = endFile(entry, ringtones, notifications, alarms, music, podcasts);                    }                }            } catch (RemoteException e) {                Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);            }//            long t2 = System.currentTimeMillis();//            Log.v(TAG, "scanFile: " + path + " took " + (t2-t1));            return result;        }

我们看一下英文说明

 MediaScanner.processFile(). * - MediaScanner.processFile() calls one of several methods, depending on the type of the *   file: parseMP3, parseMP4, parseMidi, parseOgg or parseWMA. * - each of these methods gets metadata key/value pairs from the file, and repeatedly *   calls native MyMediaScannerClient.handleStringTag, which calls back up to its Java *   counterparts in this file.

大意就是,processFile会根据文件类型调用不用的方法,并由MyMediaScannerClient.handleStringTag回调

这段native方法可以在下面的文件目录找到

frameworks/base/media/jni/android_media_MediaScanner.cpp
static voidandroid_media_MediaScanner_processFile(        JNIEnv *env, jobject thiz, jstring path,        jstring mimeType, jobject client){    ALOGV("processFile");    // Lock already hold by processDirectory    MediaScanner *mp = getNativeScanner_l(env, thiz);    if (mp == NULL) {        jniThrowException(env, kRunTimeException, "No scanner available");        return;    }    if (path == NULL) {        jniThrowException(env, kIllegalArgumentException, NULL);        return;    }    const char *pathStr = env->GetStringUTFChars(path, NULL);    if (pathStr == NULL) {  // Out of memory        return;    }    const char *mimeTypeStr =        (mimeType ? env->GetStringUTFChars(mimeType, NULL) : NULL);    if (mimeType && mimeTypeStr == NULL) {  // Out of memory        // ReleaseStringUTFChars can be called with an exception pending.        env->ReleaseStringUTFChars(path, pathStr);        return;    }    MyMediaScannerClient myClient(env, client);    MediaScanResult result = mp->processFile(pathStr, mimeTypeStr, myClient);    if (result == MEDIA_SCAN_RESULT_ERROR) {        ALOGE("An error occurred while scanning file '%s'.", pathStr);    }    env->ReleaseStringUTFChars(path, pathStr);    if (mimeType) {        env->ReleaseStringUTFChars(mimeType, mimeTypeStr);    }}

mp->processFile在路径

通过grep搜索发现,方法定义在文件frameworks/av/media/libmedia/MediaScanner.h

可以看到这是一个虚函数

    virtual MediaScanResult processFile(            const char *path, const char *mimeType, MediaScannerClient &client) = 0;

虚函数定义如下:如果父类的函数(方法)根本没有必要或者无法实现,完全要依赖子类去实现的话,可以把此函数(方法)设为virtual 函数名=0 我们把这样的函数(方法)称为纯虚函数如果一个类包含了纯虚函数,称此类为抽象类。
我们接着往下跟,找到真正实现的地方
回到前一个jni

frameworks/base/media/jni/android_media_MediaScanner.cpp

这里面有实例化mediascanner的地方

static voidandroid_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz){    ALOGV("native_setup");    MediaScanner *mp = new StagefrightMediaScanner;    if (mp == NULL) {        jniThrowException(env, kRunTimeException, "Out of memory");        return;    }    env->SetIntField(thiz, fields.context, (int)mp);}

马上就能找到实例化的地方了,打开

frameworks\av\media\libstagefright\StagefrightMediaScanner.cpp
MediaScanResult StagefrightMediaScanner::processFile(        const char *path, const char *mimeType,        MediaScannerClient &client) {    ALOGV("processFile '%s'.", path);    client.setLocale(locale());    client.beginFile();    MediaScanResult result = processFileInternal(path, mimeType, client);    client.endFile();    return result;}MediaScanResult StagefrightMediaScanner::processFileInternal(        const char *path, const char *mimeType,        MediaScannerClient &client) {    const char *extension = strrchr(path, '.');    if (!extension) {        return MEDIA_SCAN_RESULT_SKIPPED;    }    //过滤类型    if (!FileHasAcceptableExtension(extension)) {        return MEDIA_SCAN_RESULT_SKIPPED;    }    //根据扩展名调用不同的解析方法    if (!strcasecmp(extension, ".mid")            || !strcasecmp(extension, ".smf")            || !strcasecmp(extension, ".imy")            || !strcasecmp(extension, ".midi")            || !strcasecmp(extension, ".xmf")            || !strcasecmp(extension, ".rtttl")            || !strcasecmp(extension, ".rtx")            || !strcasecmp(extension, ".ota")            || !strcasecmp(extension, ".mxmf")) {        return HandleMIDI(path, &client);    }    //parse ape audio file    if (!strcasecmp(extension, ".ape")) {        return parseAPE(path, client);    }    sp mRetriever(new MediaMetadataRetriever);    int fd = open(path, O_RDONLY | O_LARGEFILE);    status_t status;    if (fd < 0) {        // couldn't open it locally, maybe the media server can?        status = mRetriever->setDataSource(path);    } else {        status = mRetriever->setDataSource(fd, 0, 0x7ffffffffffffffL);        close(fd);    }    if (status) {        return MEDIA_SCAN_RESULT_ERROR;    }    const char *value;    if ((value = mRetriever->extractMetadata(                    METADATA_KEY_MIMETYPE)) != NULL) {        status = client.setMimeType(value);        if (status) {            return MEDIA_SCAN_RESULT_ERROR;        }    }

client.endFile()这个方法的实现在

frameworks\av\media\libmedia\MediaScannerClient.cpp
void MediaScannerClient::endFile(){    if (mLocaleEncoding != kEncodingNone) {        int size = mNames->size();        uint32_t encoding = kEncodingAll;        // compute a bit mask containing all possible encodings                char* isUtf8 = new char[size];                memset(isUtf8,0,size);                for (int i = 0; i < mNames->size(); i++)                {                       // ALOGE("I=%d ,names=%s , values=%s,mLocaleEncoding =0x%x \n",i,mNames->getEntry(i),mValues->getEntry(i),mLocaleEncoding);                        uint32_t tmpEncoding = kEncodingAll;                        const char * name = mNames->getEntry(i);                        //when the system languge is russian ,the title/album/artist/must think it is win1251 .                        if((mLocaleEncoding == kEncodingWin1251 || mLocaleEncoding == kEncodingWin1252) && (!strncmp(name, "tit", 3)||!strncmp(name, "alb", 3)||!strncmp(name, "art", 3)))                            tmpEncoding = kEncodingNone;                        else                          tmpEncoding = possibleEncodings(mValues->getEntry(i));                        if(tmpEncoding == kEncodingNone)                        {                            ALOGI("%s value can't discern,the value is %s",mNames->getEntry(i),mValues->getEntry(i));                            if( mLocaleEncoding == kEncodingWin1251 || mLocaleEncoding == kEncodingWin1252 )                            {                                //revert the string value the original string                                 int8_t ch1, ch2;                                 int8_t *src = (int8_t *)mValues->getEntry(i);                                 int8_t *dst = src;                                 while ((ch1 = *src++)) {                                        if (ch1 & 0x80) {                                            ch2 = *src++;                                            ch1 = ((ch1 << 6) & 0xC0) | (ch2 & 0x3F);                                        }                                        *dst++ = ch1;                                  }                                  *dst = '\0';                                unsigned char value[1024];                                memset(value,0,1024);                                if( mLocaleEncoding == kEncodingWin1251)                                    Win1251ToUtf8((unsigned char *)mValues->getEntry(i),strlen(mValues->getEntry(i)),value,1024);                                else                                    Win1252ToUtf8((unsigned char *)mValues->getEntry(i),strlen(mValues->getEntry(i)),value,1024);                                //ALOGI("%s value do change encode type,the value is %s",mNames->getEntry(i),value);                                handleStringTag(name, (const char *)value);                            }                            else                            {                            //if the id3 value can't discern the encoding,we can think it is a utf8                                handleStringTag(name, mValues->getEntry(i));                            }                            isUtf8[i] = 0xff;                             continue;                        }                        //LOGI("%s value is %x coding,the value is %s",mNames->getEntry(i),tmpEncoding,mValues->getEntry(i));                        encoding &= tmpEncoding;                }        if(encoding != kEncodingAll)        {                    if(encoding == kEncodingNone)                        encoding = mLocaleEncoding;                    // if the locale encoding matches, then assume we have a native encoding.                    if (encoding & mLocaleEncoding)                        convertValues(mLocaleEncoding);                    else if(encoding & kEncodingEUCKR)                        convertValues(kEncodingEUCKR);                    else if(encoding & kEncodingGBK)                        convertValues(kEncodingGBK);                    else if(encoding & kEncodingBig5)                        convertValues(kEncodingBig5);                    else if(encoding & kEncodingShiftJIS)                        convertValues(kEncodingShiftJIS);                }        // finally, push all name/value pairs to the client        for (int i = 0; i < mNames->size(); i++)         {                        if(isUtf8[i] == 0xff)                            continue;            status_t status = handleStringTag(mNames->getEntry(i), mValues->getEntry(i));            if (status) {                break;            }        }    }    // else addStringTag() has done all the work so we have nothing to do    delete mNames;    delete mValues;    mNames = NULL;    mValues = NULL;}

这个方法会处理底层解析后的数据,返回给java层调用,第一次自己边跟代码边写CSDN,谢谢各位

PS:今天在测试媒体扫描时候发现,如果在开发者模式下,禁用后台进程,则媒体扫描会被系统kill,原因是媒体扫描是后台进程,发现相册图片更新问题的朋友注意了,可能跟这个有关

更多相关文章

  1. 一款常用的 Squid 日志分析工具
  2. GitHub 标星 8K+!一款开源替代 ls 的工具你值得拥有!
  3. RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装
  4. Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
  5. android调用matlab中的函数方法
  6. Mac下 android 模拟器 host修改
  7. 用Preferences,通过xml文件跳转到另一个Activity
  8. Android中banner的使用步骤
  9. Android(安卓)Mediascanner实现机制

随机推荐

  1. 使用delphi 开发 web(五)Android 与delphi
  2. Android Studio升级后,开启时遇到tools.ja
  3. android每日一问1【2011-09-06】
  4. 【随心笔录】Android多进程实现,一个APP
  5. 一种Android数据请求框架
  6. Android逐帧动画——让图片动起来
  7. Freescale IMX6 Android (7): Android启
  8. Android应用盈利广告平台的嵌入方法详解
  9. 从架构师的角度分析Android Handler 源码
  10. android LinearLayout 单击动态改变背景