Android:范围存储:getContentResolver().update(...):COLUMN_LAST_MODIFIED:UnsupportedOperationException:不支持更新
Android: Scoped Storage: getContentResolver().update(...): COLUMN_LAST_MODIFIED: UnsupportedOperationException: Update not supported
我正在尝试更新 document/file 的最后修改日期,但我收到“UnsupportedOperationException:不支持更新”
重现步骤:
- 选择文档树
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(intent, 1972);
- 在 Activity 结果在所选目录中创建新文档:
Uri treeUri = resultData.getData();
String treeDocumentId = DocumentsContract.getTreeDocumentId(treeUri);
treeUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, treeDocumentId);
Uri uri = DocumentsContract.createDocument(getContentResolver(), treeUri, "text/plain", "test.txt");
- 正在尝试更新 document/file
的最后修改日期
ContentValues values = new ContentValues();
values.put(DocumentsContract.Document.COLUMN_LAST_MODIFIED, 1592143965000L);
getContentResolver().update(uri, values, null, null);
也试过插入,但结果总是一样:
Caused by: java.lang.UnsupportedOperationException: Update not supported
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:172)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:140)
at android.content.ContentProviderProxy.update(ContentProviderNative.java:578)
at android.content.ContentResolver.update(ContentResolver.java:2009)
有没有人遇到同样的问题,各自找到了解决这个问题的方法?
您提到了分区存储。我一直在寻找一种方法来通过 存储访问框架 更新文件 copied/created 的某些属性,例如创建日期、修改日期...。
这还需要使用 DocumentsContract
、DocumentFiles
根据 doc,所有列对客户端应用程序都是只读的
但是,我为更新使用 DocumentsContract.createDocument
创建的文件的属性所做的是将 Uri 转换为文件系统上的路径。然后以这种方式直接更新这些文件的属性:
// sourceUri & targetUri reference tree Uris
Path inFilePath = Paths.get(FileUtil.getFullDocIdPathFromTreeUri(sourceUri, context));
Path outFilePath = Paths.get(FileUtil.getFullDocIdPathFromTreeUri(targetUri, context));
BasicFileAttributes inAttrs = Files.readAttributes(inFilePath, BasicFileAttributes.class);
Files.getFileAttributeView(outFilePath, BasicFileAttributeView.class).setTimes(inAttrs.lastModifiedTime(), inAttrs.lastAccessTime(), inAttrs.creationTime());
FileUtil class(改编自:此 PullRequest https://github.com/nzbget/android/pull/12/files which references also )
package com......;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.storage.StorageManager;
import android.provider.DocumentsContract;
import androidx.annotation.Nullable;
import androidx.documentfile.provider.DocumentFile;
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
public final class FileUtil {
private static final String PRIMARY_VOLUME_NAME = "primary";
public static String getFullDocIdPathFromTreeUri(@Nullable final Uri treeUri, Context con) {
if (treeUri == null) return null;
String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri),con);
if (volumePath == null) return File.separator;
if (volumePath.endsWith(File.separator))
volumePath = volumePath.substring(0, volumePath.length() - 1);
String documentPath = getDocumentPathFromTreeUri(treeUri);
if (documentPath.endsWith(File.separator))
documentPath = documentPath.substring(0, documentPath.length() - 1);
if (documentPath.length() > 0) {
if (documentPath.startsWith(File.separator))
return volumePath + documentPath;
else
return volumePath + File.separator + documentPath;
}
else return volumePath;
}
@SuppressLint("ObsoleteSdkInt")
private static String getVolumePath(final String volumeId, Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null;
try {
StorageManager mStorageManager =
(StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
Method getUuid = storageVolumeClazz.getMethod("getUuid");
Method getPath = storageVolumeClazz.getMethod("getPath");
Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
Object result = getVolumeList.invoke(mStorageManager);
final int length = Array.getLength(result);
for (int i = 0; i < length; i++) {
Object storageVolumeElement = Array.get(result, i);
String uuid = (String) getUuid.invoke(storageVolumeElement);
Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);
// primary volume?
if (primary && PRIMARY_VOLUME_NAME.equals(volumeId))
return (String) getPath.invoke(storageVolumeElement);
// other volumes?
if (uuid != null && uuid.equals(volumeId))
return (String) getPath.invoke(storageVolumeElement);
}
// not found.
return null;
} catch (Exception ex) {
return null;
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static String getVolumeIdFromTreeUri(final Uri treeUri) {
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
final String[] split = docId.split(":");
if (split.length > 0) return split[0];
else return null;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static String getDocumentPathFromTreeUri(final Uri treeUri) {
//final String docId = DocumentsContract.getTreeDocumentId(treeUri);
final String docId = DocumentsContract.getDocumentId(treeUri);
final String[] split = docId.split(":");
if ((split.length >= 2) && (split[1] != null)) return split[1];
else return File.separator;
}
}
2021-03-30 编辑
如果您的 targetSDK >= 29,您可能会在设置时间戳时遇到“AccessDeniedException”。为避免这种情况,您可以查看下面总结的解决方案 :
- 对于 Android 10 支持,请将此
android:requestLegacyExternalStorage="true"
放入清单中。
- 对于Android 11,你需要MANAGE_EXTERNAL_STORAGE权限和Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION(一些代码示例在a github commit
过去 3 天,我都在为 SAF 科目大汗淋漓。我的努力是将文件从 FTP 下载到 Android API28 上的外部 SD 卡)。我成功地做到了这一点,但是面临着同样的问题,我无法将文件的最后修改日期修改为 FTP 上的日期。我尝试了与上述相同的方法。
有趣的是,我用
尝试了很好的旧 java 文件实用程序方式
file.setLastModified(l_lastModified);
令人惊讶的是,这有效!你不能用 java file utils 写文件,你必须使用 SAF,然后一旦文件被下载得足够奇怪,你就可以修改这个文件的属性,这完全没有意义。然而,这解决了我的问题并且有效。
我正在尝试更新 document/file 的最后修改日期,但我收到“UnsupportedOperationException:不支持更新”
重现步骤:
- 选择文档树
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(intent, 1972);
- 在 Activity 结果在所选目录中创建新文档:
Uri treeUri = resultData.getData();
String treeDocumentId = DocumentsContract.getTreeDocumentId(treeUri);
treeUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, treeDocumentId);
Uri uri = DocumentsContract.createDocument(getContentResolver(), treeUri, "text/plain", "test.txt");
- 正在尝试更新 document/file 的最后修改日期
ContentValues values = new ContentValues();
values.put(DocumentsContract.Document.COLUMN_LAST_MODIFIED, 1592143965000L);
getContentResolver().update(uri, values, null, null);
也试过插入,但结果总是一样:
Caused by: java.lang.UnsupportedOperationException: Update not supported
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:172)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:140)
at android.content.ContentProviderProxy.update(ContentProviderNative.java:578)
at android.content.ContentResolver.update(ContentResolver.java:2009)
有没有人遇到同样的问题,各自找到了解决这个问题的方法?
您提到了分区存储。我一直在寻找一种方法来通过 存储访问框架 更新文件 copied/created 的某些属性,例如创建日期、修改日期...。
这还需要使用 DocumentsContract
、DocumentFiles
根据 doc,所有列对客户端应用程序都是只读的
但是,我为更新使用 DocumentsContract.createDocument
创建的文件的属性所做的是将 Uri 转换为文件系统上的路径。然后以这种方式直接更新这些文件的属性:
// sourceUri & targetUri reference tree Uris
Path inFilePath = Paths.get(FileUtil.getFullDocIdPathFromTreeUri(sourceUri, context));
Path outFilePath = Paths.get(FileUtil.getFullDocIdPathFromTreeUri(targetUri, context));
BasicFileAttributes inAttrs = Files.readAttributes(inFilePath, BasicFileAttributes.class);
Files.getFileAttributeView(outFilePath, BasicFileAttributeView.class).setTimes(inAttrs.lastModifiedTime(), inAttrs.lastAccessTime(), inAttrs.creationTime());
FileUtil class(改编自:此 PullRequest https://github.com/nzbget/android/pull/12/files which references also )
package com......;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.storage.StorageManager;
import android.provider.DocumentsContract;
import androidx.annotation.Nullable;
import androidx.documentfile.provider.DocumentFile;
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
public final class FileUtil {
private static final String PRIMARY_VOLUME_NAME = "primary";
public static String getFullDocIdPathFromTreeUri(@Nullable final Uri treeUri, Context con) {
if (treeUri == null) return null;
String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri),con);
if (volumePath == null) return File.separator;
if (volumePath.endsWith(File.separator))
volumePath = volumePath.substring(0, volumePath.length() - 1);
String documentPath = getDocumentPathFromTreeUri(treeUri);
if (documentPath.endsWith(File.separator))
documentPath = documentPath.substring(0, documentPath.length() - 1);
if (documentPath.length() > 0) {
if (documentPath.startsWith(File.separator))
return volumePath + documentPath;
else
return volumePath + File.separator + documentPath;
}
else return volumePath;
}
@SuppressLint("ObsoleteSdkInt")
private static String getVolumePath(final String volumeId, Context context) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return null;
try {
StorageManager mStorageManager =
(StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
Method getUuid = storageVolumeClazz.getMethod("getUuid");
Method getPath = storageVolumeClazz.getMethod("getPath");
Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
Object result = getVolumeList.invoke(mStorageManager);
final int length = Array.getLength(result);
for (int i = 0; i < length; i++) {
Object storageVolumeElement = Array.get(result, i);
String uuid = (String) getUuid.invoke(storageVolumeElement);
Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);
// primary volume?
if (primary && PRIMARY_VOLUME_NAME.equals(volumeId))
return (String) getPath.invoke(storageVolumeElement);
// other volumes?
if (uuid != null && uuid.equals(volumeId))
return (String) getPath.invoke(storageVolumeElement);
}
// not found.
return null;
} catch (Exception ex) {
return null;
}
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public static String getVolumeIdFromTreeUri(final Uri treeUri) {
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
final String[] split = docId.split(":");
if (split.length > 0) return split[0];
else return null;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static String getDocumentPathFromTreeUri(final Uri treeUri) {
//final String docId = DocumentsContract.getTreeDocumentId(treeUri);
final String docId = DocumentsContract.getDocumentId(treeUri);
final String[] split = docId.split(":");
if ((split.length >= 2) && (split[1] != null)) return split[1];
else return File.separator;
}
}
2021-03-30 编辑
如果您的 targetSDK >= 29,您可能会在设置时间戳时遇到“AccessDeniedException”。为避免这种情况,您可以查看下面总结的解决方案
- 对于 Android 10 支持,请将此
android:requestLegacyExternalStorage="true"
放入清单中。 - 对于Android 11,你需要MANAGE_EXTERNAL_STORAGE权限和Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION(一些代码示例在a github commit
过去 3 天,我都在为 SAF 科目大汗淋漓。我的努力是将文件从 FTP 下载到 Android API28 上的外部 SD 卡)。我成功地做到了这一点,但是面临着同样的问题,我无法将文件的最后修改日期修改为 FTP 上的日期。我尝试了与上述相同的方法。 有趣的是,我用
尝试了很好的旧 java 文件实用程序方式file.setLastModified(l_lastModified);
令人惊讶的是,这有效!你不能用 java file utils 写文件,你必须使用 SAF,然后一旦文件被下载得足够奇怪,你就可以修改这个文件的属性,这完全没有意义。然而,这解决了我的问题并且有效。