GetExternalStorageDirectory ()在 API 级别29 java 中已弃用

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

还有什么别的选择吗?

171975 次浏览

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

或者,修改 mPhotoEditor使其能够与 Uri一起工作,然后:

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

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

此外,请注意您的 Uri.fromFileACTION_MEDIA_SCANNER_SCAN_FILE应该崩溃在 Android 7.0 + 与 FileUriExposedException。在 Android Q 上,只有 MediaStore/insert()选项可以让你的内容快速被 MediaStore索引。

注意,如果您的 targetSdkVersion低于30,那么您可以选择不使用 Android 10和11上的这些“作用域存储”更改,在清单的 <application>元素中使用 android:requestLegacyExternalStorage="true"这不是一个长期的解决方案,因为你的 targetSdkVersion将需要在2021年的某个时候达到30或者更高,如果你通过 Play Store (或者其他地方)发布你的应用程序的话。

使用新的 API 调用获取 destPath:

String destPath = mContext.getExternalFilesDir(null).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 的 WRITE _ EXTERNAL _ STORAGE 权限,并向 AndroidManifest.xml 添加一个 FileProvider。

然后得到一个结果:

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_file_name")

对于 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"选择退出。

这招对我很管用

将此行添加到 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"
}

成功了

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)
}
}

你可以利用 StorageManagerStorageVolume

StorageVolume.getPrimaryStorage淮() : 此卷与 Environment # getExternalStorageDirectory ()和 Context # getExternalFilesDir (String)返回的存储设备相同。

    public String myGetExternalStorageDir() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R)
return getPrimaryStorageVolumeForAndroid11AndAbove();
else
return getPrimaryStorageVolumeBeforeAndroid11();
}


@TargetApi(Build.VERSION_CODES.R)
private String getPrimaryStorageVolumeForAndroid11AndAbove() {
StorageManager myStorageManager = (StorageManager) ctx.getSystemService(Context.STORAGE_SERVICE);
StorageVolume mySV = myStorageManager.getPrimaryStorageVolume();
return mySV.getDirectory().getPath();
}


private String getPrimaryStorageVolumeBeforeAndroid11() {
String volumeRootPath = "";
StorageManager myStorageManager = (StorageManager) ctx.getSystemService(Context.STORAGE_SERVICE);
StorageVolume mySV = myStorageManager.getPrimaryStorageVolume();
Class<?> storageVolumeClazz = null;


try {
storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
Method getPath = storageVolumeClazz.getMethod("getPath");
volumeRootPath = (String) getPath.invoke(mySV);
} catch (Exception e) {
e.printStackTrace();
}
return volumeRootPath;
}
private fun saveImage(bitmap: Bitmap, name: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val resolver = contentResolver
val contentValues = ContentValues()
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, name)
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/" + "YOUR_FOLDER")
val imageUri =
resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
val fos = resolver.openOutputStream(imageUri!!)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
fos!!.flush()
fos.close()


toast("Saved to gallery")


} else {
if (isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
val imagesDir = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_DCIM
).toString() + File.separator + "YOUR_FOLDER"
if (!file.exists()) {
file.mkdir()
}
val image = File(imagesDir, "$name.png")


val fos = FileOutputStream(image)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
fos.flush()
fos.close()


} else {
// ask for permission
}
}
}

如果你和 XAMARIN 一起工作,并且对所有这些不同的答案感到困惑(像我一样) ,那么就跟着这个例子走:

var picture = new Java.IO.File(Environment.DirectoryPictures, "fileName");

我花了点时间才想明白。

  var tempDir: File = inContext.getExternalFilesDir("/")!!
tempDir = File(tempDir.getAbsolutePath().toString() + "/.IncidentImages/")
tempDir.mkdir()

最近我也面临类似的问题,由于我的代码很大,我不想添加新的功能在现有的代码,所以我只是改变路径。.

GetExternalStorageDirectory () 替换为

GetExternalFilesDir (null) . getAbsolutePath ()

密码

private void saveImage() {


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


final String folderPath = this.getExternalFilesDir(null).getAbsolutePath() + "/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");
}
});

}

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

权限(针对所有 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" />

Android 10的遗留权限(将此权限添加到 AndroidManifest.xml > application 标记)。

android:requestLegacyExternalStorage="true"

获取内部存储目录路径:

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();
}
}