Environment.getExternalStorageDirectory() устарел в API уровня 29 java

Работая над 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");
            }
       });
   }

Какая будет альтернатива этому?

проверьте мой ответ здесь stackoverflow.com/a/68110559/6039240

Amr 24.06.2021 08:25
175
1
150 857
13
Перейти к ответу Данный вопрос помечен как решенный

Ответы 13

Ответ принят как подходящий

Используйте 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. Теперь я знаю, почему...

Nantoka 23.09.2019 23:04

@Commonsware, можете ли вы предоставить пример или соответствующий код с использованием вашего ответа?

Quick learner 03.12.2019 11:53

использование getExternalFilesDir() и getExternalCacheDir() может работать с API 29, но когда приложение удаляется, все данные будут удалены с помощью этих методов... Если вы используете этот атрибут внутри тега приложения "android:requestLegacyExternalStorage = "true"" может работать также для API 29 . Когда имя удаленного файла приложения закрывается, но данные удаляются

Ucdemir 21.04.2020 10:55

Привет, как мы можем использовать getExternalFilesDir() с настраиваемым путем для сохранения изображения, я хочу использовать это для предотвращения показа изображений в галерее? По умолчанию сохраняется в Andorid/data/packagename/...

milad salimi 22.04.2020 12:55

Привет, пожалуйста, проверьте мой вопрос.

milad salimi 22.04.2020 20:38

@miladsalimi: Извините, но я не понимаю вашего вопроса. Я предлагаю вам задать отдельный вопрос о переполнении стека, где вы подробно объясните, что вас беспокоит.

CommonsWare 22.04.2020 20:48

В getExternalMediaDir нет Context, смотрите сами: developer.android.com/reference/android/content/Context И getExternalMediaDirs устарело, также смотрите: developer.android.com/reference/android/content/… Оба getExternalFilesDir() и getExternalCacheDir() удаляются при удалении приложения (см. тот же URL). Итак, ничто из вышеперечисленного не может заменить Environment.getExternalStorageDirectory().

Dims 08.07.2020 15:18

@Dims: Мои извинения за отсутствие s в getExternalMediaDirs(). getExternalMediaDirs() устарела в Android 11. «Итак, ни одно из вышеперечисленных не может заменить Environment.getExternalStorageDirectory()» — для вашего варианта использования это может быть правдой. Ваш вариант использования не такой, как у всех остальных. И, в вашем случае, в конечном итоге вам нужно будет перейти на ACTION_CREATE_DOCUMENT или использовать MediaStore.

CommonsWare 08.07.2020 15:46

Речь идет не о моем случае использования, а о случае использования топикстартера выше.

Dims 09.07.2020 16:13

Что я должен использовать для сохранения файла изображения во внешней памяти?

luke cross 27.07.2020 21:46

getExternalMediaDirs() также устарел

Ali Azaz Alam 06.10.2020 08:24

Что произойдет, если ContentResolver.insert() выполнен успешно, но сохранить файл не удалось, не загрязняет ли это базу данных мультимедиа?

ssynhtn 12.11.2020 07:57

@ssynhtn: Если вас беспокоит загрузка прямо из Интернета в указанное MediaStore место, это действительно возможный риск. Чтобы немного смягчить это, вы можете загрузить в файл (например, в getCacheDir()), а только потом insert() в MediaStore и скопировать содержимое. Это все еще не идеально, так как у вас может не хватить места на диске при создании отдельной копии, но это лучше, чем полагаться на сеть. В качестве альтернативы загрузите в указанное MediaStore место, и если вы обнаружите, что загрузка не удалась, delete() эту MediaStore запись.

CommonsWare 12.11.2020 13:15

@CommonsWare да, это то, о чем я говорил. Спасибо за разъяснения. но я думаю, что новый способ делать вещи не имеет большого смысла. Думаю, я буду использовать устаревший метод, пока он не сломается.

ssynhtn 13.11.2020 14:08

Укдемир правильно заметил, что при удалении приложения каталог также УДАЛЯЕТСЯ. Если вам нужно дать пользователю возможность выбрать сохранение пользовательских данных, добавьте эту строку: android:hasFragileUserData = "true" в файле манифеста Android в разделе "приложение", см. пример: <application android:label = "your_app_name.Android" android :hasFragileUserData = "true"></application>

Marius Rusu 29.01.2021 22:12

