getExternalStoragePublicDirectory在Android Q中已弃用

时间:2019-06-05 21:53:50

标签: android

由于getExternalStoragePublicDirectory在Android Q中已被弃用,建议使用其他方式。那么如何指定要将相机应用程序生成的照片存储到DCIM文件夹或DCIM中的自定义子文件夹中?

文档指出,接下来的3个选项是新的首选选项:

  1. Context#getExternalFilesDir(String)
  2. MediaStore
  3. Intent#ACTION_OPEN_DOCUMENT。

选项1不存在,因为这意味着如果卸载该应用,照片将被删除。

选项3也不是一个选择,因为它将要求用户通过SAF文件浏览器选择位置。

我们剩下选项2,即MediaStore;但是没有文档说明如何用它代替Android Q中的getExternalStoragePublicDirectory。

8 个答案:

答案 0 :(得分:4)

@CommonsWare的答案是惊人的。但是对于那些想要使用Java的人,您需要尝试一下:

ContentResolver resolver = context.getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);

Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);

因此,在我有mimeType的地方,您输入所需的MIME类型。例如,如果您想要txt(@Panache),则应使用以下字符串替换mimeType"text/plain"。以下是哑剧类型的列表:https://www.freeformatter.com/mime-types-list.html

此外,在我有变量name的情况下,请根据您的情况将其替换为文件名。

答案 1 :(得分:4)

由于安全问题,面向 Android Q - API 29+ 的应用默认禁用存储访问。如果要启用它在AndroidManifest.xml中添加如下属性:

<manifest ... >
    <!-- This attribute is "false" by default for Android Q or higher -->
    <application android:requestLegacyExternalStorage="true" ... >
     ...
    </application>
</manifest>

那么你必须使用 getExternalStorageDirectory() 而不是 getExternalStoragePublicDirectory()

示例:如果您想在内部存储中创建一个目录(如果不存在)。

 File mediaStorageDir = new File(Environment.getExternalStorageDirectory() + "/SampleFolder");

 // Create the storage directory if it does not exist
 if (! mediaStorageDir.exists()){
     if (! mediaStorageDir.mkdirs()){
         Log.d("error", "failed to create directory");
     }
 }

答案 2 :(得分:2)

根据文档,对the RELATIVE_PATH使用DCIM/...,其中...就是您的自定义子目录。因此,您最终会遇到类似这样的情况:

      val resolver = context.contentResolver
      val contentValues = ContentValues().apply {
        put(MediaStore.MediaColumns.DISPLAY_NAME, "CuteKitten001")
        put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
        put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/PerracoLabs")
      }

      val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)

      resolver.openOutputStream(uri).use {
        // TODO something with the stream
      }

请注意,由于RELATIVE_PATH是API级别29的新功能,因此您需要在较新的设备上使用此方法,而在较旧的设备上使用getExternalStoragePublicDirectory()

答案 3 :(得分:1)

import android.content.ContentValues
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.MediaStore
import android.util.Log
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity

class MyActivity : AppCompatActivity() {

    @RequiresApi(Build.VERSION_CODES.Q)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_mine)
        val editText: EditText = findViewById(R.id.edt)
        val write: Button = findViewById(R.id.Output)
        val read: Button = findViewById(R.id.Input)
        val textView: TextView = findViewById(R.id.textView)

        val resolver = this.contentResolver
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, "myDoc1")
            put(MediaStore.MediaColumns.MIME_TYPE, "text/plain")
            put(MediaStore.MediaColumns.RELATIVE_PATH, "Documents")
        }
        val uri: Uri? = resolver.insert(MediaStore.Files.getContentUri("external"), contentValues)
        Log.d("Uri", "$uri")

        write.setOnClickListener {
            val edt : String = editText.text.toString()
            if (uri != null) {
                resolver.openOutputStream(uri).use {
                    it?.write("$edt".toByteArray())
                    it?.close()

                }
            }
        }
        read.setOnClickListener {
            if (uri != null) {
                resolver.openInputStream(uri).use {
                    val data = ByteArray(50)
                    it?.read(data)
                    textView.text = String(data)

                }
            }
        }
    }
}

