API级别29中不推荐使用Environment.getExternalStorageDirectory()


109

在android Java上工作,最近将SDK更新到API级别29,现在显示警告,指出

Environment.getExternalStorageDirectory() 在API级别29中已弃用

我的代码是

private void saveImage() {

if (requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {

    final String folderPath = Environment.getExternalStorageDirectory() + "/PhotoEditors";
    File folder = new File(folderPath);
    if (!folder.exists()) {
        File wallpaperDirectory = new File(folderPath);
        wallpaperDirectory.mkdirs();
    }


    showLoading("Saving...");
    final String filepath=folderPath
                + File.separator + ""
                + System.currentTimeMillis() + ".png";
    File file = new File(filepath);

    try {
        file.createNewFile();
        SaveSettings saveSettings = new SaveSettings.Builder()
                .setClearViewsEnabled(true)
                .setTransparencyEnabled(true)
                .build();
        if(isStoragePermissionGranted() ) {
            mPhotoEditor.saveAsFile(file.getAbsolutePath(), saveSettings, new PhotoEditor.OnSaveListener() {
            @Override
            public void onSuccess(@NonNull String imagePath) {
                hideLoading();
                showSnackbar("Image Saved Successfully");
                mPhotoEditorView.getSource().setImageURI(Uri.fromFile(new File(imagePath)));
                sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,Uri.fromFile(new File(filepath))));
                Intent intent = new Intent(EditImageActivity.this, StartActivity.class);
                startActivity(intent);
                finish();

            } 

            @Override
            public void onFailure(@NonNull Exception exception) {
                hideLoading();
                showSnackbar("Failed to save Image");
            }
       });
   }

有什么替代方法呢?

Answers:


108

使用getExternalFilesDir()getExternalCacheDir()getExternalMediaDirs()(方法Context)代替Environment.getExternalStorageDirectory()

或者,进行修改mPhotoEditor以能够使用Uri,然后:

  • 使用ACTION_CREATE_DOCUMENT获得Uri用户的选择的位置,或

  • 使用MediaStoreContentResolverinsert()获取Uri特定媒体类型(例如图像)的-请参见此示例应用程序该示例演示了如何从网站下载MP4视频

另外,请注意,您的Uri.fromFilewithACTION_MEDIA_SCANNER_SCAN_FILE会在Android 7.0+上使用崩溃FileUriExposedException。在Android Q上,只有MediaStore/insert()选项会MediaStore快速将您的内容编入索引。

请注意,如果您的清单未满targetSdkVersion30岁,则可以android:requestLegacyExternalStorage="true"<application>清单10的元素中选择退出Android 10和11上的“范围存储”更改。这不是一个长期的解决方案,因为targetSdkVersion如果您要通过Play商店(或其他地方)分发应用,则您需要在2021年某个时候达到30岁或更高。


12
亲爱的@CommonsWare,如果我们真的要见面,请提醒我,我欠您的博客文章啤酒。当我将targetSdk级别提高到29时,我花了整整一个下午来找出为什么某个外部库停止工作的原因。现在我知道为什么了……
Nantoka

1
使用getExternalFilesDir()和getExternalCacheDir()可以与api 29一起使用,但是当应用程序解除注册时,所有数据都将使用此方法删除...如果在应用程序标记“ android:requestLegacyExternalStorage =“ true”“中使用此属性,也可以对api起作用29。当应用程序不受限制的文件名退出但数据被删除时
Ucdemir '20

1
@miladsalimi:对不起,但我不明白你的问题。我建议您提出一个单独的堆栈溢出问题,在其中详细说明您的关注点。
CommonsWare

3
有没有getExternalMediaDirContext,看到它自己:developer.android.com/reference/android/content/ContextgetExternalMediaDirs被弃用,也期待:developer.android.com/reference/android/content/...getExternalFilesDir()getExternalCacheDir()在应用程序卸载删除(看到相同的网址)。因此,以上任何一项都不能替代Environment.getExternalStorageDirectory()
Dims

1
这与我的用例无关,但与上面的topicstarter的用例有关。
Dims

33

destPath通过新的API调用获取:

String destPath = mContext.getExternalFilesDir(null).getAbsolutePath();

3
我们可以使用这种方法:developer.android.com/training/data-storage/…?你怎么看待这件事 ?:)
aeroxr1

2
仍然可以获取环境变量getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)替换Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
Rowan Berry

