Работая над 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) вместо Environment.getExternalStorageDirectory().
Или измените mPhotoEditor, чтобы иметь возможность работать с Uri, затем:
Используйте ACTION_CREATE_DOCUMENT, чтобы получить Uri место по выбору пользователя, или
Используйте MediaStore, ContentResolver и insert(), чтобы получить Uri для определенного типа мультимедиа (например, изображения) — см. это пример приложения, в котором показано, как это делается для загрузки видео MP4 с веб-сайта.
Также обратите внимание, что ваш Uri.fromFile с ACTION_MEDIA_SCANNER_SCAN_FILE должен давать сбой на Android 7.0+ с FileUriExposedException. На Android Q только опция MediaStore/insert() быстро проиндексирует ваш контент с помощью MediaStore.
Обратите внимание, что вы можете отказаться от этих изменений «хранилища с заданной областью» на Android 10 и 11, если ваш targetSdkVersion ниже 30, используя android:requestLegacyExternalStorage = "true" в элементе <application> манифеста. Это не долгосрочное решение, так как ваш targetSdkVersion должен быть 30 или выше где-то в 2021 году, если вы распространяете свое приложение через Play Store (и, возможно, где-то еще).
Уважаемый @CommonsWare, если мы когда-нибудь встретимся вживую, пожалуйста, напомните мне, что я должен вам пиво за ваш пост в блоге. Целый день я потратил на то, чтобы выяснить, почему перестала работать некая внешняя библиотека, как только я поднял уровень targetSdk до 29. Теперь я знаю, почему...
@Commonsware, можете ли вы предоставить пример или соответствующий код с использованием вашего ответа?
использование getExternalFilesDir() и getExternalCacheDir() может работать с API 29, но когда приложение удаляется, все данные будут удалены с помощью этих методов... Если вы используете этот атрибут внутри тега приложения "android:requestLegacyExternalStorage = "true"" может работать также для API 29 . Когда имя удаленного файла приложения закрывается, но данные удаляются
Привет, как мы можем использовать getExternalFilesDir() с настраиваемым путем для сохранения изображения, я хочу использовать это для предотвращения показа изображений в галерее? По умолчанию сохраняется в Andorid/data/packagename/...
Привет, пожалуйста, проверьте мой вопрос.
@miladsalimi: Извините, но я не понимаю вашего вопроса. Я предлагаю вам задать отдельный вопрос о переполнении стека, где вы подробно объясните, что вас беспокоит.
В getExternalMediaDir нет Context, смотрите сами: developer.android.com/reference/android/content/Context И getExternalMediaDirs устарело, также смотрите: developer.android.com/reference/android/content/… Оба getExternalFilesDir() и getExternalCacheDir() удаляются при удалении приложения (см. тот же URL). Итак, ничто из вышеперечисленного не может заменить Environment.getExternalStorageDirectory().
@Dims: Мои извинения за отсутствие s в getExternalMediaDirs(). getExternalMediaDirs() устарела в Android 11. «Итак, ни одно из вышеперечисленных не может заменить Environment.getExternalStorageDirectory()» — для вашего варианта использования это может быть правдой. Ваш вариант использования не такой, как у всех остальных. И, в вашем случае, в конечном итоге вам нужно будет перейти на ACTION_CREATE_DOCUMENT или использовать MediaStore.
Речь идет не о моем случае использования, а о случае использования топикстартера выше.
Что я должен использовать для сохранения файла изображения во внешней памяти?
getExternalMediaDirs() также устарел
Что произойдет, если ContentResolver.insert() выполнен успешно, но сохранить файл не удалось, не загрязняет ли это базу данных мультимедиа?
@ssynhtn: Если вас беспокоит загрузка прямо из Интернета в указанное MediaStore место, это действительно возможный риск. Чтобы немного смягчить это, вы можете загрузить в файл (например, в getCacheDir()), а только потом insert() в MediaStore и скопировать содержимое. Это все еще не идеально, так как у вас может не хватить места на диске при создании отдельной копии, но это лучше, чем полагаться на сеть. В качестве альтернативы загрузите в указанное MediaStore место, и если вы обнаружите, что загрузка не удалась, delete() эту MediaStore запись.
@CommonsWare да, это то, о чем я говорил. Спасибо за разъяснения. но я думаю, что новый способ делать вещи не имеет большого смысла. Думаю, я буду использовать устаревший метод, пока он не сломается.
Укдемир правильно заметил, что при удалении приложения каталог также УДАЛЯЕТСЯ. Если вам нужно дать пользователю возможность выбрать сохранение пользовательских данных, добавьте эту строку: android:hasFragileUserData = "true" в файле манифеста Android в разделе "приложение", см. пример: <application android:label = "your_app_name.Android" android :hasFragileUserData = "true"></application>
я пробовал этот код, он не работает для меня в Samsung a70 android 10. каталог не создается при использовании этого кода. getExternalStorageDirectory() работает, но устарел... какое-нибудь решение?
@athul: я не знаю, к чему относится «этот код». Я рекомендую вам задать отдельный вопрос о переполнении стека, где вы предоставите минимальный воспроизводимый пример, показывающий, что вы пробовали, и подробно объясняющий, где возникла ваша проблема.
Представьте, что вы используете DownloadManager и FileProvider для отображения загруженного файла. DownloadManager не будет сохранять в приватный каталог и FileProvider не позволит отображать файлы в общедоступном каталоге с действием ACTION_VIEW. Публичные API Android испорчены ради security, о чем они должны были подумать 10 лет назад. Интересно, заботятся ли они вообще о других общедоступных API, которые их новые меры делают недействительными?
getExternalMediaDirs() устарел в Context.class, но не в public class ContextWrapper extends Context, так как сохранить файл внутри Android/media/com.yourpackage, как это сделал WhatsApp? Они переместили папку WhatsApp туда.
Получите destPath с новым вызовом API:
String destPath = mContext.getExternalFilesDir(null).getAbsolutePath();
Мы можем использовать этот метод: developer.android.com/training/data-storage/… ? Что вы думаете об этом ? :)
По-прежнему можно получить переменную среды getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) вместо Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
Обновлен устаревший метод для этой ИСПРАВЛЕННОЙ ошибки «Отказано в доступе» из createNewFile для внешнего хранилища, нацеленного на 29! Спасибо!
Это не публичный каталог
я пробовал этот код, он не работает для меня в Samsung a70 android 10. каталог не создается при использовании этого кода. getExternalStorageDirectory() работает, но устарел... какое-нибудь решение?
Это небольшой пример того, как получить URI для файла, если вы хотите сделать снимок с помощью камеры по умолчанию и сохранить его в папке DCIM (DCIM/имя_приложения/имя_файла.jpg):
Открыть камеру (помните о разрешении 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
)
}
Не забудьте о разрешении WRITE_EXTERNAL_STORAGE для pre Q и добавьте 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
}
}
}
}
}
это также устарело
выложи джаву пожалуйста
Что должно быть эквивалентно для открытия файла PDF вместо изображения?
Пожалуйста, используйте getExternalFilesDir(), getExternalCacheDir() вместо Environment.getExternalStorageDirectory() при создании файла в Android 10.
См. строку ниже:
val file = File(this.externalCacheDir!!.absolutePath, "/your_file_name")
Привет, как мы можем использовать getExternalFilesDir() с настраиваемым путем для сохранения изображения, я хочу использовать это для предотвращения показа изображений в галерее? По умолчанию сохраняется в Andorid/data/packagename/...
Вам нужно будет сохранить файл в каталоге приложения, а затем вставить его с помощью Media uri.
неправильный ответ .. актуальный вопрос, как получить /storage/emulated/0/MyFolder/, но ваш ответ /data/user/0/com.example.package/files/MyFolder
я пробовал этот код, он не работает для меня в Samsung a70 android 10. каталог не создается при использовании этого кода. getExternalStorageDirectory() работает, но устарел... какое-нибудь решение?
Для 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".
Этот пост спас мои поиски решения хранения данных на внешнем хранилище в последней версии Android Studio с ранее разработанным кодом — которые длились 8 часов. Спасибо.
Он будет работать до API 29. «Внимание: после того, как вы обновите свое приложение до целевой версии Android 11 (уровень API 30), система игнорирует атрибут requestLegacyExternalStorage, когда ваше приложение работает на устройствах Android 11, поэтому ваше приложение должно быть готово к поддержке области действия. хранилища и для переноса данных приложения для пользователей на этих устройствах». developer.android.com/training/data-storage/use-cases
Не рекомендуется
Это сработало для меня
Добавьте эту строку в тег приложения файла 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>
Target SDK is 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)
}
}
Вы можете использовать классы StorageManager и StorageVolume
StorageVolume.getPrimaryStorageVolume(): этот том является тем же устройством хранения, которое возвращает 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;
}
Привет @usilo, сначала спасибо за ваш ответ, с этим кодом что-то не так, когда я использую код метода getPrimaryStorageVolumeBeforeAndroid11, он не будет поддерживать все более низкие версии, потому что доступ к myStorageManager.getPrimaryStorageVolume() можно получить только с Android N. Еще ниже, чем более низкая версия N, находится под знаком вопроса с вашим ответом. Спасибо
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() Заменить на
context.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 и ниже мы можем использовать наши старые разрешения, но в Android 10 мы должны использовать requestLegacyExternalStorage в нашем теге приложения (AndroidManifest.xml > тег приложения).
android:requestLegacyExternalStorage = "true"
Получить путь к каталогу внутреннего хранилища:
@Nullable
public static String getInternalStorageDirectoryPath(Context context) {
String storageDirectoryPath;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
StorageManager storageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
if (storageManager == null) {
storageDirectoryPath = null; //you can replace it with the Environment.getExternalStorageDirectory().getAbsolutePath()
} else {
storageDirectoryPath = storageManager.getPrimaryStorageVolume().getDirectory().getAbsolutePath();
}
} else {
storageDirectoryPath = Environment.getExternalStorageDirectory().getAbsolutePath();
}
return storageDirectoryPath;
}
проверьте мой ответ здесь stackoverflow.com/a/68110559/6039240