public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); final Uri uri = intent.getData(); //收到开机广播后扫描 if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { // Scan both internal and external storage //扫描内部存储器中/system/media里面的内容,存放的是铃声等多媒体文件 scan(context, MediaProvider.INTERNAL_VOLUME); //扫描手机的外部存储实际扫描的是/mnt/sdcard/ scan(context, MediaProvider.EXTERNAL_VOLUME);
// Start up the thread running the service. Note that we create a // separate thread because the service normally runs in the process's // main thread, which we don't want to block. Threadthr=newThread(null, this, "MediaScannerService"); thr.start(); }
public void run() { // reduce priority below other background threads to avoid interfering // with other services at boot time. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND +Process.THREAD_PRIORITY_LESS_FAVORABLE); Looper.prepare();
private void prescan(String filePath, boolean prescanFiles) throws RemoteException { Cursor c = null; String where = null; String[] selectionArgs = null; //建立mPlayLists if (mPlayLists == null) { mPlayLists = new ArrayList<FileEntry>(); } else { mPlayLists.clear(); } //通过上述处理mPlayLists将会被清空, if (filePath != null) { // query for only one file where = MediaStore.Files.FileColumns._ID+ ">?" +" AND " + Files.FileColumns.DATA + "=?"; selectionArgs = new String[] { "", filePath }; } else { where = MediaStore.Files.FileColumns._ID+ ">?"; selectionArgs = new String[] { "" }; }
// 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). //在这里告诉provider不要删除那些通过扫描发现不存在的文件, 为的是避免在扫描的时候文件系统卸载了,但是在以后的时候又挂载上了。 Uri.Builder builder = mFilesUri.buildUpon(); builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false"); MediaBulkDeleter deleter = newMediaBulkDeleter(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) { //如果为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 = Os.access(path, android.system.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 = newFile(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(); } }
//如果为音频或者视频文件抽取元数据 // we only extract metadata for audio and video files if (isaudio || isvideo) { processFile(path, mimeType, this); } //如果是图像则调用processImageFile if (isimage) { processImageFile(path); } //调用endFile result = endFile(entry, ringtones, notifications, alarms, music, podcasts); } } return result; }
private Uri endFile(FileEntry entry, boolean ringtones, boolean notifications, boolean alarms, boolean music, boolean podcasts) throws RemoteException { // update database // use album artist if artist is missing if (mArtist == null || mArtist.length() == 0) { mArtist = mAlbumArtist; } ContentValuesvalues= toValues(); Stringtitle= values.getAsString(MediaStore.MediaColumns.TITLE); if (title == null || TextUtils.isEmpty(title.trim())) { title = MediaFile.getFileTitle(values.getAsString(MediaStore.MediaColumns.DATA)); values.put(MediaStore.MediaColumns.TITLE, title); } Stringalbum= values.getAsString(Audio.Media.ALBUM); if (MediaStore.UNKNOWN_STRING.equals(album)) { album = values.getAsString(MediaStore.MediaColumns.DATA); // extract last path segment before file name intlastSlash= album.lastIndexOf('/'); if (lastSlash >= 0) { intpreviousSlash=0; while (true) { intidx= album.indexOf('/', previousSlash + 1); if (idx < 0 || idx >= lastSlash) { break; } previousSlash = idx; } if (previousSlash != 0) { album = album.substring(previousSlash + 1, lastSlash); values.put(Audio.Media.ALBUM, album); } } } longrowId= entry.mRowId; if (MediaFile.isAudioFileType(mFileType) && (rowId == 0 || mMtpObjectHandle != 0)) { // Only set these for new entries. For existing entries, they // may have been modified later, and we want to keep the current // values so that custom ringtones still show up in the ringtone // picker. values.put(Audio.Media.IS_RINGTONE, ringtones); values.put(Audio.Media.IS_NOTIFICATION, notifications); values.put(Audio.Media.IS_ALARM, alarms); values.put(Audio.Media.IS_MUSIC, music); values.put(Audio.Media.IS_PODCAST, podcasts); } elseif (mFileType == MediaFile.FILE_TYPE_JPEG && !mNoMedia) { ExifInterfaceexif=null; try { exif = newExifInterface(entry.mPath); } catch (IOException ex) { // exif is null } if (exif != null) { float[] latlng = newfloat[2]; if (exif.getLatLong(latlng)) { values.put(Images.Media.LATITUDE, latlng[0]); values.put(Images.Media.LONGITUDE, latlng[1]); }
longtime= exif.getGpsDateTime(); if (time != -1) { values.put(Images.Media.DATE_TAKEN, time); } else { // If no time zone information is available, we should consider using // EXIF local time as taken time if the difference between file time // and EXIF local time is not less than 1 Day, otherwise MediaProvider // will use file time as taken time. time = exif.getDateTime(); if (time != -1 && Math.abs(mLastModified * 1000 - time) >= 86400000) { values.put(Images.Media.DATE_TAKEN, time); } }
intorientation= exif.getAttributeInt( ExifInterface.TAG_ORIENTATION, -1); if (orientation != -1) { // We only recognize a subset of orientation tag values. int degree; switch(orientation) { case ExifInterface.ORIENTATION_ROTATE_90: degree = 90; break; case ExifInterface.ORIENTATION_ROTATE_180: degree = 180; break; case ExifInterface.ORIENTATION_ROTATE_270: degree = 270; break; default: degree = 0; break; } values.put(Images.Media.ORIENTATION, degree); } } }
UritableUri= mFilesUri; MediaInserterinserter= mMediaInserter; if (!mNoMedia) { if (MediaFile.isVideoFileType(mFileType)) { tableUri = mVideoUri; } elseif (MediaFile.isImageFileType(mFileType)) { tableUri = mImagesUri; } elseif (MediaFile.isAudioFileType(mFileType)) { tableUri = mAudioUri; } } Uriresult=null; booleanneedToSetSettings=false; if (rowId == 0) { if (mMtpObjectHandle != 0) { values.put(MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, mMtpObjectHandle); } if (tableUri == mFilesUri) { intformat= entry.mFormat; if (format == 0) { format = MediaFile.getFormatCode(entry.mPath, mMimeType); } values.put(Files.FileColumns.FORMAT, format); } // Setting a flag in order not to use bulk insert for the file related with // notifications, ringtones, and alarms, because the rowId of the inserted file is // needed. if (mWasEmptyPriorToScan) { if (notifications && !mDefaultNotificationSet) { if (TextUtils.isEmpty(mDefaultNotificationFilename) || doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) { needToSetSettings = true; } } elseif (ringtones && !mDefaultRingtoneSet) { if (TextUtils.isEmpty(mDefaultRingtoneFilename) || doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename)) { needToSetSettings = true; } } elseif (alarms && !mDefaultAlarmSet) { if (TextUtils.isEmpty(mDefaultAlarmAlertFilename) || doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)) { needToSetSettings = true; } } }
// New file, insert it. // Directories need to be inserted before the files they contain, so they // get priority when bulk inserting. // If the rowId of the inserted file is needed, it gets inserted immediately, // bypassing the bulk inserter. if (inserter == null || needToSetSettings) { if (inserter != null) { inserter.flushAll(); } result = mMediaProvider.insert(mPackageName, tableUri, values); } elseif (entry.mFormat == MtpConstants.FORMAT_ASSOCIATION) { inserter.insertwithPriority(tableUri, values); } else { inserter.insert(tableUri, values); }
if (result != null) { rowId = ContentUris.parseId(result); entry.mRowId = rowId; } } else { // updated file result = ContentUris.withAppendedId(tableUri, rowId); // path should never change, and we want to avoid replacing mixed cased paths // with squashed lower case paths values.remove(MediaStore.MediaColumns.DATA);
// handle playlists last, after we know what media files are on the storage. //如果当前扫描路径为外部存储路径则,则处理播放列表 if (mProcessPlaylists) { //只处理最新的和上次扫描以后修改后的播放列表文件, //获得最后的名字以及最后的修改时间, //并根据播放列表的类型调用不同的方法处理播放列表文件 processPlayLists(); }
if (mOriginalCount == 0 && mImagesUri.equals(Images.Media.getContentUri("external"))) pruneDeadThumbnailFiles();
// allow GC to clean up mPlayLists = null; mMediaProvider = null; }
privatevoidprocessPlayLists()throws RemoteException { Iterator<FileEntry> iterator = mPlayLists.iterator(); CursorfileList=null; try { // use the files uri and projection because we need the format column, // but restrict the query to just audio files /获取全部的音频文件 fileList = mMediaProvider.query(mPackageName, mFilesUri, FILES_PRESCAN_PROJECTION, "media_type=2", null, null, null); while (iterator.hasNext()) { FileEntryentry= iterator.next(); // only process playlist files if they are new or have been modified since the last scan // 只处理最新的和上次扫描以后修改后的播放列表文件 if (entry.mLastModifiedChanged) { processPlayList(entry, fileList); } } } catch (RemoteException e1) { } finally { if (fileList != null) { fileList.close(); } } }