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

Android API级别29 java中不推荐使用Environment.getExternalStorageDirectory(),android,deprecated,android-api-levels,Android,Deprecated,Android Api Levels,在android Java上工作,最近将SDK更新为API级别29,现在显示了一条警告,指出 API级别29中不推荐使用Environment.getExternalStorageDirectory 我的代码是 private void saveImage() { if (requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { final String folderPath = Environment.getE

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

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

我的代码是

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");
            }
       });
   }
有什么替代方法?

在上下文上使用getExternalFilesDir、getExternalCacheDir或getExternalMediaDirs方法,而不是Environment.getExternalStorageDirectory

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

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

使用MediaStore、ContentResolver和insert获取特定类型媒体(例如图像)的Uri-请参阅演示如何从网站下载MP4视频

另外,请注意,您的Uri.fromFile和ACTION_MEDIA_SCANNER_SCAN_文件应该在Android 7.0+上崩溃,并且出现FileUriExposedException。在Android Q上,只有MediaStore/insert选项才能让MediaStore快速索引您的内容

请注意,如果targetSdkVersion低于30,则可以在清单的元素中使用Android:requestLegacyExternalStorage=true,选择退出Android 10和11上的这些范围存储更改。这不是一个长期的解决方案,因为如果您通过Play Store或其他地方分发应用程序,您的targetSdkVersion在2021年的某个时候将需要达到30或更高版本。

使用新的API调用获取destPath:

字符串destPath=mContext.getExternalFilesDirnull.getAbsolutePath;
这是一个小示例,如果您想使用默认相机拍照并将其存储在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)
        }
    }
}
并获取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
    )
}
不要忘记为pre Q写入外部存储权限,并向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
                }
            }
        }
    }
}
在Android 10中创建文件时,请使用getExternalFilesDir、getExternalCacheDir而不是Environment.getExternalStorageDirectory

见下一行:

val file=Filethis.externalCacheDir!!。绝对路径,/您的文件名
对于Android Q,您可以将Android:requestLegacyExternalStorage=true添加到清单中的元素中。这将把您引入到遗留存储模型中,并且您现有的外部存储代码将正常工作

... 从技术上讲,您只需要在将targetSdkVersion更新到29之后才需要它。targetSdkVersion值较低的应用程序默认选择传统存储,并且需要android:requestLegacyExternalStorage=false才能选择退出。

这对我很有效

在清单文件的应用程序标记中添加此行

范例

这起作用了

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    contentResolver?.also { resolver ->
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, "Image_"+".jpg")
            put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
            put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator+ "TestFolder")
        }
        val imageUri: Uri? = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
            fos = imageUri?.let { resolver.openOutputStream(it) }
            bitmap.compress(Bitmap.CompressFormat.JPEG,100,fos)
            Objects.requireNonNull(fos)
    }
}
您可以使用StorageManager和StorageVolume类

:此卷是EnvironmentgetExternalStorageDirectory和ContextgetExternalFilesDirString返回的同一存储设备

公共字符串myGetExternalStorageDir{ 如果Build.VERSION.SDK\u INT>=Build.VERSION\u code.R 返回GetPrimaryStorageVolumeForandRoida11及以上; 其他的 返回getPrimaryStorageVolumeBeforeAndroid11; } @TargetApiBuild.VERSION_code.R 私有字符串GetPrimaryStorageVolumeForandRoida11及以上{ StorageManager myStorageManager=StorageManager ctx.getSystemServiceContext.STORAGE\u服务; StorageVolume mySV=myStorageManager.getPrimaryStorageVolume; 返回mySV.getDirectory.getPath; } 私有字符串getPrimaryStorageVolumeBeforeAndroid11{ 字符串volumeRootPath=; StorageManager myStorageManager=StorageManager ctx.getSystemServiceContext.STORAGE\u服务; StorageVolume mySV=myStorageManager.getPrimaryStorageVolume; 类storageVolumeClazz=null; 试一试{ storageVolumeClazz=Class.forNameandroid.os.storage.StorageVolume; 方法getPath=storageVolumeClazz.getMethodgetPath; volumeRootPath=字符串getPath.invokemySV; }捕获异常e{ e、 打印跟踪; } 返回卷路径; }
亲爱的@commonware,如果我们在真实生活中相遇,请提醒我,我欠你一杯啤酒,感谢你的博客帖子。当我将targetSdk级别提升到29级时,我花了整整一个下午的时间找出某个外部库停止工作的原因。现在我知道了为什么…使用getExternalFilesDir和getExternalCacheDir可以与api 29一起工作,但是当应用程序未安装时,所有数据都将使用此方法删除。。。如果在应用程序标记android:requestLegacyExternalStorage=true中使用此属性,则也可以用于api 29。当应用程序未安装的文件名退出但数据removed@miladsalimi:对不起,我不明白你的问题。我建议您提出一个单独的堆栈溢出问题,详细解释您关心的是什么。上下文中没有getExternalMediaDir,请自己查看:getExternalMediaDirs是d
eprecated,也可以查看:getExternalFilesDir和getExternalCacheDir在应用程序卸载时都会被删除。请查看相同的URL。所以,上面的任何一个都不能替代Environment.getExternalStorageDirectory。这不是我的用例,而是上面topicstarter的用例。我们可以使用以下方法:?你觉得怎么样仍然可以获取环境变量getExternalFilesDirEnvironment.DIRECTORY\u DOWNLOADS替换environment.getExternalStoragePublicDirectoryEnvironment.DIRECTORY\u DOWNLOADS将不推荐的方法更新为此修复了createNewFile针对外部存储的权限拒绝错误29!非常感谢。这不是公共目录我试过这段代码,它在三星a70安卓10中对我不起作用。使用此代码时未创建目录。getExternalStorageDirectory正在工作,但已弃用..有解决方案吗?它也是弃用的Post-it java pleaseHi,我们如何使用getExternalFilesDir和自定义路径来保存图像,我想使用它来防止将图像显示到库中?默认情况下,它保存到android/data/packagename/…您需要将文件保存到app目录,然后使用媒体插入此文件。回答错误..实际问题如何获取/storage/emulated/0/MyFolder/但您的anser/data/user/0/com.example.package/files/MyFolderi尝试了此代码,三星a70安卓10对我不起作用。使用此代码时未创建目录。getExternalStorageDirectory正在工作,但已被弃用..有解决方案吗?这篇文章保存了我对最新版本的Android Studio中存储外部存储数据的解决方案的搜索,其中包含一个早期开发的代码,持续了8个小时。谢谢。它将一直工作到API 29。警告:将应用程序更新为目标Android 11 API级别30后,当应用程序在Android 11设备上运行时,系统将忽略requestLegacyExternalStorage属性,因此应用程序必须准备好支持作用域存储,并为这些设备上的用户迁移应用程序数据。仍然得到相同的错误不推荐
 <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>
  defaultConfig {
        minSdkVersion 16
        targetSdkVersion 29
        multiDexEnabled true
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
    contentResolver?.also { resolver ->
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, "Image_"+".jpg")
            put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
            put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator+ "TestFolder")
        }
        val imageUri: Uri? = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
            fos = imageUri?.let { resolver.openOutputStream(it) }
            bitmap.compress(Bitmap.CompressFormat.JPEG,100,fos)
            Objects.requireNonNull(fos)
    }
}