前面我已经简单介绍了安卓中MediaScanner&MediaProvider的大概作用: https://blog.csdn.net/cheriyou_/article/details/102585977 本文将继续探索MediaScanner&MediaProvider的具体实现。
1. app生成文件之后可能会将该文件加到对应的数据库中(app可以自己创建数据,也可以使用系统的数据库)
2. app在进入某一界面,比如文件管理器在进入分类->视频界面的时候会去调用mediascanner全盘搜索文件,此处可能会做一个简单的判断,比如后缀之类的,然后发现这个文件可能是视频,就会调用mediacodec去解码,解出来一帧当做缩略图去显示,然后把这个文件加入到自己的数据库中。
3. 重启的时候系统会扫描所有的文件,并存放数据库。
1. 看看生成文件有没有问题,格式是否正确,是否可以正常播放。
2. pull 出provider数据库看文件到底放在数据库的哪个表里,adb pull /data/data/com.android.providers.media/databases, 然后用sqlitebrowser看
3. 在代码里 mediaformat 那块,MediaFormat.java 是不是 那种type没有加
参考: https://droidyue.com/blog/2014/07/12/scan-media-files-in-android-chinese-edition/
https://my.oschina.net/youranhongcha/blog/787223 讲的很详细
MediaProvider.java、MediaScannerService、MediaScannerReceiver等文件最终生成MediaProvider.apk提供服务。
step1. APP新建一个文件之后将文件加入媒体库:
此时只需要发送一个正确的intent广播到MediaScannerReceiver即可。
// APP代码
String saveAs = "Your_Created_File_Path"
Uri contentUri = Uri.fromFile(new File(saveAs));
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,contentUri);
getContext().sendBroadcast(mediaScanIntent);
step2. MediaScannerReciever 收到了APP发的intent
MediaScannerReciever 收到了APP发的intent
当且仅当接收到action android.intent.action.BOOT_COMPLETED才扫描内部存储(非内置和外置sdcard)
除了action为android.intent.action.BOOT_COMPLETED 的以外的intent都必须要有数据传递。
当收到 Intent.ACTION_MEDIA_MOUNTED intent,扫描Sdcard
当收到 Intent.ACTION_MEDIA_SCANNER_SCAN_FILE intent,检测没有问题,将扫描单个文件。
MediaScannerReceiver.java onReceive判断是否是自己需要处理的消息,并根据消息内容做不同的处理,此处以ACTION_BOOT_COMPLETED消息为例
MediaScannerReceiver.java scan,在此函数中调用Context::startservice,开启MediaScannerService,startservice的时候会把消息转发给MediaScannerService(我猜的)
public class MediaScannerReceiver extends BroadcastReceiver {
private final static String TAG = "MediaScannerReceiver";
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(context, MediaProvider.INTERNAL_VOLUME); // 扫描内部存储
} else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
scanTranslatable(context);
} else {
if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
// ......
} else if (Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) &&
startsWithExternalStoragePath(context, path)) {
scanFile(context, path); // 扫描单个文件
}
else if (MiuiIntent.ACTION_MEDIA_SCANNER_SCAN_FOLDER.equals(action) &&
startsWithExternalStoragePath(context, path)) {
scanFolder(context, path); // 扫描文件夹
}
}
}
}
private void scanFile(Context context, String path) {
Bundle args = new Bundle();
args.putString("filepath", path);
context.startService( // 开启MediaScannerService
new Intent(context, MediaScannerService.class).putExtras(args));
}
step3. 下面开始扫描工作:
扫描过程流程图如下:
MediaScannerService.java handleMessage
MediaScannerService.java scan 在此处会广播消息Intent.ACTION_MEDIA_SCANNER_STARTED
MediaScanner.java scanDirectories
MediaScanner.java processDirectory
JNI android_media_MediaScanner_processDirectory
MediaScanner::processDirectory
MediaScanner::doProcessDirectory
MediaScanner::doProcessDirectoryEntry 如果还没到实际目录则再调用doProcessDirectory
MediaScanner.java中的scanFile
MediaScanner.java中的doScanFile,在此函数中会先调用beginFile,判断fileType,再分别调用MediaFile.isAudioFileType、isVideoFileType、isImageFileType,判断当前文件是什么类型,最后调用endFile,把文件信息写入数据库。
MediaScanner.java beginFile 在此函数中如果mimeType不是null,mFileType = MediaFile.getFileTypeForMimeType(mimeType),如果mimeType是null,MediaFile.getFileType(path);获取fileType
MediaFile.java getFileType
MediaFile.java isVideoFileType 在此函数中判断fileType是否介于FIRST_VIDEO_FILE_TYPE和LAST_VIDEO_FILE_TYPE,若是则返回true。
MediaScanner.java endFile 再次函数中先调用toValues()获取values,然后根据fileType给values中put数据,最后把values insert到mMediaProvider中。
下面将做具体分析:
// /packages/providers/MediaProvider/src/com/android/providers/media/MediaScannerService.java
public class MediaScannerService extends Service implements Runnable {
private static final String TAG = "MediaScannerService";
private volatile Looper mServiceLooper;
private volatile ServiceHandler mServiceHandler;
private PowerManager.WakeLock mWakeLock;
private String[] mExternalStoragePaths;
private void scan(String[] directories, String volumeName) {
Uri uri = Uri.parse("file://" + directories[0]);
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);
}
try (MediaScanner scanner = new MediaScanner(this, volumeName)) {
scanner.scanDirectories(directories); // 扫描目录
}
} 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();
}
}
}
接下来就是扫描目录了
// /frameworks/base/media/java/android/media/MediaScanner.java
public class MediaScanner implements AutoCloseable {
public void scanDirectories(String[] directories) {
try {
long start = System.currentTimeMillis();
prescan(null, true);
long prescan = System.currentTimeMillis();
if (ENABLE_BULK_INSERTS) {
mMediaInserter = new MediaInserter(mMediaProvider, 500);
}
for (int i = 0; i < directories.length; i++) {
processDirectory(directories[i], mClient); // 循环处理每个文件夹
}
if (ENABLE_BULK_INSERTS) {
// flush remaining inserts
mMediaInserter.flushAll();
mMediaInserter = null;
}
postscan(directories);
......
} finally {
releaseResources();
}
}
}
此处processDirectory会通过jni调用到/frameworks/av/media/libmedia/MediaScanner.cpp中
// /frameworks/av/media/libmedia/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;
}
client.setLocale(locale());
// 开始扫描
MediaScanResult result = doProcessDirectory(pathBuffer, pathRemaining, client, false);
free(pathBuffer);
return result;
}
MediaScanResult MediaScanner::doProcessDirectory(
char *path, int pathRemaining, MediaScannerClient &client, bool noMedia) {
char* fileSpot = path + strlen(path);
struct dirent* entry;
if (shouldSkipDirectory(path)) { // 跳过部分目录
ALOGD("Skipping: %s", path);
return MEDIA_SCAN_RESULT_OK;
}
DIR* dir = opendir(path); // 打开当前目录
MediaScanResult result = MEDIA_SCAN_RESULT_OK;
while ((entry = readdir(dir))) { // 循环读取当前目录下的文件
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;
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.
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) {
status_t status = client.scanFile(path, statbuf.st_mtime, 0,
true /*isDirectory*/, childNoMedia); // scanFile
if (status) {
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); // // scanFile
if (status) {
return MEDIA_SCAN_RESULT_ERROR;
}
}
return MEDIA_SCAN_RESULT_OK;
}
doProcessDirectoryEntry又会调用client的scanFile,实际调用的就是MediaScanner.java中的scanFile。
// frameworks/base/media/java/android/media/MediaScanner.java
public class MediaScanner implements AutoCloseable {
public void scanFile(String path, long lastModified, long fileSize,
boolean isDirectory, boolean noMedia) {
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) {
Uri result = null;
try {
FileEntry entry = beginFile(path, mimeType, lastModified,
fileSize, isDirectory, noMedia); // 重点! beginFile
if (entry == null) {
return null;
}
if (mMtpObjectHandle != 0) {
entry.mRowId = 0;
}
if (entry.mPath != null) {
if (((!mDefaultNotificationSet &&
doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename))
|| (!mDefaultRingtoneSet &&
doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename))
|| (!mDefaultAlarmSet &&
doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)))) {
Log.w(TAG, "forcing rescan of " + entry.mPath +
"since ringtone setting didn't finish");
scanAlways = true;
} else if (isSystemSoundWithMetadata(entry.mPath)
&& !Build.FINGERPRINT.equals(sLastInternalScanFingerprint)) {
Log.i(TAG, "forcing rescan of " + entry.mPath
+ " since build fingerprint changed");
scanAlways = true;
}
}
if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {
if (noMedia) {
result = endFile(entry, false, false, false, false, false);
} else {
boolean isaudio = MediaFile.isAudioFileType(mFileType);
boolean isvideo = MediaFile.isVideoFileType(mFileType);
boolean isimage = MediaFile.isImageFileType(mFileType);
if (isaudio || isvideo || isimage) {
path = Environment.maybeTranslateEmulatedPathToInternal(new File(path))
.getAbsolutePath();
}
// we only extract metadata for audio and video files
if (isaudio || isvideo) {
// MIUI ADD:
MediaScannerInjector.processFileBegin(path, mContext);
// 重点! processFile
mScanSuccess = processFile(path, mimeType, this);
// MIUI ADD:
MediaScannerInjector.processFileEnd();
}
if (isimage) {
mScanSuccess = processImageFile(path);
}
String lowpath = path.toLowerCase(Locale.ROOT);
boolean ringtones = mScanSuccess && (lowpath.indexOf(RINGTONES_DIR) > 0);
boolean notifications = mScanSuccess &&
(lowpath.indexOf(NOTIFICATIONS_DIR) > 0);
boolean alarms = mScanSuccess && (lowpath.indexOf(ALARMS_DIR) > 0);
boolean podcasts = mScanSuccess && (lowpath.indexOf(PODCAST_DIR) > 0);
boolean music = mScanSuccess && ((lowpath.indexOf(MUSIC_DIR) > 0) ||
(!ringtones && !notifications && !alarms && !podcasts));
// 重点! endFile
result = endFile(entry, ringtones, notifications, alarms, music, podcasts);
}
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException in MediaScanner.scanFile()", e);
}
return result;
}
}
此处在doScanFile会调用processFile,processFile从jni会调用到frameworks/av/media/libstagefright/StagefrightMediaScanner.cpp中
// 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(); // beginFile
MediaScanResult result = processFileInternal(path, mimeType, client);
ALOGV("result: %d", result);
if (mimeType == NULL && result != MEDIA_SCAN_RESULT_OK) {
ALOGW("media scan failed for %s", path);
client.setMimeType("application/octet-stream");
}
client.endFile(); // 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;
}
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?
sp nullService;
status = mRetriever->setDataSource(nullService, 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;
}
}
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 },
{ "date", METADATA_KEY_DATE },
{ "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) { // 获取媒体信息
status = client.addStringTag(kKeyMap[i].tag, value);
if (status != OK) {
return MEDIA_SCAN_RESULT_ERROR;
}
}
}
return MEDIA_SCAN_RESULT_OK;
}
status_t MediaScannerClient::addStringTag(const char* name, const char* value)
{
handleStringTag(name, value);
return OK;
}
// handleStringTag会调用到frameworks/base/media/java/android/media/MediaScanner.java中
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.
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();
} else if (name.equalsIgnoreCase("album") || name.startsWith("album;")) {
mAlbum = value.trim();
} else if (name.equalsIgnoreCase("composer") || name.startsWith("composer;")) {
mComposer = value.trim();
} else if (mProcessGenres &&
(name.equalsIgnoreCase("genre") || name.startsWith("genre;"))) {
mGenre = getGenreName(value);
} else if (name.equalsIgnoreCase("year") || name.startsWith("year;")) {
mYear = parseSubstring(value, 0, 0);
} else if (name.equalsIgnoreCase("tracknumber") || name.startsWith("tracknumber;")) {
// track number might be of the form "2/12"
// we just read the number before the slash
int num = parseSubstring(value, 0, 0);
mTrack = (mTrack / 1000) * 1000 + num;
} else if (name.equalsIgnoreCase("discnumber") ||
name.equals("set") || name.startsWith("set;")) {
// set number might be of the form "1/3"
// we just read the number before the slash
int num = parseSubstring(value, 0, 0);
mTrack = (num * 1000) + (mTrack % 1000);
} else if (name.equalsIgnoreCase("duration")) {
mDuration = parseSubstring(value, 0, 0);
} else if (name.equalsIgnoreCase("writer") || name.startsWith("writer;")) {
mWriter = value.trim();
} else if (name.equalsIgnoreCase("compilation")) {
mCompilation = parseSubstring(value, 0, 0);
} else if (name.equalsIgnoreCase("isdrm")) {
mIsDrm = (parseSubstring(value, 0, 0) == 1);
} else if (name.equalsIgnoreCase("date")) {
mDate = parseDate(value);
} else if (name.equalsIgnoreCase("width")) {
mWidth = parseSubstring(value, 0, 0);
} else if (name.equalsIgnoreCase("height")) {
mHeight = parseSubstring(value, 0, 0);
} else {
//Log.v(TAG, "unknown tag: " + name + " (" + mProcessGenres + ")");
}
}
// 在endFile的时候会调用toValues()函数把上面的参数全部push到ContentValues中。
上面我们已经知道了如何获取一个目录下所有文件的媒体信息,接下来我们看看如何把获取的信息插入数据库中。其实也就是上面的beginFile和endFile的具体实现:
// frameworks/base/media/java/android/media/MediaScanner.java
public class MediaScanner implements AutoCloseable {
private class MyMediaScannerClient implements MediaScannerClient {
// beginFile的实际作用就是初始化一个entry并返回
public FileEntry beginFile(String path, String mimeType, long lastModified,
long fileSize, boolean isDirectory, boolean noMedia) {
// 此时输入的mimeType实际为null,isDirectory为false
mMimeType = mimeType;
mFileType = 0;
mFileSize = fileSize;
mIsDrm = false;
mScanSuccess = true;
if (!isDirectory) {
if (!noMedia && isNoMediaFile(path)) {
noMedia = true;
}
mNoMedia = noMedia;
if (mimeType != null) { // 如果有mimeType就用mimeType去获取文件类型
mFileType = MediaFile.getFileTypeForMimeType(mimeType);
}
if (mFileType == 0) {
MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path); // 自己去解析文件类型
if (mediaFileType != null) {
mFileType = mediaFileType.fileType;
if (mMimeType == null) {
mMimeType = mediaFileType.mimeType;
}
}
}
}
FileEntry entry = makeEntryFor(path);
long delta = (entry != null) ? (lastModified - entry.mLastModified) : 0;
boolean wasModified = delta > 1 || delta < -1;
if (entry == null || wasModified) {
if (wasModified) {
entry.mLastModified = lastModified;
} else {
entry = new FileEntry(0, path, lastModified,
(isDirectory ? MtpConstants.FORMAT_ASSOCIATION : 0));
}
entry.mLastModifiedChanged = true;
}
if (mProcessPlaylists && MediaFile.isPlayListFileType(mFileType)) {
mPlayLists.add(entry);
return null;
}
// clear all the metadata
mArtist = null;
mAlbumArtist = null;
mAlbum = null;
mTitle = null;
mComposer = null;
mGenre = null;
mTrack = 0;
mYear = 0;
mDuration = 0;
mPath = path;
mDate = 0;
mLastModified = lastModified;
mWriter = null;
mCompilation = 0;
mWidth = 0;
mHeight = 0;
return entry;
}
private Uri endFile(FileEntry entry, boolean ringtones, boolean notifications,
boolean alarms, boolean music, boolean podcasts)
throws RemoteException {
if (mArtist == null || mArtist.length() == 0) {
mArtist = mAlbumArtist;
}
ContentValues values = toValues(); // 把processFile解析出来的信息全部放进来
String title = 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);
}
String album = values.getAsString(Audio.Media.ALBUM);
if (MediaStore.UNKNOWN_STRING.equals(album)) {
album = values.getAsString(MediaStore.MediaColumns.DATA);
// extract last path segment before file name
int lastSlash = album.lastIndexOf('/');
if (lastSlash >= 0) {
int previousSlash = 0;
while (true) {
int idx = 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);
}
}
}
long rowId = 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);
} else if ((mFileType == MediaFile.FILE_TYPE_JPEG
|| mFileType == MediaFile.FILE_TYPE_HEIF
|| MediaFile.isRawImageFileType(mFileType)) && !mNoMedia) {
ExifInterface exif = null;
try {
exif = new ExifInterface(entry.mPath);
} catch (IOException ex) {
// exif is null
}
if (exif != null) {
float[] latlng = new float[2];
if (exif.getLatLong(latlng)) {
values.put(Images.Media.LATITUDE, latlng[0]);
values.put(Images.Media.LONGITUDE, latlng[1]);
}
long time = 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);
}
}
int orientation = 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);
}
}
}
Uri tableUri = mFilesUri;
MediaInserter inserter = mMediaInserter;
if (mScanSuccess && !mNoMedia) {
if (MediaFile.isVideoFileType(mFileType)) {
tableUri = mVideoUri;
} else if (MediaFile.isImageFileType(mFileType)) {
tableUri = mImagesUri;
} else if (MediaFile.isAudioFileType(mFileType)) {
tableUri = mAudioUri;
}
}
Uri result = null;
boolean needToSetSettings = false;
// 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 (notifications && !mDefaultNotificationSet) {
if (TextUtils.isEmpty(mDefaultNotificationFilename) ||
doesPathHaveFilename(entry.mPath, mDefaultNotificationFilename)) {
needToSetSettings = true;
}
} else if (ringtones && !mDefaultRingtoneSet) {
if (TextUtils.isEmpty(mDefaultRingtoneFilename) ||
doesPathHaveFilename(entry.mPath, mDefaultRingtoneFilename)) {
needToSetSettings = true;
}
} else if (alarms && !mDefaultAlarmSet) {
if (TextUtils.isEmpty(mDefaultAlarmAlertFilename) ||
doesPathHaveFilename(entry.mPath, mDefaultAlarmAlertFilename)) {
needToSetSettings = true;
}
}
if (rowId == 0) { // 如果是新的记录,则插入
if (mMtpObjectHandle != 0) {
values.put(MediaStore.MediaColumns.MEDIA_SCANNER_NEW_OBJECT_ID, mMtpObjectHandle);
}
if (tableUri == mFilesUri) {
int format = entry.mFormat;
if (format == 0) {
format = MediaFile.getFormatCode(entry.mPath, mMimeType);
}
values.put(Files.FileColumns.FORMAT, format);
}
if (inserter == null || needToSetSettings) {
// 如果inserter为null就用mMediaProvider去insert,否则就用inserter
if (inserter != null) {
inserter.flushAll();
}
result = mMediaProvider.insert(tableUri, values); // 插入记录
} else if (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);
values.remove(MediaStore.MediaColumns.DATA);
int mediaType = 0;
if (mScanSuccess && !MediaScanner.isNoMediaPath(entry.mPath)) {
int fileType = MediaFile.getFileTypeForMimeType(mMimeType);
if (MediaFile.isAudioFileType(fileType)) {
mediaType = FileColumns.MEDIA_TYPE_AUDIO;
} else if (MediaFile.isVideoFileType(fileType)) {
mediaType = FileColumns.MEDIA_TYPE_VIDEO;
} else if (MediaFile.isImageFileType(fileType)) {
mediaType = FileColumns.MEDIA_TYPE_IMAGE;
} else if (MediaFile.isPlayListFileType(fileType)) {
mediaType = FileColumns.MEDIA_TYPE_PLAYLIST;
}
values.put(FileColumns.MEDIA_TYPE, mediaType);
}
mMediaProvider.update(result, values, null, null); // 更新记录
}
if(needToSetSettings) {
if (notifications) {
setRingtoneIfNotSet(Settings.System.NOTIFICATION_SOUND, tableUri, rowId);
mDefaultNotificationSet = true;
} else if (ringtones) {
setRingtoneIfNotSet(Settings.System.RINGTONE, tableUri, rowId);
mDefaultRingtoneSet = true;
} else if (alarms) {
setRingtoneIfNotSet(Settings.System.ALARM_ALERT, tableUri, rowId);
mDefaultAlarmSet = true;
}
}
return result;
}
}
}
上面就是本文的全部内容啦~想要继续了解MediaProvider相关内容的同学,可以期待下一篇文章哦~