使用 MediaStore 在 Android Q 中创建/复制文件

Pan*_*che 13 android mediastore android-contentresolver

我正在尝试找到可以处理创建和复制除媒体文件(图片/视频/音频)之外的任何文件的方法,以便在 Android Q 的内部存储中从一个地方复制到另一个地方。在此我在我的应用程序文件夹中创建了我的文件我希望那些移动到下载文件夹或我可以在内部存储中创建的某个目录,然后移动它们。

我搜索并发现下面修改过的代码,但缺少一些使其可行的东西。有人可以帮忙吗。

ContentResolver contentResolver = getContentResolver();

ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, "sam.txt");
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "text/plain");
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);

Uri uri = contentResolver.insert(MediaStore.Files.getContentUri("external"), contentValues);

try {
    InputStream inputStream = contentResolver.openInputStream(uri);
    OutputStream outputStream = new FileOutputStream(Environment.DIRECTORY_DOWNLOADS+"/");

    byte[] buffer = new byte[1024];

    int length;

    //copy the file content in bytes
    while ((length = inputStream.read(buffer)) > 0) {
            outputStream.write(buffer, 0, length);
        }
        inputStream.close();
        outputStream.close();
        } catch (Exception e) {
           e.printStackTrace();
        }
Run Code Online (Sandbox Code Playgroud)

上面是完整的代码,因为我收到错误“未知 URL”。有什么不见了?请帮忙。

Sam*_*hen 25

1. 创建和写入文件

createAndWriteButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        try {
            ContentValues values = new ContentValues();

            values.put(MediaStore.MediaColumns.DISPLAY_NAME, "menuCategory");       //file name                     
            values.put(MediaStore.MediaColumns.MIME_TYPE, "text/plain");        //file extension, will automatically add to file
            values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOCUMENTS + "/Kamen Rider Decade/");     //end "/" is not mandatory

            Uri uri = getContentResolver().insert(MediaStore.Files.getContentUri("external"), values);      //important!

            OutputStream outputStream = getContentResolver().openOutputStream(uri);

            outputStream.write("This is menu category data.".getBytes());

            outputStream.close();

            Toast.makeText(view.getContext(), "File created successfully", Toast.LENGTH_SHORT).show();
        } catch (IOException e) {
            Toast.makeText(view.getContext(), "Fail to create file", Toast.LENGTH_SHORT).show();
        }
    }
});
Run Code Online (Sandbox Code Playgroud)

2. 查找和读取文件

findAndReadButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Uri contentUri = MediaStore.Files.getContentUri("external");

        String selection = MediaStore.MediaColumns.RELATIVE_PATH + "=?";

        String[] selectionArgs = new String[]{Environment.DIRECTORY_DOCUMENTS + "/Kamen Rider Decade/"};

        Cursor cursor = getContentResolver().query(contentUri, null, selection, selectionArgs, null);

        Uri uri = null;

        if (cursor.getCount() == 0) {
            Toast.makeText(view.getContext(), "No file found in \"" + Environment.DIRECTORY_DOCUMENTS + "/Kamen Rider Decade/\"", Toast.LENGTH_LONG).show();
        } else {
            while (cursor.moveToNext()) {
                String fileName = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME));

                if (fileName.equals("menuCategory.txt")) {
                    long id = cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns._ID));

                    uri = ContentUris.withAppendedId(contentUri, id);

                    break;
                }
            }

            if (uri == null) {
                Toast.makeText(view.getContext(), "\"menuCategory.txt\" not found", Toast.LENGTH_SHORT).show();
            } else {
                try {
                    InputStream inputStream = getContentResolver().openInputStream(uri);

                    int size = inputStream.available();

                    byte[] bytes = new byte[size];

                    inputStream.read(bytes);

                    inputStream.close();

                    String jsonString = new String(bytes, StandardCharsets.UTF_8);

                    AlertDialog.Builder builder = new AlertDialog.Builder(view.getContext());

                    builder.setTitle("File Content");
                    builder.setMessage(jsonString);
                    builder.setPositiveButton("OK", null);

                    builder.create().show();
                } catch (IOException e) {
                    Toast.makeText(view.getContext(), "Fail to read file", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }
});
Run Code Online (Sandbox Code Playgroud)

3. 查找和覆盖文件

findAndWriteButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        Uri contentUri = MediaStore.Files.getContentUri("external");

        String selection = MediaStore.MediaColumns.RELATIVE_PATH + "=?";

        String[] selectionArgs = new String[]{Environment.DIRECTORY_DOCUMENTS + "/Kamen Rider Decade/"};    //must include "/" in front and end

        Cursor cursor = getContentResolver().query(contentUri, null, selection, selectionArgs, null);

        Uri uri = null;

        if (cursor.getCount() == 0) {
            Toast.makeText(view.getContext(), "No file found in \"" + Environment.DIRECTORY_DOCUMENTS + "/Kamen Rider Decade/\"", Toast.LENGTH_LONG).show();
        } else {
            while (cursor.moveToNext()) {
                String fileName = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME));

                if (fileName.equals("menuCategory.txt")) {                          //must include extension
                    long id = cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns._ID));

                    uri = ContentUris.withAppendedId(contentUri, id);

                    break;
                }
            }

            if (uri == null) {
                Toast.makeText(view.getContext(), "\"menuCategory.txt\" not found", Toast.LENGTH_SHORT).show();
            } else {
                try {
                    OutputStream outputStream = getContentResolver().openOutputStream(uri, "rwt");      //overwrite mode, see below

                    outputStream.write("This is overwritten data?\n????????".getBytes());

                    outputStream.close();

                    Toast.makeText(view.getContext(), "File written successfully", Toast.LENGTH_SHORT).show();
                } catch (IOException e) {
                    Toast.makeText(view.getContext(), "Fail to write file", Toast.LENGTH_SHORT).show();
                }
            }
        }
    }
});
Run Code Online (Sandbox Code Playgroud)

演示:https : //www.youtube.com/watch?v=idsUMiWjfnM

希望这可以帮助你。


art*_*art 3

正如您提到的,Environment.getExternalStoragePublicDirectory已标记为已弃用。因此,没有常规方法可以获取下载目录的路径来保存文件。或者,您可以使用ACTION_CREATE_DOCUMENT显示路径选择器,然后使用返回的 uri 将文件写入所选位置。

这是显示选择器的方法:

// Request code for creating a document.

const val CREATE_FILE = 1

private fun createFile(pickerInitialUri: Uri) {
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "text/plain"
        putExtra(Intent.EXTRA_TITLE, "sam.txt")

        // Optionally, specify a URI for the directory that should be opened in
        // the system file picker before your app creates the document.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }
    startActivityForResult(intent, CREATE_FILE)
}
Run Code Online (Sandbox Code Playgroud)

这是获取选定 uri 并写入文件的方法:

override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
    if (requestCode == CREATE_FILE && resultCode == Activity.RESULT_OK) {
        // The result data contains a URI for the document or directory that
        // the user selected.
        resultData?.data?.also { outputUri ->
            // Perform operations on the document using its URI.
            FileInputStream(inputFile).use { inputStream ->
                context.contentResolver.openFileDescriptor(outputUri, "w")?.use {
                    FileOutputStream(it.fileDescriptor).use { outputStream ->
                        FileUtils.copy(inputStream, outputStream)
                    }
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

更多信息可以在这里找到。

编辑:

ACTION_OPEN_DOCUMENT_TREE可以使用选择一个目录来保存文件。然后使用takePersistableUriPermission方法获取授予的持久权限,以便在设备重新启动后能够使用它。然后用来DocumentFile执行文件操作。

打开目录请求:

private static final int OPEN_DIRECTORY_REQUEST_CODE = 1;

void openDirectory() {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
    startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE);
}
Run Code Online (Sandbox Code Playgroud)

接收选择的目录并获取持久权限:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == OPEN_DIRECTORY_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
        Uri directoryUri = data.getData();
        if (directoryUri == null)
            return;
        requireContext()
                .getContentResolver()
                .takePersistableUriPermission(directoryUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        // persist picked uri to be able to reuse it later
    } else
        super.onActivityResult(requestCode, resultCode, data);
}
Run Code Online (Sandbox Code Playgroud)

最后保存该文件:

private void persistFile(@NonNull Uri directoryUri,
                         @NonNull File fileToPersist,
                         @NonNull String mimeType,
                         @NonNull String displayName) {
    DocumentFile dirFile = DocumentFile.fromSingleUri(requireContext(), directoryUri);
    if (dirFile != null) {
        DocumentFile file = dirFile.createFile(mimeType, displayName);
        if (file != null) {
            Uri outputUri = file.getUri();
            try (ParcelFileDescriptor fd = requireContext().getContentResolver().openFileDescriptor(outputUri, "w")) {
                if (fd != null) {
                    try (FileInputStream inputStream = new FileInputStream(fileToPersist)) {
                        try (FileOutputStream outputStream = new FileOutputStream(fd.getFileDescriptor())) {
                            FileUtils.copy(inputStream, outputStream);
                        }
                    }
                }
            } catch (Throwable th) {
                th.printStackTrace();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

查看此存储库以获取使用示例ACTION_CREATE_DOCUMENT