前景
最近有个需求:PC端传过来的文件,需要保持原来的LastModified time。
自然而然的想到了用file.setLastModified(), 实际情况是虽然返回true,但是没有修改成功。一个三星android12的手机除外。
通过研究,发现不同厂商,不同android版本, 都有不同表现。总的来说就两个方式:
1. Java file API
file.setLastModified(),从有android开始这个接口就是个废接口。google就没有实现它。所以测试的几款手机除一款三星android12的手机可以设置成功,其他都不行。三星一款android 8的也不行。几款android10以上的手机虽然能返回true,但是设置是不成功的。
2. MediaStore API
通过更新数据库,来实现修改LastModified time。这种方式基本都能成功。
也有例外,Google Pixel 4 android 12的手机不行,这个手机需要先调file.setLastModified(),然后再用media store更新才能成功。
更新数据库,不同的andriod版本也是不同的。分android 10以前,及以后。
- android 10以前
比较简单。
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Files.FileColumns.DATE_MODIFIED, time / 1000);
external = getContext().getContentResolver().update(getMediaBaseUri(path), contentValues, MediaStore.MediaColumns.DATA + " = \"" + path + "\"", null);
public static Uri getMediaBaseUri(String path) {
String mimeType = FileUtil.getMimeType(path);
if (TextUtils.isEmpty(mimeType))
return null;
mimeType = mimeType.toLowerCase();
if (mimeType.contains("image")) {
return MediaStore.Images.Media.getContentUri("external");
} else if (mimeType.contains("video")) {
return MediaStore.Video.Media.getContentUri("external");
} else if (mimeType.contains("audio")) {
return MediaStore.Audio.Media.getContentUri("external");
}
return null;
}
- android 10及以后
android 10及以后, 由于分区存储的关系,复杂了一点点。 在数据库表中,增加了一个字段isPending, 想修改LastModified time, 必须先把isPending更新成1, 设置完了以后再设置回0. 不设置回0, 文件就找不到了。
要修改的文件必须是你自己app创建的才能修改。
必须是绝对Uri, 也就是说,先找到文件的id, 再拼接成一个file的uri, 这样才能更新成功。要不然会抛异常。
java.lang.IllegalArgumentException:
Movement of content://media/external/image/media
which isn't part of well-defined collection not allowed完整的代码如下:
public static boolean setLastModifiedTime(String path, long time) {
int external = 0;
try {
File file = new File(path);
boolean result = file.setLastModified(time); //google pixel 4(android 12)need both call file api and media store api, samsung android 12 only need call file api.
// if (result) {
// if (file.lastModified() == time) {
// return true;
// }
// }
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.Files.FileColumns.DATE_MODIFIED, time / 1000);
Uri uri = getUriByPath(path);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContentValues startPendingContentValues = new ContentValues();
startPendingContentValues.put(MediaStore.MediaColumns.IS_PENDING, 1);
external = getContext().getContentResolver().update(uri, startPendingContentValues, null, null);
contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0);
external = getContext().getContentResolver().update(uri, contentValues, null, null);
} else {
external =getContext().getContentResolver().update(getMediaBaseUri(path), contentValues, MediaStore.MediaColumns.DATA + " = \"" + path + "\"", null);
}
} catch (Exception e) {
Log.e("FileUtil", "setLastModifiedTime error" + e.getMessage());
}
return external == 1;
}
public static Uri getMediaBaseUri(String path) {
String mimeType = FileUtil.getMimeType(path);
if (TextUtils.isEmpty(mimeType))
return null;
mimeType = mimeType.toLowerCase();
if (mimeType.contains("image")) {
return MediaStore.Images.Media.getContentUri("external");
} else if (mimeType.contains("video")) {
return MediaStore.Video.Media.getContentUri("external");
} else if (mimeType.contains("audio")) {
return MediaStore.Audio.Media.getContentUri("external");
}
return null;
}MediaStore API的方式可以引申到其他文件属性的修改,比如一些图片,音乐,视频所特有的属性。有的文件属性不需要设置IS_PENDING, 而有的则需要,那怎么决定呢? 我们在MediaProvider源码中可以看到,sMutableColumns 中的不需要先设置IS_PENDING,而其他属性都是需要的。
static {
sMutableColumns.add(MediaStore.MediaColumns.DATA);
sMutableColumns.add(MediaStore.MediaColumns.RELATIVE_PATH);
sMutableColumns.add(MediaStore.MediaColumns.DISPLAY_NAME);
sMutableColumns.add(MediaStore.MediaColumns.IS_PENDING);
sMutableColumns.add(MediaStore.MediaColumns.IS_TRASHED);
sMutableColumns.add(MediaStore.MediaColumns.IS_FAVORITE);
sMutableColumns.add(MediaStore.MediaColumns.OWNER_PACKAGE_NAME);
sMutableColumns.add(MediaStore.Audio.AudioColumns.BOOKMARK);
sMutableColumns.add(MediaStore.Video.VideoColumns.TAGS);
sMutableColumns.add(MediaStore.Video.VideoColumns.CATEGORY);
sMutableColumns.add(MediaStore.Video.VideoColumns.BOOKMARK);
sMutableColumns.add(MediaStore.Audio.Playlists.NAME);
sMutableColumns.add(MediaStore.Audio.Playlists.Members.AUDIO_ID);
sMutableColumns.add(MediaStore.Audio.Playlists.Members.PLAY_ORDER);
sMutableColumns.add(MediaStore.DownloadColumns.DOWNLOAD_URI);
sMutableColumns.add(MediaStore.DownloadColumns.REFERER_URI);
sMutableColumns.add(MediaStore.Files.FileColumns.MIME_TYPE);
sMutableColumns.add(MediaStore.Files.FileColumns.MEDIA_TYPE);
}