Как скопировать личную папку с содержимым в общий каталог в Android q и выше?

У меня есть личная папка, содержащая вложенные папки с изображениями и видео, мне нужно скопировать эту папку в Environment.DIRECTORY_PICTURES для общего доступа.

val fodler = getExternalFilesDir(Folder) // содержит подпапки с изображениями и видео

val DESTINATION = Environment.getExternalStorageDirectory().toString() + File.separator + Environment.DIRECTORY_PICTURES

copyFileOrDirectory(fodler.absolutePath, DESTINATION)

private fun copyFileOrDirectory(srcDir: String, dstDir: String) {
    try {
        val src: File = File(srcDir)
        val dst: File = File(dstDir, src.name)
        if (src.isDirectory) {
            val files = src.list()
            val filesLength = files.size
            for (i in 0 until filesLength) {
                val src1 = File(src, files[i]).path
                val dst1 = dst.path
                copyFileOrDirectory(src1, dst1)
            }
        } else {
            copyFile(src, dst)
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

private fun copyFile(sourceFile: File, destFile: File) {
    if (!destFile.getParentFile().exists()) destFile.getParentFile().mkdirs()
    if (!destFile.exists()) {
        destFile.createNewFile()
    }
    var source: FileChannel? = null
    var destination: FileChannel? = null
    try {
        source = FileInputStream(sourceFile).getChannel()
        destination = FileOutputStream(destFile).getChannel()
        destination.transferFrom(source, 0, source.size())
    } finally {
        if (source != null) {
            source.close()
        }
        if (destination != null) {
            destination.close()
        }
    }
}

теперь я получаю папку с файлами (со всем ее содержимым) в каталоге изображений, видимую всем и общедоступную, именно то, что мне нужно.

Это решение работает для уровней API 23-29 (android:requestLegacyExternalStorage=true для уровня API 29 в манифесте), но оно не работает на уровне API 30, поскольку getExternalStorageDirectory() устарело, а android:requestLegacyExternalStorage=true в манифесте не работает для апи уровня 30

какое будет решение для уровня API 30?


person Lev T.    schedule 10.04.2021    source источник
comment
android:requestLegacyExternalStorage=true в манифесте не работает для API-уровня 30 — работает, если ваш targetSdkVersion ниже 30. Однако Play Store потребует 30 позже в этом году. какое будет решение для уровня API 30? -- либо используйте ACTION_OPEN_DOCUMENT_TREE и позвольте пользователю выбирать, куда копировать содержимое, либо используйте MediaStore.   -  person CommonsWare    schedule 11.04.2021
comment
@CommonsWare Спасибо за быстрый ответ, я пытался использовать как ACTION_OPEN_DOCUMENT_TREE, так и MediaStore, но по какой-то причине всегда получаю одно и то же исключение: java.io.IOException: Нет такого файла или каталога. Буду очень признателен, если дадите пример рабочего кода   -  person Lev T.    schedule 11.04.2021
comment
Я всегда получаю одно и то же исключение - возможно, задаю отдельные вопросы о переполнении стека для каждого, предоставляя минимальный воспроизводимый пример и включая полная трассировка стека исключения. Я был бы очень признателен, если бы вы могли привести пример рабочего кода — извините, но у меня нет примеров кода для этих конкретных сценариев. У меня есть эта серия из 14 сообщений в блоге на общие темы, однако.   -  person CommonsWare    schedule 11.04.2021
comment
@CommonsWare Спасибо, что посоветовали мне использовать ACTION_OPEN_DOCUMENT_TREE, теперь все работает нормально   -  person Lev T.    schedule 11.04.2021


Ответы (2)


Я не знаю, как использовать kotlin, чтобы показать вам пример, но если ваша проблема в том, что в sdk ‹ 29 вы не можете использовать getExternalStorageDirectory(), это устарело, возможно, вам следует использовать MediaStore для сохранения файлов (изображений):

private void saveImage(Bitmap bitmap) {
    if (android.os.Build.VERSION.SDK_INT >= 29) {
        ContentValues values = contentValues(); //define your content values to save data
        values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + "Example Folder"); //here your path 
        values.put(MediaStore.Images.Media.IS_PENDING, true);
        Uri uri = this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); //this to insert data 
        if (uri != null) {
            try {
                saveImageToStream(bitmap, this.getContentResolver().openOutputStream(uri)); //so save the image file
                values.put(MediaStore.Images.Media.IS_PENDING, false);
                Toast.makeText(this, "It works!!", Toast.LENGTH_SHORT).show();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }
    } else { //and if sdk < 29 
        File directory = new File(Environment.getExternalStorageDirectory().toString() + '/' + getString(R.string.app_name));
        if (!directory.exists()) {
            directory.mkdirs();
        }
        String fileName = "file" + ".jpg";
        File file = new File(directory, fileName);
        try {
            saveImageToStream(bitmap, new FileOutputStream(file));
            ContentValues values = new ContentValues();
            values.put(MediaStore.Images.Media.DATA, file.getAbsolutePath());
            this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

    }
}

private ContentValues contentValues() { //here we do the some values to save in sdk>=29
    ContentValues values = new ContentValues();
    values.put(MediaStore.Images.Media.DISPLAY_NAME, "fileImage" + ".jpg");
    values.put(MediaStore.Images.Media.MIME_TYPE, "image/*");
    values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
    }
    return values;
}

private void saveImageToStream(Bitmap bitmap, OutputStream outputStream) { //and this to give a format 
if (outputStream != null) {
    try {
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
        outputStream.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
person Franco Joel Balsamo    schedule 11.04.2021

Для устройств с API 30 вы можете просто скопировать папку в папку с изображениями, как всегда.

Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) работает, поэтому используйте его.

Но... файлы должны быть изображениями.

Для API 29 запросите устаревшее внешнее хранилище в теге приложения файла манифеста для копирования в общедоступный каталог изображений.

person blackapps    schedule 11.04.2021