Android 10以上,修改文件属性的问题

前景

最近有个需求: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);
    }

https://android.googlesource.com/platform/packages/providers/MediaProvider/+/refs/heads/master/src/com/android/providers/media/MediaProvider.javahttps://android.googlesource.com/platform/packages/providers/MediaProvider/+/refs/heads/master/src/com/android/providers/media/MediaProvider.java


版权声明:本文为zjuter原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。