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

Noa*_*ram 14 android deprecated android-api-levels

在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");
            }
       });
   }
Run Code Online (Sandbox Code Playgroud)

有什么替代方法呢?

小智 31

对于 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>
Run Code Online (Sandbox Code Playgroud)

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

  • 它将一直工作到 API 29。“注意:将应用更新为针对 Android 11(API 级别 30)后,当您的应用在 Android 11 设备上运行时,系统会忽略 requestLegacyExternalStorage 属性,因此您的应用必须准备好支持作用域存储并为这些设备上的用户迁移应用程序数据。” https://developer.android.com/training/data-storage/use-cases (2认同)

Com*_*are 16

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

或者,修改mPhotoEditor为可以使用Uri,然后:

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

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

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

有关Android Q如何影响外部存储的更多信息,请参见此博客文章

  • 亲爱的@CommonsWare,如果我们在现实生活中见面,请提醒我,我欠你一杯啤酒,因为你的博客文章。我花了一下午的时间找出为什么某个外部库在我将 targetSdk 级别提升到 29 后就停止工作了。现在我知道为什么了...... (22认同)
  • `Context` 中没有 `getExternalMediaDir`,请自行查看:https://developer.android.com/reference/android/content/Context 并且 `getExternalMediaDirs` 已弃用,另请参阅:https://developer.android. com/reference/android/content/Context#getExternalMediaDirs() `getExternalFilesDir()` 和 `getExternalCacheDir()` 都会在应用程序卸载时被删除(请参见相同的 URL)。因此,以上都不能替代 `Environment.getExternalStorageDirectory()`。 (8认同)
  • 使用 getExternalFilesDir() 和 getExternalCacheDir() 可以与 api 29 一起使用,但是当应用程序卸载时,所有数据都将使用此方法删除...如果您在应用程序标记“android:requestLegacyExternalStorage=”true“”中使用此属性也可以用于 api 29. 当应用程序卸载文件名退出但数据被删除时 (2认同)
  • 这不是关于我的用例,而是关于上面 topicstarter 的用例。 (2认同)

Har*_*ara 11

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

请参阅以下行:

val file = File(this.externalCacheDir!!.absolutePath, "/your_file_name")
Run Code Online (Sandbox Code Playgroud)

  • 错误的答案..实际问题如何获取/storage/emulated/0/MyFolder/,但你的答案/data/user/0/com.example.package/files/MyFolder (16认同)

Qui*_*ner 8

这对我有用

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

android:requestLegacyExternalStorage="true"
Run Code Online (Sandbox Code Playgroud)

例子

 <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>
Run Code Online (Sandbox Code Playgroud)

目标 SDK 为 29

  defaultConfig {
        minSdkVersion 16
        targetSdkVersion 29
        multiDexEnabled true
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
Run Code Online (Sandbox Code Playgroud)


小智 7

destPath使用新的API调用获取:

String destPath = mContext.getExternalFilesDir(null).getAbsolutePath();
Run Code Online (Sandbox Code Playgroud)

  • 这不是公共目录 (4认同)
  • 我们可以使用这种方法:https://developer.android.com/training/data-storage/app-specific#external-select-location?你怎么看待这件事 ?:) (3认同)
  • 仍然可以获取环境变量 getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) 替换 Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) (3认同)

bit*_*ale 7

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

打开相机(记住相机权限):

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)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

并获取 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
    )
}
Run Code Online (Sandbox Code Playgroud)

不要忘记 pre 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
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Ata*_*rim 6

要获取内部存储目录而无需硬编码,

权限(适用于所有 Android 版本)。不要忘记获得用户的权限。

请求所有文件访问权限

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
Run Code Online (Sandbox Code Playgroud)

Android 10 的旧权限(将其添加到 AndroidManifest.xml > 应用程序标记)。

android:requestLegacyExternalStorage="true"
Run Code Online (Sandbox Code Playgroud)

获取内部存储目录路径:

public static String getInternalStorageDirectoryPath(Context context) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
        return storageManager.getPrimaryStorageVolume().getDirectory().getAbsolutePath();
    } else {
        return Environment.getExternalStorageDirectory().getAbsolutePath();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • &lt;uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" /&gt; 您需要证明使用此权限的合理性以及您的应用被 Google Play 拒绝的可能性。 (2认同)