已将createNewFile中已弃用的方法更新为此FIXED“权限被拒绝”错误,以外部存储为29!谢谢!
coolcool1994 '20

这不是公开目录
伊尔凡·哈克

25

对于Android Q,您可以将其添加android:requestLegacyExternalStorage="true"到清单中。这使您可以选择旧式存储模型,并且现有的外部存储代码将起作用。

<manifest ... >
<!-- This attribute is "false" by default on apps targeting
     Android 10 or higher. -->
  <application android:requestLegacyExternalStorage="true" ... >
    ...
  </application>
</manifest>

从技术上讲,仅在将其更新targetSdkVersion为29后才需要这样做。具有较低targetSdkVersion值的应用程序默认选择使用旧存储,并且需要android:requestLegacyExternalStorage="false"选择退出。


1
这篇博文为我寻找了一种解决方案,该解决方案是使用最新开发的代码在最新版本的Android Studio中将数据存储在外部存储器上的,该过程持续了8个小时。谢谢。
Sriram Nadiminti

1
它将一直持续到API29。“警告:将应用更新为目标Android 11(API级别30)后,当您的应用在Android 11设备上运行时,系统将忽略requestLegacyExternalStorage属性,因此您的应用必须准备好支持范围存储并为这些设备上的用户迁移应用数据。” developer.android.com/training/data-storage/use-cases
avisper

仍然出现相同的错误
olajide

6

请使用getExternalFilesDir()getExternalCacheDir()而不是Environment.getExternalStorageDirectory()在Android 10中创建文件时使用。

参见以下行:

val file = File(this.externalCacheDir!!.absolutePath, "/your_file_name")

1
嗨,我们如何使用getExternalFilesDir()自定义路径保存图像,我想用它来防止将图像显示到Gallery中?默认情况下,它保存为Andorid/data/packagename/...
milad salimi

您需要将文件保存到应用程序目录中,然后使用Media uri将其插入
Ajith Memana,

6
错误的答案..实际问题如何获取/ storage / emulated / 0 / MyFolder /但您的回答/data/user/0/com.example.package/files/MyFolder
Tarif Chakder

5

这是一个小示例,如果您想使用默认相机拍照并将其存储在DCIM文件夹(DCIM / app_name / filename.jpg)中,如何获取文件的URI:

打开相机(记住有关CAMERA的权限):

private var photoURI: Uri? = null

private fun openCamera() {
    Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
        photoURI = getPhotoFileUri()
        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
        takePictureIntent.resolveActivity(requireActivity().packageManager)?.also {
            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
        }
    }
}

并获取URI:

private fun getPhotoFileUri(): Uri {
    val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
    val fileName = "IMG_${timeStamp}.jpg"

    var uri: Uri? = null
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val resolver = requireContext().contentResolver
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, fileName)
            put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
            put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/app_name/")
        }

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

    return uri ?: getUriForPreQ(fileName)
}

private fun getUriForPreQ(fileName: String): Uri {
    val dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)
    val photoFile = File(dir, "/app_name/$fileName")
    if (photoFile.parentFile?.exists() == false) photoFile.parentFile?.mkdir()
    return FileProvider.getUriForFile(
        requireContext(),
        "ru.app_name.fileprovider",
        photoFile
    )
}

不要忘记对Q的WRITE_EXTERNAL_STORAGE权限,并将FileProvider添加到AndroidManifest.xml。

并得到结果:

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    super.onActivityResult(requestCode, resultCode, data)
    when (requestCode) {
        REQUEST_IMAGE_CAPTURE -> {
            photoURI?.let {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                    val thumbnail: Bitmap =
                        requireContext().contentResolver.loadThumbnail(
                            it, Size(640, 480), null
                        )
                } else {
                    // pre Q actions
                }
            }
        }
    }
}

它也已弃用
Leonardo Henao

1
请张贴Java
Attaullah

5

这对我有用

manifest文件的应用程序标签中添加此行

android:requestLegacyExternalStorage="true"

 <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:networkSecurityConfig="@xml/network_security_config"
        android:requestLegacyExternalStorage="true"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

 </application>

目标SDK是29

  defaultConfig {
        minSdkVersion 16
        targetSdkVersion 29
        multiDexEnabled true
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.