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

102

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

Что будет для этого альтернатива?

Ноаман Акрам
источник

Ответы:

94

Использование getExternalFilesDir(), getExternalCacheDir()или getExternalMediaDirs()(методы на Context) вместо Environment.getExternalStorageDirectory().

Или измените, mPhotoEditorчтобы иметь возможность работать с a Uri, а затем:

  • Используйте, ACTION_CREATE_DOCUMENTчтобы добраться Uriдо места по выбору пользователя, или

  • Используйте MediaStore, ContentResolverи insert()для получения Uriопределенного типа мультимедиа (например, изображения) - см. Этот пример приложения, в котором демонстрируется выполнение этого для загрузки видео MP4 с веб-сайта.

Также обратите внимание, что ваш Uri.fromFilewith ACTION_MEDIA_SCANNER_SCAN_FILEдолжен давать сбой на Android 7.0+ с расширением FileUriExposedException. В Android Q только параметр MediaStore/ insert()позволит быстро проиндексировать ваш контент MediaStore.

Обратите внимание, что вы можете отказаться от этих изменений «ограниченного хранилища» на Android 10 и 11, если у вас targetSdkVersionменьше 30, используя android:requestLegacyExternalStorage="true"в <application>элементе манифеста. Это не долгосрочное решение , так как targetSdkVersionв 2021 году вам понадобится 30 или больше, если вы распространяете свое приложение через Play Store (и, возможно, где-то еще).

CommonsWare
источник
10
Дорогой @CommonsWare, если мы когда-нибудь встретимся вживую, пожалуйста, напомните мне, что я должен вам пива за ваш пост в блоге. Я потратил целый день, выясняя, почему перестала работать некоторая внешняя библиотека, как только я поднял уровень targetSdk до 29. Теперь я знаю, почему ...
Нантока
1
использование getExternalFilesDir () и getExternalCacheDir () может работать с api 29, но когда приложение не установлено, все данные будут удалены с помощью этих методов ... Если вы используете этот атрибут внутри тега приложения, "android: requestLegacyExternalStorage =" true "" может работать также для api 29. Когда приложение неустановленное имя файла закрывается, но данные удаляются
Ucdemir
1
@miladsalimi: Извините, но я не понимаю вашего вопроса. Я предлагаю вам задать отдельный вопрос о переполнении стека, где вы подробно объясните, что вас беспокоит.
CommonsWare
3
Там нет getExternalMediaDirв Context, убедитесь в этом сами: developer.android.com/reference/android/content/Context И getExternalMediaDirsосуждается, также смотрят: developer.android.com/reference/android/content/... Оба getExternalFilesDir()и getExternalCacheDir()удаляются после приложения удаления ( см. тот же URL). Итак, ничто из вышеперечисленного не может заменить Environment.getExternalStorageDirectory().
Затухает
1
Речь идет не о моем варианте использования, а о том, что описано выше.
Затухает
29

Получите destPathс помощью нового вызова API:

String destPath = mContext.getExternalFilesDir(null).getAbsolutePath();
Рассел А
источник
3
Мы можем использовать этот метод: developer.android.com/training/data-storage/… ? Что вы думаете об этом ? :)
aeroxr1
2
По-прежнему можно получить переменную среды getExternalFilesDir (Environment.DIRECTORY_DOWNLOADS) заменяет Environment.getExternalStoragePublicDirectory (Environment.DIRECTORY_DOWNLOADS)
Роуэн Берри,
Обновлен устаревший метод до этой ИСПРАВЛЕННОЙ ошибки «Permission denied» из createNewFile для внешнего хранилища, предназначенного для 29! Спасибо!
coolcool1994
Это не общедоступный каталог
Ирфан Уль Хак,
22

Для 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"будет отказаться.

Махмуд Мансур
источник
1
Этот пост сохранил мой поиск решения для хранения данных на внешнем хранилище в последней версии Android Studio с ранее разработанным кодом - который длился 8 часов. Спасибо.
Шрирам Надиминти,
1
Он будет работать до API 29 ». Внимание! После обновления приложения до Android 11 (уровень API 30) система игнорирует атрибут requestLegacyExternalStorage, когда ваше приложение работает на устройствах Android 11, поэтому ваше приложение должно быть готово к поддержке ограниченной области действия. хранилище и перенести данные приложений для пользователей на этих устройствах ". developer.android.com/training/data-storage/use-cases
avisper
5

Это небольшой пример того, как получить URI для файла, если вы хотите сделать снимок с помощью камеры по умолчанию и сохранить его в папке DCIM (DCIM / app_name / filename.jpg):

Открыть камеру (помните про разрешение КАМЕРЫ):

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
                }
            }
        }
    }
}
Bitvale
источник
он также устарел
Леонардо Энао
опубликуйте
5

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

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

val file = File(this.externalCacheDir!!.absolutePath, "/your_file_name")
Хардик Хирпара
источник
1
Привет, как мы можем использовать getExternalFilesDir()настраиваемый путь для сохранения изображения, я хочу использовать его для предотвращения отображения изображений в галерее? По умолчанию он сохраняется вAndorid/data/packagename/...
milad salimi
Вам нужно будет сохранить файл в каталоге приложения, а затем вставить его с помощью Media uri
Аджит Мемана
3
неправильный ответ .. актуальный вопрос как получить / storage / emulated / 0 / MyFolder / но ваш anser /data/user/0/com.example.package/files/MyFolder
Tarif Chakder
3

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

Добавьте эту строку в тег приложения 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"
    }
Способный ученик
источник
1
Дубликат этого комментария: stackoverflow.com/a/61774820/7487335
Джош Коррейя