将 Android 11 中的文件保存到外部存储(SDK 30)

Posted

技术标签:

【中文标题】将 Android 11 中的文件保存到外部存储(SDK 30)【英文标题】:Saving files in Android 11 to external storage(SDK 30) 【发布时间】:2021-04-14 16:03:39 【问题描述】:

我正在 android 11(SDK 版本 30)上编写一个新应用程序,但我根本找不到有关如何将文件保存到外部存储的示例。 我阅读了他们的文档,现在知道他们基本上忽略了清单权限(READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE)。他们还会忽略 manifest.xml 应用程序标记中的 android:requestLegacyExternalStorage="true"

在他们的文档https://developer.android.com/about/versions/11/privacy/storage 中,他们写道,您需要启用DEFAULT_SCOPED_STORAGEFORCE_ENABLE_SCOPED_STORAGE 标志才能在您的应用中启用范围存储。

我必须在哪里启用这些功能? 当我这样做时,我如何以及何时获得写入外部存储的实际权限?有人可以提供工作代码吗? 我想保存 .gif、.png 和 .mp3 文件。所以我不想给画廊写信。

提前致谢。

【问题讨论】:

阅读:***.com/a/66366102/9917404 【参考方案1】:

对应所有Api,包括Api 30、Android 11:

public static File commonDocumentDirPath(String FolderName)

    File dir = null;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
    
        dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/" + FolderName);
    
    else
    
        dir = new File(Environment.getExternalStorageDirectory() + "/" + FolderName);
    

    // Make sure the path directory exists.
    if (!dir.exists())
    
        // Make it, if it doesn't exit
        boolean success = dir.mkdirs();
        if (!success)
        
            dir = null;
        
    
    return dir;

现在,使用commonDocumentDirPath 保存文件。

来自 cmets 的附注,getExternalStoragePublicDirectory 具有某些范围现在正在使用 Api 30、Android 11。干杯!感谢 CommonsWare 的提示。

【讨论】:

dir.mkdirs(); mkdirs() 不会抛出异常,而是返回 true 或 false。如果它返回 false,您应该让 commonDocumentDirPath 函数返回 null。调用者应该检查 null。 我已经编辑了我的答案。谢谢。 ???您删除了整个 mkdirs() 调用。请重新添加。你不能没有。 在使用 getParentDirs 保存文件时我已经保存了它。 getExternalStoragePublicDirectory 和 getExternalStorageDirectory 都已弃用...【参考方案2】:

您可以将文件保存到外部存储上的公共目录中。

如文档、下载、DCIM、图片等。

在版本 10 之前的常规方式中。

【讨论】:

但是,卸载并重新安装应用后,应用无法识别这些文件。 是的,我们知道,但这不是我们要求的。并且.. 使用 saf 应用程序可以。 什么是安全?以及应用程序如何识别它在卸载之前创建的旧文件,重新安装之后? 问题是关于Android 11【参考方案3】:

**最简单的答案和测试(Java)**

private void createFile(String title) 
        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("text/html");
        intent.putExtra(Intent.EXTRA_TITLE, title);

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) 
        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, Uri.parse("/Documents"));
    
    createInvoiceActivityResultLauncher.launch(intent);


private void createInvoice(Uri uri) 
    try 
        ParcelFileDescriptor pfd = getContentResolver().
                openFileDescriptor(uri, "w");
        if (pfd != null) 
            FileOutputStream fileOutputStream = new FileOutputStream(pfd.getFileDescriptor());
            fileOutputStream.write(invoice_html.getBytes());
            fileOutputStream.close();
            pfd.close();
        
     catch (IOException e) 
        e.printStackTrace();
    


/////////////////////////////////////////////////////
// You can do the assignment inside onAttach or onCreate, i.e, before the activity is displayed


    String invoice_html;
    ActivityResultLauncher<Intent> createInvoiceActivityResultLauncher;

    @Override
    protected void onCreate(Bundle savedInstanceState) 

       invoice_html = "<h1>Just for testing received...</h1>";
       createInvoiceActivityResultLauncher = registerForActivityResult(
                new ActivityResultContracts.StartActivityForResult(),
                result -> 
                    if (result.getResultCode() == Activity.RESULT_OK) 
                        // There are no request codes
                        Uri uri = null;
                        if (result.getData() != null) 
                            uri = result.getData().getData();
                            createInvoice(uri);
                            // Perform operations on the document using its URI.
                        
                    
                );

【讨论】:

一整天后,这就是我保存 PDF 的解决方案。非常感谢!【参考方案4】:

我正在使用这种方法,它确实对我有用 我希望我能帮助你。如果您有不清楚的地方,请随时问我

   Bitmap imageBitmap;

   OutputStream outputStream ;
    
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q)
            
                ContentResolver resolver = context.getContentResolver();
                ContentValues contentValues = new ContentValues();
                contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME,"Image_"+".jpg");
                contentValues.put(MediaStore.MediaColumns.MIME_TYPE,"image/jpeg");
                contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH,Environment.DIRECTORY_PICTURES + File.separator+"TestFolder");
                Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,contentValues);
                try 
                    outputStream =  resolver.openOutputStream(Objects.requireNonNull(imageUri) );
                    imageBitmap.compress(Bitmap.CompressFormat.JPEG,100,outputStream);
                    Objects.requireNonNull(outputStream);
                    Toast.makeText(context, "Image Saved", Toast.LENGTH_SHORT).show();
                    
                 catch (Exception e) 
                    Toast.makeText(context, "Image Not Not  Saved: \n "+e, Toast.LENGTH_SHORT).show();
    
                    e.printStackTrace();
                
    
            

清单文件(添加权限)

<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />

【讨论】:

嗨@Mark Nashat 对于低于 Q 的 android 版本怎么办。我的意思是如何处理这两种情况? 由此代码文件 filePath = Environment.getExternalStorageDirectory();文件目录 = new File(filePath.getAbsolutePath() + "/" + context.getString(R.string.app_name) + "/"); dir.mkdir();文件 file1 = new File(dir, System.currentTimeMillis() + ".jpg"); 尝试 outputStream = new FileOutputStream(file1); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); Toast.makeText(context, "图像保存", Toast.LENGTH_SHORT).show(); catch (FileNotFoundException e) Toast.makeText(context, "图像未保存: \n " + e, Toast.LENGTH_SHORT).show(); e.printStackTrace(); 尝试 outputStream.flush(); outputStream.close(); catch (IOException e) e.printStackTrace() 非常感谢@Mark Nashat。此外,最后一件事是如何知道文件是否存在于 android 10+ 的外部存储中。实际上,如果文件不存在,我想创建文件,否则覆盖它。 (就像你上面建议的答案)

以上是关于将 Android 11 中的文件保存到外部存储(SDK 30)的主要内容,如果未能解决你的问题,请参考以下文章

Android 11 拍照+录制视频保存到外部共享区域

Android将文件保存到外部存储

将Android中的位图保存为外部存储中的JPEG文件夹

Android 10:我都有哪些选择可以将外部存储上的文件保存到名为“/sdcard/my-app/”的目录中

如何将文件写入 Android 中的外部公共存储,以便它们在 Windows 中可见?

将文件从内部存储复制到Android中的外部存储