Media Data之多媒体扫描过程分析(二)
2016-08-20 16:18
369 查看
此分析代码基于Android 6.0,转载请注明来源地址http://blog.csdn.net/lemon_blue/article/details/52262023
Media Data之多媒体扫描过程分析(一)
Media Data之多媒体扫描过程分析(三)
android_media_MediaScanner_native_init的功能主要是动态注册。
android_media_MediaScanner_native_setup方法的作用是创建native的MediaScanner对象,并且用的是StagefrightMediaScanner,等会分析。
android_media_MediaScanner_processDirectory方法的作用是启动native层processDirectory扫描方法,在配置过程稍显复杂,其一是java的MediaScanner的上下文环境传递给native额MediaScanner对象中,其二是native的MyMediaScannerClient对象与java的MyMediaScannerClient对象建立联系,方便将结果回调到java层。
先分析MediaScanner.cpp父类的方法。
从上面的分析中看到调用到了MyMediaScannerClient的scanFile函数,下面分析这个函数
可以看出在native层的MyMediaScannerClient调用的是java层MyMediaScannerClient的scanFile函数,下面分析java层的逻辑。
从上面的分析可以看到,其实又调用到了processFile方法中,他也是一个native方法,需要再回到jni层继续分析此方法。
从上面的分析可以看出,调用了StagefrightMediaScanner对象的processFile方法,下面分析此方法。
从上面的分析中,设置tag和value是通过MyMediaScannerClient调用的,在MyMediaScannerClient的父类MediaScannerClient有addStringTag方法,在方法中又调用了子类MyMediaScannerClient的handleStringTag方法。
此时在native层中又去调用java层的方法了,此处调用的是handleStringTag方法。
到此文件的读取过程分析完成了,这些成员变量装填完成之后就会调用到endFile方法中,进行更新数据库了。
通过注释可以看出,MediaScannerConnection可以提供另一种非发广播的方式去主动扫描文件,他的调用过程是跨进程的,扫描的结果会通过回调函数获得。
在MediaScannerConnection内部提供了两种方式去供客户端使用,一种是实现接口和回调方法,另一种是使用代理模式所提供的静态方法。
(1)实现接口
首先通过构造方法新建实例,并且设置相关的成员变量。然后在客户端处调用connect方法,去绑定service,并且调用requestScanFile方法去跨进程调用MediaScannerService中的方法。当连接到MediaScannerService后回调客户端onMediaScannerConnected方法,当MediaScannerService扫描完成后,回调客户端onScanCompleted方法,整个过程完成。
MediaScannerConnection部分分析完成,可以看出在connect方法中去绑定了远程的MediaScannerService,接下来分析在MediaScannerService完成的操作。
在MediaScannerService的主要作用就是接受intent,调用scanFile方法扫描,扫描完成之后调用回调方法,给客户端回调。
(2) 静态方法实现
在MediaScannerConnection也可以通过提供的静态方法去实现扫描。其原理就是实现代理模式,远程代理客户端的实例进行相关操作,客户端只需要传入相应的参数即可,不需要手动连接service等操作,比较方便实用。
所以对于客户端来说,实现此静态方法去扫描,只需要传入上下文,查询的路径(可以是多个路径,用数组表示),文件类型和监听器即可,不需要考虑其他,比较方便使用。
Media Data之多媒体扫描过程分析(一)
Media Data之多媒体扫描过程分析(三)
2.1.5 android_media_MediaScanner.cpp
对于android_media_MediaScanner.cpp来说,主要分析三个函数native_init,native_setup和processDirectory。static void android_media_MediaScanner_native_init(JNIEnv *env) { ALOGV("native_init"); jclass clazz = env->FindClass(kClassMediaScanner); if (clazz == NULL) { return; } //将之后创建的native对象的指针保存到MediaScanner.java的mNativeContext字段中 fields.context = env->GetFieldID(clazz, "mNativeContext", "J"); if (fields.context == NULL) { return; } }
android_media_MediaScanner_native_init的功能主要是动态注册。
static void android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz) { //获取Stagefright的MediaScanner对象 MediaScanner *mp = new StagefrightMediaScanner; if (mp == NULL) { jniThrowException(env, kRunTimeException, "Out of memory"); return; } //将对象保存到mNativeContext中 env->SetLongField(thiz, fields.context, (jlong)mp); }
android_media_MediaScanner_native_setup方法的作用是创建native的MediaScanner对象,并且用的是StagefrightMediaScanner,等会分析。
static void android_media_MediaScanner_processDirectory( JNIEnv *env, jobject thiz, jstring path, jobject client) { //传入的参数path是需要扫描的路径,client是MediaScannerClient.java对象 //获取之前保存到mNativeContext的StagefrightMediaScanner对象 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; } //构造native层的MyMediaScannerClient对象,参数是java层的MyMediaScannerClient //对象 MyMediaScannerClient myClient(env, client); //调用native层processDirectory方法,参数是扫描路径和native的MyMediaScannerClient //对象 MediaScanResult result = mp->processDirectory(pathStr, myClient); if (result == MEDIA_SCAN_RESULT_ERROR) { ALOGE("An error occurred while scanning directory '%s'.", pathStr); } env->ReleaseStringUTFChars(path, pathStr); }
android_media_MediaScanner_processDirectory方法的作用是启动native层processDirectory扫描方法,在配置过程稍显复杂,其一是java的MediaScanner的上下文环境传递给native额MediaScanner对象中,其二是native的MyMediaScannerClient对象与java的MyMediaScannerClient对象建立联系,方便将结果回调到java层。
2.1.6 MediaScanner.cpp
下面分析的是native层的相关处理,StagefrightMediaScanner.cpp继承自MediaScanner.cpp,在JNI调用的方法processDirectory也是由父类实现的。先分析MediaScanner.cpp父类的方法。
MediaScanResult MediaScanner::processDirectory( const char *path, MediaScannerClient &client) { //前期的一些准备工作 int pathLength = strlen(path); if (pathLength >= PATH_MAX) { return MEDIA_SCAN_RESULT_SKIPPED; } char* pathBuffer = (char *)malloc(PATH_MAX + 1); if (!pathBuffer) { return MEDIA_SCAN_RESULT_ERROR; } int pathRemaining = PATH_MAX - pathLength; strcpy(pathBuffer, path); if (pathLength > 0 && pathBuffer[pathLength - 1] != '/') { pathBuffer[pathLength] = '/'; pathBuffer[pathLength + 1] = 0; --pathRemaining; } //设置native的MyMediaScannerClient对象的local信息 client.setLocale(locale()); //执行doProcessDirectory方法 MediaScanResult result = doProcessDirectory(pathBuffer, pathRemaining, client, false); //释放资源 free(pathBuffer); return result; } MediaScanResult MediaScanner::doProcessDirectory( char *path, int pathRemaining, MediaScannerClient &client, bool noMedia) { // place to copy file or directory name char* fileSpot = path + strlen(path); struct dirent* entry; if (shouldSkipDirectory(path)) { ALOGD("Skipping: %s", path); return MEDIA_SCAN_RESULT_OK; } // Treat all files as non-media in directories that contain a ".nomedia" file if (pathRemaining >= 8 /* strlen(".nomedia") */ ) { strcpy(fileSpot, ".nomedia"); if (access(path, F_OK) == 0) { ALOGV("found .nomedia, setting noMedia flag"); noMedia = true; } // restore path fileSpot[0] = 0; } //打开对应的文件夹路径 DIR* dir = opendir(path); if (!dir) { ALOGW("Error opening directory '%s', skipping: %s.", path, strerror(errno)); return MEDIA_SCAN_RESULT_SKIPPED; } MediaScanResult result = MEDIA_SCAN_RESULT_OK; //循环遍历所有文件 while ((entry = readdir(dir))) { //调用doProcessDirectoryEntry方法 if (doProcessDirectoryEntry(path, pathRemaining, client, noMedia, entry, fileSpot) == MEDIA_SCAN_RESULT_ERROR) { result = MEDIA_SCAN_RESULT_ERROR; break; } } //关闭文件夹 closedir(dir); return result; } MediaScanResult MediaScanner::doProcessDirectoryEntry( char *path, int pathRemaining, MediaScannerClient &client, bool noMedia, struct dirent* entry, char* fileSpot) { struct stat statbuf; //枚举目录中的文件和子文件夹信息 const char* name = entry->d_name; // ignore "." and ".." if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) { return MEDIA_SCAN_RESULT_SKIPPED; } int nameLength = strlen(name); if (nameLength + 1 > pathRemaining) { // path too long! return MEDIA_SCAN_RESULT_SKIPPED; } strcpy(fileSpot, name); int type = entry->d_type; if (type == DT_UNKNOWN) { // If the type is unknown, stat() the file instead. // This is sometimes necessary when accessing NFS mounted filesystems, but // could be needed in other cases well. //执行stat方法,获取文件的所有属性,成功返回0失败返回-1 if (stat(path, &statbuf) == 0) { if (S_ISREG(statbuf.st_mode)) { type = DT_REG; } else if (S_ISDIR(statbuf.st_mode)) { type = DT_DIR; } } else { ALOGD("stat() failed for %s: %s", path, strerror(errno) ); } } if (type == DT_DIR) { bool childNoMedia = noMedia; // set noMedia flag on directories with a name that starts with '.' // for example, the Mac ".Trashes" directory if (name[0] == '.') childNoMedia = true; // report the directory to the client if (stat(path, &statbuf) == 0) { //调用MyMediaScannerClient的scanFile函数 status_t status = client.scanFile(path, statbuf.st_mtime, 0, true /*isDirectory*/, childNoMedia); if (status) { //返回值是checkAndClearExceptionFromCallback,如果是true就出错 return MEDIA_SCAN_RESULT_ERROR; } } // and now process its contents strcat(fileSpot, "/"); MediaScanResult result = doProcessDirectory(path, pathRemaining - nameLength - 1, client, childNoMedia); if (result == MEDIA_SCAN_RESULT_ERROR) { return MEDIA_SCAN_RESULT_ERROR; } } else if (type == DT_REG) { stat(path, &statbuf); status_t status = client.scanFile(path, statbuf.st_mtime, statbuf.st_size, false /*isDirectory*/, noMedia); if (status) { return MEDIA_SCAN_RESULT_ERROR; } } return MEDIA_SCAN_RESULT_OK; }
从上面的分析中看到调用到了MyMediaScannerClient的scanFile函数,下面分析这个函数
virtual status_t scanFile(const char* path, long long lastModified, long long fileSize, bool isDirectory, bool noMedia) { jstring pathStr; if ((pathStr = mEnv->NewStringUTF(path)) == NULL) { mEnv->ExceptionClear(); return NO_MEMORY; } //此处的mClient是java层的MyMediaScannerClient,调用的也是java层的scanFile方法 mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize, isDirectory, noMedia); mEnv->DeleteLocalRef(pathStr); return checkAndClearExceptionFromCallback(mEnv, "scanFile"); }
可以看出在native层的MyMediaScannerClient调用的是java层MyMediaScannerClient的scanFile函数,下面分析java层的逻辑。
public void scanFile(String path, long lastModified, long fileSize, boolean isDirectory, boolean noMedia) { // This is the callback funtion from native codes. //调用了doScanFile方法 doScanFile(path, null, lastModified, fileSize, isDirectory, false, noMedia); } public Uri doScanFile(String path, String mimeType, long lastModified, long fileSize, boolean isDirectory, boolean scanAlways, boolean noMedia) { //参数scanAlways控制是否强制扫描 Uri result = null; try { // beginFile方法的作用主要是1. 生成FileEntry,2.判断是否有修改文件 FileEntry entry = beginFile(path, mimeType, lastModified, fileSize, isDirectory, noMedia); // 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) { //不是media的情况 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 the given path exists on emulated external storage, //return the translated backing path hosted on internal storage. path = Environment.maybeTranslateEmulatedPathToInternal (new File(path)).getAbsolutePath(); } // we only extract metadata for audio and video files if (isaudio || isvideo) { //调用processFile方法,把MyMediaScannerClient作为参数传入 // processFile方法是native方法,稍后分析 processFile(path, mimeType, this); } if (isimage) { //如果是图片,单独处理,调用processImageFile方法 //Decode a file path into a bitmap. processImageFile(path); } // endFile方法是更新数据库 result = endFile(entry, ringtones, notifications, alarms, music, podcasts); } } } catch (RemoteException e) { Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e); } return result; }
从上面的分析可以看到,其实又调用到了processFile方法中,他也是一个native方法,需要再回到jni层继续分析此方法。
static void android_media_MediaScanner_processFile( JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client) { // Lock already hold by processDirectory //获取的还是native层的MediaScanner对象,实际类型是StagefrightMediaScanner对象 MediaScanner *mp = getNativeScanner_l(env, thiz); const char *pathStr = env->GetStringUTFChars(path, NULL); if (pathStr == NULL) { // Out of memory return; } //构造了新的native层的MyMediaScannerClient对象,传入的还是java层的MyMediaScannerClient对象 MyMediaScannerClient myClient(env, client); //调用的是StagefrightMediaScanner对象的processFile方法,等会分析 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); } }
从上面的分析可以看出,调用了StagefrightMediaScanner对象的processFile方法,下面分析此方法。
MediaScanResult StagefrightMediaScanner::processFile( const char *path, const char *mimeType, MediaScannerClient &client) { //调用native层的MyMediaScannerClient对象进行local信息,语言设置 client.setLocale(locale()); //beginFile方法是由MyMediaScannerClient的父类实现的,其实谷歌并没有实现此方法 client.beginFile(); //具体的方法是调用processFileInternal实现的 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) && !AVUtils::get()->isEnhancedExtension(extension)) { return MEDIA_SCAN_RESULT_SKIPPED; } // MediaMetadataRetriever将一个输入媒体文件中设置帧和元数据 sp<MediaMetadataRetriever> 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(NULL /* httpService */, 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; } } //构造元数据的tag struct KeyMap { const char *tag; int key; }; static const KeyMap kKeyMap[] = { { "tracknumber", METADATA_KEY_CD_TRACK_NUMBER }, { "discnumber", METADATA_KEY_DISC_NUMBER }, { "album", METADATA_KEY_ALBUM }, { "artist", METADATA_KEY_ARTIST }, { "albumartist", METADATA_KEY_ALBUMARTIST }, { "composer", METADATA_KEY_COMPOSER }, { "genre", METADATA_KEY_GENRE }, { "title", METADATA_KEY_TITLE }, { "year", METADATA_KEY_YEAR }, { "duration", METADATA_KEY_DURATION }, { "writer", METADATA_KEY_WRITER }, { "compilation", METADATA_KEY_COMPILATION }, { "isdrm", METADATA_KEY_IS_DRM }, { "width", METADATA_KEY_VIDEO_WIDTH }, { "height", METADATA_KEY_VIDEO_HEIGHT }, }; static const size_t kNumEntries = sizeof(kKeyMap) / sizeof(kKeyMap[0]); //循环遍历 for (size_t i = 0; i < kNumEntries; ++i) { const char *value; if ((value = mRetriever->extractMetadata(kKeyMap[i].key)) != NULL) { //设置tag和value到MyMediaScannerClient中,稍后分析 status = client.addStringTag(kKeyMap[i].tag, value); if (status != OK) { return MEDIA_SCAN_RESULT_ERROR; } } } return MEDIA_SCAN_RESULT_OK; }
从上面的分析中,设置tag和value是通过MyMediaScannerClient调用的,在MyMediaScannerClient的父类MediaScannerClient有addStringTag方法,在方法中又调用了子类MyMediaScannerClient的handleStringTag方法。
status_t MediaScannerClient::addStringTag(const char* name, const char* value) { //调用子类的handleStringTag方法 handleStringTag(name, value); return OK; }
virtual status_t handleStringTag(const char* name, const char* value) { jstring nameStr, valueStr; //获取字符串的值 if ((nameStr = mEnv->NewStringUTF(name)) == NULL) { mEnv->ExceptionClear(); return NO_MEMORY; } char *cleaned = NULL; //如果value的值不是utf-8编码,则需要特殊处理 if (!isValidUtf8(value)) { cleaned = strdup(value); char *chp = cleaned; char ch; while ((ch = *chp)) { if (ch & 0x80) { *chp = '?'; } chp++; } value = cleaned; } //将处理完成的值赋值到新的字符串valueStr中 valueStr = mEnv->NewStringUTF(value); //释放资源 free(cleaned); if (valueStr == NULL) { mEnv->DeleteLocalRef(nameStr); mEnv->ExceptionClear(); return NO_MEMORY; } //调用java层MyMediaScanner的handleStringTag方法 mEnv->CallVoidMethod( mClient, mHandleStringTagMethodID, nameStr, valueStr); mEnv->DeleteLocalRef(nameStr); mEnv->DeleteLocalRef(valueStr); return checkAndClearExceptionFromCallback(mEnv, "handleStringTag"); }
此时在native层中又去调用java层的方法了,此处调用的是handleStringTag方法。
public void handleStringTag(String name, String value) { if (name.equalsIgnoreCase("title") || name.startsWith("title;")) { // Don't trim() here, to preserve the special \001 character // used to force sorting. The media provider will trim() before // inserting the title in to the database. //将tag信息中的value值都赋值到了成员变量中 mTitle = value; } else if (name.equalsIgnoreCase("artist") || name.startsWith("artist;")) { mArtist = value.trim(); } else if (name.equalsIgnoreCase("albumartist") || name.startsWith("albumartist;") || name.equalsIgnoreCase("band") || name.startsWith("band;")) { mAlbumArtist = value.trim(); ... ... }
到此文件的读取过程分析完成了,这些成员变量装填完成之后就会调用到endFile方法中,进行更新数据库了。
2.2 IPC方式
由于发广播的方式无法实时地获取连接的状态,所以Android又提供了一种查询方法,就是通过IPC,也就是进程间通信的方式去启动扫描,然后获取扫描的状态。2.2.1流程图
2.2.2MediaScannerConnection.java
/** * MediaScannerConnection provides a way for applications to pass a * newly created or downloaded media file to the media scanner service. * The media scanner service will read metadata from the file and add * the file to the media content provider. * The MediaScannerConnectionClient provides an interface for the * media scanner service to return the Uri for a newly scanned file * to the client of the MediaScannerConnection class. */
通过注释可以看出,MediaScannerConnection可以提供另一种非发广播的方式去主动扫描文件,他的调用过程是跨进程的,扫描的结果会通过回调函数获得。
在MediaScannerConnection内部提供了两种方式去供客户端使用,一种是实现接口和回调方法,另一种是使用代理模式所提供的静态方法。
(1)实现接口
首先通过构造方法新建实例,并且设置相关的成员变量。然后在客户端处调用connect方法,去绑定service,并且调用requestScanFile方法去跨进程调用MediaScannerService中的方法。当连接到MediaScannerService后回调客户端onMediaScannerConnected方法,当MediaScannerService扫描完成后,回调客户端onScanCompleted方法,整个过程完成。
//监听扫描完成的接口 public interface OnScanCompletedListener { public void onScanCompleted(String path, Uri uri); } //客户端需要实现的接口,同时也是在服务端所获取的客户端的实例 public interface MediaScannerConnectionClient extends OnScanCompletedListener { public void onMediaScannerConnected(); public void onScanCompleted(String path, Uri uri); } //构造方法,传入的参数是客户端的上下文环境和客户端的实例 public MediaScannerConnection(Context context, MediaScannerConnectionClient client) { mContext = context; mClient = client; } // ServiceConnection的回调方法,当service连接时回调 public void onServiceConnected(ComponentName className, IBinder service) { synchronized (this) { //获取IMediaScannerService的实例mService mService = IMediaScannerService.Stub.asInterface(service); if (mService != null && mClient != null) { //当service连接上时,回调到客户端的onMediaScannerConnected方法 mClient.onMediaScannerConnected(); } } } // IMediaScannerListener是AIDL文件,只有一个方法scanCompleted //这里获取了服务端IMediaScannerListener的实例 private final IMediaScannerListener.Stub mListener = new IMediaScannerListener.Stub() { public void scanCompleted(String path, Uri uri) { MediaScannerConnectionClient client = mClient; if (client != null) { //当回调到scanCompleted时,调用客户端的onScanCompleted方法 client.onScanCompleted(path, uri); } } }; //此方法是在客户端处调用,传入需要扫描的路径和文件类型 public void scanFile(String path, String mimeType) { synchronized (this) { if (mService == null || !mConnected) { throw new IllegalStateException("not connected to MediaScannerService"); } try { //调用IMediaScannerService的方法 mService.requestScanFile(path, mimeType, mListener); } catch (RemoteException e) { } } } //在客户端调用方法,bindService到MediaScannerService public void connect() { synchronized (this) { if (!mConnected) { Intent intent = new Intent(IMediaScannerService.class.getName()); intent.setComponent( new ComponentName("com.android.providers.media", "com.android.providers.media.MediaScannerService")); mContext.bindService(intent, this, Context.BIND_AUTO_CREATE); mConnected = true; } } }
MediaScannerConnection部分分析完成,可以看出在connect方法中去绑定了远程的MediaScannerService,接下来分析在MediaScannerService完成的操作。
@Override public IBinder onBind(Intent intent){ return mBinder; } //在绑定之后获取到了服务端的实例,实现requestScanFile的具体方法 private final IMediaScannerService.Stub mBinder = new IMediaScannerService.Stub() { //此处是requestScanFile实现的具体方法 public void requestScanFile(String path, String mimeType, IMediaScannerListener listener){ Bundle args = new Bundle(); //将相关的参数都放入到了bundle中 args.putString("filepath", path); args.putString("mimetype", mimeType); if (listener != null) { args.putIBinder("listener", listener.asBinder()); } // 用startService的启动方式去启动,传入bundle startService(new Intent(MediaScannerService.this, MediaScannerService.class).putExtras(args)); } //此处是scanFile实现的具体方法 public void scanFile(String path, String mimeType) { requestScanFile(path, mimeType, null); } }; //在onStartCommand方法中将intent的值发送到了ServiceHandler处理 private final class ServiceHandler extends Handler { @Override public void handleMessage(Message msg) { Bundle arguments = (Bundle) msg.obj; String filePath = arguments.getString("filepath"); try { if (filePath != null) { //从intent中获取IBinder对象 IBinder binder = arguments.getIBinder("listener"); //获取IMediaScannerListener的实例 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) { //查询完成后,回调到IMediaScannerListener,客户端处也随之回调 listener.scanCompleted(filePath, uri); } ... ...
在MediaScannerService的主要作用就是接受intent,调用scanFile方法扫描,扫描完成之后调用回调方法,给客户端回调。
(2) 静态方法实现
在MediaScannerConnection也可以通过提供的静态方法去实现扫描。其原理就是实现代理模式,远程代理客户端的实例进行相关操作,客户端只需要传入相应的参数即可,不需要手动连接service等操作,比较方便实用。
public static void scanFile(Context context, String[] paths, String[] mimeTypes, OnScanCompletedListener callback) { //实例化ClientProxy,并给构造函数传参 ClientProxy client = new ClientProxy(paths, mimeTypes, callback); //实例化MediaScannerConnection,并给构造函数传参 MediaScannerConnection connection = new MediaScannerConnection(context, client); client.mConnection = connection; //调用connect函数 connection.connect(); } //客户端的远程代理类 static class ClientProxy implements MediaScannerConnectionClient { final String[] mPaths; final String[] mMimeTypes; final OnScanCompletedListener mClient; MediaScannerConnection mConnection; int mNextPath; //构造函数,配置参数 ClientProxy(String[] paths, String[] mimeTypes, OnScanCompletedListener client) { mPaths = paths; mMimeTypes = mimeTypes; mClient = client; } //实现回调方法 public void onMediaScannerConnected() { scanNextPath(); } public void onScanCompleted(String path, Uri uri) { if (mClient != null) { mClient.onScanCompleted(path, uri); } scanNextPath(); } //因为传入的路径是数组,进行循环扫描 void scanNextPath() { if (mNextPath >= mPaths.length) { mConnection.disconnect(); return; } String mimeType = mMimeTypes != null ? mMimeTypes[mNextPath] : null; mConnection.scanFile(mPaths[mNextPath], mimeType); mNextPath++; } }
所以对于客户端来说,实现此静态方法去扫描,只需要传入上下文,查询的路径(可以是多个路径,用数组表示),文件类型和监听器即可,不需要考虑其他,比较方便使用。
相关文章推荐
- Media Data之多媒体扫描过程分析(一)
- Android 多媒体扫描过程(Android Media Scanner Process)
- android 多媒体扫描过程(Android Media Scanner Process)
- Android 多媒体扫描过程(Android Media Scanner Process)
- Media Data之多媒体扫描过程分析(三)
- Android 多媒体扫描过程(Android Media Scanner Process)
- Android多媒体扫描过程(Android Media Scanner Prosess)
- Media Data之多媒体扫描过程分析
- Android 多媒体扫描过程(Android Media Scanner Process)
- Android多媒体扫描过程(Android Media Scanner Prosess)
- Android多媒体扫描过程(Android Media Scanner Prosess)
- Android 多媒体扫描过程(Android Media Scanner Process)
- 【译】Android 多媒体扫描过程(Android Media Scanner Process)
- Android 多媒体扫描过程(Android Media Scanner Process)
- Android 多媒体扫描过程(Android Media Scanner Process)
- Android 多媒体扫描过程(Android Media Scanner Process)
- Android 多媒体扫描过程(Android Media Scanner Process)
- Android 多媒体扫描过程(Android Media Scanner Process)
- android 多媒体扫描过程(Android Media Scanner Process)
- Android之多媒体扫描过程