此分析代码基于Android 6.0,转载请注明来源地址http://www.jianshu.com/p/71c662bbabd5
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 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++;
}
}
所以对于客户端来说,实现此静态方法去扫描,只需要传入上下文,查询的路径(可以是多个路径,用数组表示),文件类型和监听器即可,不需要考虑其他,比较方便使用。