插入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;
}
}
//初始化默认的uri
private 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 void
android_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 void
android_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,原因是媒体扫描是后台进程,发现相册图片更新问题的朋友注意了,可能跟这个有关