在这里,我通过将文本写入编辑文本并单击“写入”按钮将文本文件存储在手机的文档文件夹中,它将保存带有写入文本的文件。 单击“读取”按钮时,它将从该文件中获取文本,然后将其显示在文本视图中。

<块引用>

它不会在低于 android Q 或 android 10 的设备上运行,因为 RELATIVE_PATH 只能在这些版本中使用。

答案 4 :(得分:0)

我的应用程序针对Android 28,但以上解决方案适用于Android 29。 我在应用程序的文件夹中生成了一个图像,但随后希望它出现在图库中。 上面的代码没有把我带到那里。以下是我得出的可行解决方案。保存位图并刷新并关闭文件后,我立即调用了此方法。

void galleryAddPic(Bitmap bitmap, String pathName, String fileName) {
    ContentResolver resolver = context.getContentResolver();
    ContentValues contentValues = new ContentValues();
    contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
    contentValues.put(MediaStore.MediaColumns.DATA, pathName);
    contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg");

    MediaStore.Images.Media.insertImage(resolver, bitmap, fileName, "Mandelbrot fractal image");
}

关键是方法     insertImage(ContentResolver cr,位图源,字符串标题,字符串描述)

传递的路径名是:/storage/emulated/0/Android/data/com.tropicalcoder.mandelbrot/files/Pictures/IMG_20191129_1141.jpg

传递的文件名是:IMG_20191129_1141.jpg

我找不到应该为“ title”提供的内容,因此给了它文件名和说明。但是,Google相簿不会显示说明。

答案 5 :(得分:0)

对于 Xamarin.Android,来自已发布项目的以下代码可能会有所帮助:

    Java.IO.File jFolder;

    if ((int)Android.OS.Build.VERSION.SdkInt >= 29)
    {
        jFolder = new Java.IO.File(Android.App.Application.Context.GetExternalFilesDir(Environment.DirectoryDcim), "Camera");
    }
    else
    {
        jFolder = new Java.IO.File(Environment.GetExternalStoragePublicDirectory(Environment.DirectoryDcim), "Camera");
    }

    if (!jFolder.Exists())
        jFolder.Mkdirs();

    var filename = GenerateJpgFileName();
    var jFile = new Java.IO.File(jFolder, filename);
    var fullFilename = jFile.AbsoluteFile.ToString();

    using (var output = new System.IO.FileStream(fullFilename, System.IO.FileMode.Create))
    {
        outputBitmap.Compress(Bitmap.CompressFormat.Jpeg, 90, output);
        output.Close();
    }

未在 Android 中使用权限,而是在使用 Xamarin.Essentials 的共享项目中完成的。

答案 6 :(得分:0)

一般我是这样用的:

     var data: File =Environment.getExternalStoragePublicDirectory
(Environment.DIRECTORY_DOWNLOADS)
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                        data = getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)!!
                    }

答案 7 :(得分:0)

如果您想将文件保存在特定于应用程序的外部存储中,可以使用 context.getExternalFilesDir()。许多答案都指出这一点。

然而,这不是这个问题的答案,因为 getExternalFilesDir() 是应用特定的外部存储,getExternalStoragePublicDirectory() 是共享存储。

例如,您想将下载的 pdf 文件保存到“共享”下载目录。你是怎样做的 ?对于 api 29 及更高版本,您可以在没有许可的情况下执行此操作。

对于 api 28 及以下版本,您需要 getExternalStoragePublicDirectory() 方法,但它已被弃用。如果您不想使用这种已弃用的方法怎么办?然后您可以使用 SAF 文件浏览器(Intent#ACTION_OPEN_DOCUMENT)。正如问题中所说,这需要用户手动选择位置。

这正是 Google 想要的。 为了改善用户隐私,不推荐直接访问共享/外部存储设备。

<块引用>

当应用以 Build.VERSION_CODES.Q 为目标时,此路径返回 应用程序不再可以直接访问方法。应用程序可以继续 通过迁移到共享/外部存储来访问存储的内容 替代方案,例如 Context#getExternalFilesDir(String)、MediaStore、 或意图#ACTION_OPEN_DOCUMENT。

详情见以下链接:

https://developer.android.com/reference/android/os/Environment#getExternalStorageDirectory()