如何将包含内容的私有文件夹复制到 android q 及更高版本中的公共目录?

Posted

技术标签:

【中文标题】如何将包含内容的私有文件夹复制到 android q 及更高版本中的公共目录?【英文标题】:How to copy a private folder with content to public directory in android q and higher? 【发布时间】:2021-07-06 10:38:23 【问题描述】:

我有一个私人文件夹,其中包含带有图像和视频的子文件夹, 我需要将该文件夹复制到 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" 不适用于 api 级别 30

api 级别 30 的解决方案是什么?

【问题讨论】:

"android:requestLegacyExternalStorage="true" in menifest doesn't work for api level 30" - 如果您的 targetSdkVersion 低于 30,则可以使用。Play 商店今年晚些时候将需要 30,尽管。 “api 30 级的解决方案是什么?” -- 要么使用ACTION_OPEN_DOCUMENT_TREE,让用户选择复制内容的位置,要么使用MediaStore @CommonsWare 感谢您的快速回答,我尝试同时使用 ACTION_OPEN_DOCUMENT_TREE 和 MediaStore,但由于某种原因,我总是得到相同的异常:java.io.IOException:没有这样的文件或目录。如果您能提供一个工作代码示例,我将不胜感激 “我总是得到相同的异常”——也许针对每个问题分别提出堆栈溢出问题,提供 minimal reproducible example 并包括异常的完整堆栈跟踪。 “如果您能提供一个工作代码示例,我将不胜感激”——抱歉,我没有这些特定场景的代码示例。不过,我在一般主题上有this 14 blog post series。 @CommonsWare 感谢您指导我使用 ACTION_OPEN_DOCUMENT_TREE,现在可以正常使用了 【参考方案1】:

我不知道如何使用 kotlin 向您展示示例,但是,如果您的问题是 sdk 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();
    

【讨论】:

【参考方案2】:

对于 api 30 设备,您只需将文件夹复制到 Pictures 目录即可。

Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) 有效,所以使用它。

但是..文件必须是图片。

对于 api 29 请求清单文件的应用程序标签中的旧外部存储复制到公共图片目录。

【讨论】:

以上是关于如何将包含内容的私有文件夹复制到 android q 及更高版本中的公共目录?的主要内容,如果未能解决你的问题,请参考以下文章

如何把android开发的时候把LogCat里的内容复制出来?如果不能复制,要是转储到文件中请说明详细步骤?

如何将 .AAR 作为新模块包含在 android 项目中? [复制]

如何使用 android studio 将复制任务运行到资产文件夹中

android如何将SQLite 3.7 WAL文件的内容合并到主数据库文件中?

Android:如何获取应用程序私有文件的视频缩略图?

如何将 MATLAB libsvm 模型复制到 android 应用程序文件夹