я пробовал этот код, он не работает для меня в Samsung a70 android 10. каталог не создается при использовании этого кода. getExternalStorageDirectory() работает, но устарел... какое-нибудь решение?

athul 23.02.2021 13:39

@athul: я не знаю, к чему относится «этот код». Я рекомендую вам задать отдельный вопрос о переполнении стека, где вы предоставите минимальный воспроизводимый пример, показывающий, что вы пробовали, и подробно объясняющий, где возникла ваша проблема.

CommonsWare 23.02.2021 13:46

Представьте, что вы используете DownloadManager и FileProvider для отображения загруженного файла. DownloadManager не будет сохранять в приватный каталог и FileProvider не позволит отображать файлы в общедоступном каталоге с действием ACTION_VIEW. Публичные API Android испорчены ради security, о чем они должны были подумать 10 лет назад. Интересно, заботятся ли они вообще о других общедоступных API, которые их новые меры делают недействительными?

Farid 26.02.2021 18:37
getExternalMediaDirs() устарел в Context.class, но не в public class ContextWrapper extends Context, так как сохранить файл внутри Android/media/com.yourpackage, как это сделал WhatsApp? Они переместили папку WhatsApp туда.
Bhavin Patel 06.07.2021 13:01

Получите destPath с новым вызовом API:

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

Мы можем использовать этот метод: developer.android.com/training/data-storage/… ? Что вы думаете об этом ? :)

aeroxr1 07.01.2020 09:54

По-прежнему можно получить переменную среды getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) вместо Environment.getExternalStoragePublicDirectory(Environment.DI‌​RECTORY_DOWNLOADS)

Rowan Berry 06.03.2020 04:04

Обновлен устаревший метод для этой ИСПРАВЛЕННОЙ ошибки «Отказано в доступе» из createNewFile для внешнего хранилища, нацеленного на 29! Спасибо!

coolcool1994 22.08.2020 21:13

Это не публичный каталог

Irfan Ul Haq 21.10.2020 10:45

я пробовал этот код, он не работает для меня в Samsung a70 android 10. каталог не создается при использовании этого кода. getExternalStorageDirectory() работает, но устарел... какое-нибудь решение?

athul 23.02.2021 13:39

Это небольшой пример того, как получить 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
                }
            }
        }
    }
}

это также устарело

Leonardo Henao 06.04.2020 06:38

выложи джаву пожалуйста

Attaullah 01.10.2020 14:45

Что должно быть эквивалентно для открытия файла PDF вместо изображения?

Deepak Gahlot 10.08.2021 08:51

Пожалуйста, используйте getExternalFilesDir(), getExternalCacheDir() вместо Environment.getExternalStorageDirectory() при создании файла в Android 10.

См. строку ниже:

val file = File(this.externalCacheDir!!.absolutePath, "/your_file_name")

Привет, как мы можем использовать getExternalFilesDir() с настраиваемым путем для сохранения изображения, я хочу использовать это для предотвращения показа изображений в галерее? По умолчанию сохраняется в Andorid/data/packagename/...

milad salimi 22.04.2020 12:58

Вам нужно будет сохранить файл в каталоге приложения, а затем вставить его с помощью Media uri.

Ajith Memana 07.05.2020 20:48

неправильный ответ .. актуальный вопрос, как получить /storage/emulated/0/MyFolder/, но ваш ответ /data/user/0/com.example.package/files/MyFolder

Tarif Chakder 27.08.2020 11:58

я пробовал этот код, он не работает для меня в Samsung a70 android 10. каталог не создается при использовании этого кода. getExternalStorageDirectory() работает, но устарел... какое-нибудь решение?

athul 23.02.2021 13:38

Для 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 часов. Спасибо.

Sriram Nadiminti 12.07.2020 19:09

Он будет работать до API 29. «Внимание: после того, как вы обновите свое приложение до целевой версии Android 11 (уровень API 30), система игнорирует атрибут requestLegacyExternalStorage, когда ваше приложение работает на устройствах Android 11, поэтому ваше приложение должно быть готово к поддержке области действия. хранилища и для переноса данных приложения для пользователей на этих устройствах». developer.android.com/training/data-storage/use-cases

avisper 14.09.2020 15:32

Не рекомендуется

Cícero Moura 21.04.2021 02:36

Это сработало для меня

Добавьте эту строку в тег приложения файла 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, находится под знаком вопроса с вашим ответом. Спасибо

SaravanaRaja 23.09.2021 19:59
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;
}

Другие вопросы по теме