Environment.getExternalStorageDirectory () застаріло в Java рівня 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.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небудь у 2021 році вам доведеться досягти 30 років або вище, якщо ви поширюєте свій додаток через Play Store (а можливо, і деінде).


11
Шановний @CommonsWare, якщо ми коли-небудь зустрінемось у реальному режимі, будь ласка, нагадай мені, що я винен тобі пивом за твій пост у блозі. Я провів цілий полудень, з’ясовуючи, чому певна зовнішня бібліотека перестала працювати, як тільки я підняв рівень targetSdk до 29. Тепер я знаю, чому ...
Нантока,

1
використання getExternalFilesDir () та getExternalCacheDir () може працювати з api 29, але коли програма буде видалена, всі дані будуть видалені за допомогою цих методів ... Якщо ви використовуєте цей атрибут всередині тегу додатка "android: requestLegacyExternalStorage =" true "", може працювати і для api 29. Коли ім’я файлу
недістальованого

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
Справа не в моєму варіанті використання, а у варіанті використання тематичного стартера вище.
Dims

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)
Роуан Беррі

Оновлено застарілий метод до цієї ВИПРАВЛЕНОЇ помилки "Дозвіл відмовлено" від createNewFile для зовнішнього сховища, націленого на 29! Дякую!
coolcool1994

Це не загальнодоступний каталог
Irfan Ul Haq

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 / ім'я_програми / ім'я файлу.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 для попереднього 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
                }
            }
        }
    }
}

воно також застаріло
Леонардо Енао

опублікуйте його java будь ласка
Аттаулла

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
Ajith Memana

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
Джош Коррея
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.