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,现在显示了一条警告,指出 Environment.getExternalStorageDirectory()在API级别29中不推荐使用 我的代码是 private void saveImage() { if (requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { final String folderPath = Environment.g

在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");
            }
       });
   }

有什么替代方法?

使用
getExternalFilesDir()
getExternalCacheDir()
,或
getExternalMediaDirs()
Context
上的方法)而不是
环境。getExternalStorageDirectory()

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

  • 使用
    ACTION\u CREATE\u DOCUMENT
    Uri
    获取到用户选择的位置,或

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

另外,请注意,您的
Uri.fromFile
ACTION\u MEDIA\u SCANNER\u SCAN\u FILE
应该会在Android 7.0+上崩溃,出现
FileUriExposedException
。在安卓Q上,只有
MediaStore
/
insert()
选项才能通过
MediaStore
快速索引您的内容


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

使用新的API调用获取
destPath

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

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

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

并获取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=file(this.externalCacheDir!!.absolutePath,“/your\u file\u name”)

对于Android Q,您可以将
Android:requestLegacyExternalStorage=“true”
添加到清单中的元素中。这将把您引入到遗留存储模型中,并且您现有的外部存储代码将正常工作


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

这对我很有效

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

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>
这起作用了

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

:此卷是由环境#getExternalStorageDirectory()和上下文#getExternalFilesDir(字符串)返回的同一存储设备

公共字符串myGetExternalStorageDir(){ if(Build.VERSION.SDK\u INT>=Build.VERSION\u code.R) 返回GetPrimaryStorageVolumeForandRoid11及以上(); 其他的 返回getPrimaryStorageVolumeBeforeAndroid11(); } @TargetApi(Build.VERSION\u code.R) 私有字符串GetPrimaryStorageVolumeForandRoid11及以上(){ StorageManager myStorageManager=(StorageManager)ctx.getSystemService(Context.STORAGE\u服务); StorageVolume mySV=myStorageManager.getPrimaryStorageVolume(); 返回mySV.getDirectory().getPath(); } 私有字符串getPrimaryStorageVolumeBeforeAndroid11(){ 字符串volumeRootPath=“”; StorageManager myStorageManager=(StorageManager)ctx.getSystemService(Context.STORAGE\u服务); StorageVolume mySV=myStorageManager.getPrimaryStorageVolume(); 类storageVolumeClazz=null; 试一试{ storageVolumeClazz=Class.forName(“android.os.storage.StorageVolume”); 方法getPath=storageVolumeClazz.getMethod(“getPath”); volumeRootPath=(字符串)getPath.invoke(mySV); }捕获(例外e){ e、 printStackTrace(); } 返回卷路径; }
亲爱的@commonware,如果我们真的在现场见面,请提醒我,我欠你一杯啤酒,谢谢你的博客帖子。当我将targetSdk级别提升到29级时,我花了整整一个下午的时间找出某个外部库停止工作的原因。现在我知道了为什么…使用getExternalFilesDir()和getExternalCacheDir()可以与api 29一起工作,但是当应用程序未安装时,所有数据都将使用此方法删除。。。如果在应用程序标记“android:requestLegacyExternalStorage=“true”中使用此属性,则api 29也可以使用此属性。当应用程序未安装的文件名退出但数据removed@miladsalimi:对不起,我不明白你的问题。我建议你问一个单独的堆栈溢出问题,详细解释你所关心的是什么。在
上下文中没有
getExternalMediaDir
,你自己看看吧:
getExternalMediaDir
被弃用了,再看看:
getExternalFilesDir()
getExternalCacheDir()
在卸载应用程序时被删除(请参见相同的URL)。因此,上面的任何一个都不能替代
环境。getExternalStorageDirectory()
。这不是我的用例,而是topicstarter
  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)
    }
}