针对 Android 11 (Api 30) 重命名视频/图像

Koa*_*ied 5 java android

我很难简单地重命名由应用程序创建但已放入文档文件夹中的文件。

编辑:

碰巧这些视频不是由应用程序创建的,但预计会由应用程序重命名。用户在开始时手动将视频放入文档文件夹中。我的错。

这是我的代码:

public static boolean renameVideoFile(Context c, File from, File to) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        try {
            Uri fromUri = FileProvider.getUriForFile(c, c.getPackageName() + ".provider", new File(FileUtils.getVideosDir(), from.getName()));
            ContentResolver contentResolver = c.getContentResolver();
            ContentValues contentValues = new ContentValues();

            contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 1);
            contentResolver.update(fromUri, contentValues, null, null);
            contentValues.clear();
            contentValues.put(MediaStore.Files.FileColumns.DISPLAY_NAME, to.getName());
            contentValues.put(MediaStore.Files.FileColumns.IS_PENDING, 0);
            contentResolver.update(fromUri, contentValues, null, null);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    } else {
        if (from.renameTo(to)) {
            removeMedia(c, from);
            addMedia(c, to);
            return true;
        } else {
            return false;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我克服了一些错误,但最终的错误是:

java.lang.UnsupportedOperationException:没有外部更新

这是 FileProvider 的内部问题

在 androidx.core.content.FileProvider.update(FileProvider.java:523)

编辑 #2 另外,这里是清单中我的提供者声明:

 <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths">
        </meta-data>
    </provider>
Run Code Online (Sandbox Code Playgroud)

这是我的路径声明。同样,这不会导致保存问题:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path
    name="internal_images"
    path="files/Pictures" />
<external-files-path
    name="internal_images_alternate"
    path="Pictures" />
<external-path
    name="external"
    path="." />
<external-files-path
    name="external_files"
    path="." />
<cache-path
    name="cache"
    path="." />
<external-cache-path
    name="external_cache"
    path="." />
<files-path
    name="files"
    path="." />
</paths>
Run Code Online (Sandbox Code Playgroud)

Koa*_*ied 0

因为我发现这是一个非常受欢迎的问题,所以我将继续更新我要执行的操作,因为其中一些代码已被弃用或无法工作。

首先在您的 build.gradle 文件中,实现 SAF 框架的 DocumentFile 类:

implementation 'androidx.documentfile:documentfile:1.0.1'
Run Code Online (Sandbox Code Playgroud)

接下来调用此方法来请求 SAF 的操作权限(您只需在用户安装时执行一次):

 private void requestDocumentTreePermissions() {
    // Choose a directory using the system's file picker.
    new AlertDialog.Builder(this)
            .setMessage("*Please Select A Folder For The App To Organize The Videos*")
            .setPositiveButton("Ok", new DialogInterface.OnClickListener() {
                @RequiresApi(api = Build.VERSION_CODES.Q)
                @Override
                public void onClick(DialogInterface dialog, int which) {
                    StorageManager sm = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
                    Intent intent = sm.getPrimaryStorageVolume().createOpenDocumentTreeIntent();

                    String startDir = "Documents";
                    Uri uri = intent.getParcelableExtra("android.provider.extra.INITIAL_URI");

                    String scheme = uri.toString();


                    scheme = scheme.replace("/root/", "/document/");
                    scheme += "%3A" + startDir;

                    uri = Uri.parse(scheme);
                    Uri rootUri = DocumentsContract.buildDocumentUri(
                            EXTERNAL_STORAGE_PROVIDER_AUTHORITY,
                            uri.toString()
                    );
                    Uri treeUri = DocumentsContract.buildTreeDocumentUri(
                            EXTERNAL_STORAGE_PROVIDER_AUTHORITY,
                            uri.toString()
                    );
                    uri = Uri.parse(scheme);
                    Uri treeUri2 = DocumentsContract.buildTreeDocumentUri(
                            EXTERNAL_STORAGE_PROVIDER_AUTHORITY,
                            uri.toString()
                    );
                    List<Uri> uriTreeList = new ArrayList<>();
                    uriTreeList.add(treeUri);
                    uriTreeList.add(treeUri2);
                    getPrimaryVolume().createOpenDocumentTreeIntent()
                            .putExtra(EXTRA_INITIAL_URI, rootUri);
                    Intent intent2 = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
                    // Optionally, specify a URI for the directory that should be opened in
                    // the system file picker when it loads.
                    intent2.addFlags(
                            Intent.FLAG_GRANT_READ_URI_PERMISSION
                                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                                    | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
                                    | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
                    intent2.putExtra(EXTRA_INITIAL_URI, rootUri);
                    startActivityForResult(intent2, 99);
                }
            })
            .setCancelable(false)
            .show();


}
Run Code Online (Sandbox Code Playgroud)

接下来存储一些持久权限:

    @Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 99 && resultCode == RESULT_OK) {
        //get back the document tree URI (in this case we expect the documents root directory)
        Uri uri = data.getData();
        //now we grant permanent persistant permissions to our contentResolver and we are free to open up sub directory Uris as we please until the app is uninstalled
        getSharedPreferences().edit().putString(ACCESS_FOLDER, uri.toString()).apply();
        final int takeFlags = (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        getApplicationContext().getContentResolver().takePersistableUriPermission(uri, takeFlags);
        //simply recreate the activity although you could call some function at this point
        recreate();
    }
}
Run Code Online (Sandbox Code Playgroud)

最后在正确的文件上调用 documentFile 的重命名方法

DocumentFile df = DocumentFile.fromTreeUri(MainActivity.this, uri);
df = df.findFile("CurrentName")
df.renameTo("NewName");
Run Code Online (Sandbox Code Playgroud)

您还可以使用内容解析器打开 InputStreams 和 OutputStreams,因为使用以下代码片段向内容解析器授予了该 DocumentFile 的持久 URI 权限:

getContentResolver().openInputStream(df.getUri());
getContentResolver().openOutputStream(df.getUri());
Run Code Online (Sandbox Code Playgroud)

您可以使用列出文件

df.listFiles();
Run Code Online (Sandbox Code Playgroud)

或者您可以使用以下方式列出文件:

public static DocumentFile findFileInDirectoryMatchingName(Context mContext, Uri mUri, String name) {
    final ContentResolver resolver = mContext.getContentResolver();
    final Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(mUri,
            DocumentsContract.getDocumentId(mUri));
    Cursor c = null;
    try {
        c = resolver.query(childrenUri, new String[]{
                DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                DocumentsContract.Document.COLUMN_DISPLAY_NAME,
                DocumentsContract.Document.COLUMN_MIME_TYPE,
                DocumentsContract.Document.COLUMN_LAST_MODIFIED

        }, DocumentsContract.Document.COLUMN_DISPLAY_NAME + " LIKE '?%'", new String[]{name}, null);
        c.moveToFirst();
        while (!c.isAfterLast()) {
            final String filename = c.getString(1);
            final String mimeType = c.getString(2);
            final Long lastModified = c.getLong(3);
            if (filename.contains(name)) {
                final String documentId = c.getString(0);
                final Uri documentUri = DocumentsContract.buildDocumentUriUsingTree(mUri,
                        documentId);

                return DocumentFile.fromTreeUri(mContext, documentUri);
            }
            c.moveToNext();
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (c != null) {
            c.close();
        }
    }

    return null;
}
Run Code Online (Sandbox Code Playgroud)

它比 df.listFiles() 方法运行得更快