Environment.getExternalStorageDirectory () obsoleto en API nivel 29 java

102

Trabajando en Android Java, SDK actualizado recientemente al nivel de API 29 ahora se muestra una advertencia que indica que

Environment.getExternalStorageDirectory() está obsoleto en el nivel de API 29

Mi codigo es

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

¿Cuál será la alternativa para esto?

Noaman Akram
fuente

Respuestas:

94

Utilice getExternalFilesDir(), getExternalCacheDir()o getExternalMediaDirs()(métodos activados Context) en lugar de Environment.getExternalStorageDirectory().

O modifique mPhotoEditorpara poder trabajar con a Uri, luego:

  • Úselo ACTION_CREATE_DOCUMENTpara llegar Uria una ubicación que elija el usuario, o

  • Use MediaStore, ContentResolvery insert()para obtener un Uritipo de medio en particular (por ejemplo, una imagen): vea esta aplicación de muestra que demuestra cómo hacer esto para descargar videos MP4 desde un sitio web

Además, tenga en cuenta que su Uri.fromFilecon ACTION_MEDIA_SCANNER_SCAN_FILEdebería fallar en Android 7.0+ con un FileUriExposedException. En Android Q, solo la opción MediaStore/ insert()hará que su contenido indexe MediaStorerápidamente.

Tenga en cuenta que puede optar por no participar en estos cambios de "almacenamiento de alcance" en Android 10 y 11, si targetSdkVersiones inferior a 30, mediante android:requestLegacyExternalStorage="true"el <application>elemento del manifiesto. Esta no es una solución a largo plazo , ya que targetSdkVersiondeberá tener 30 años o más en algún momento de 2021 si distribuye su aplicación a través de Play Store (y quizás en otros lugares).

CommonsWare
fuente
10
Estimado @CommonsWare, si alguna vez nos encontramos en vivo, recuérdeme que le debo una cerveza por la publicación de su blog. Pasé una tarde entera averiguando por qué cierta biblioteca externa dejó de funcionar, tan pronto como elevé el nivel targetSdk a 29. Ahora sé por qué ...
Nantoka
1
el uso de getExternalFilesDir () y getExternalCacheDir () puede funcionar con api 29, pero cuando la aplicación se desinstala, todos los datos se eliminarán usando estos métodos ... Si usa este atributo dentro de la etiqueta de la aplicación "android: requestLegacyExternalStorage =" true "" puede funcionar también para api 29. Cuando el nombre de archivo no instalado de la aplicación sale pero se eliminan los datos
Ucdemir
1
@miladsalimi: Lo siento, pero no entiendo tu pregunta. Le sugiero que haga una pregunta separada de Stack Overflow en la que explique en detalle cuál es su preocupación.
CommonsWare
3
No hay getExternalMediaDiren el Context, ver usted mismo: developer.android.com/reference/android/content/Context Y getExternalMediaDirsestá en desuso, también miran: developer.android.com/reference/android/content/... Tanto getExternalFilesDir()y getExternalCacheDir()se eliminan al desinstalar la aplicación ( ver la misma URL). Entonces, ninguno de los anteriores puede sustituir Environment.getExternalStorageDirectory().
Atenúa el
1
No se trata del caso de uso mío, sino del caso de uso de topicstarter anterior.
atenúa el
29

Obtenga el destPathcon la nueva llamada API:

String destPath = mContext.getExternalFilesDir(null).getAbsolutePath();
Russell A
fuente
3
Podemos usar este método: developer.android.com/training/data-storage/… ? Qué piensa usted al respecto ? :)
aeroxr1
2
Todavía se puede obtener la variable de entorno getExternalFilesDir (Environment.DIRECTORY_DOWNLOADS) reemplaza Environment.getExternalStoragePublicDirectory (Environment.DIRECTORY_DOWNLOADS)
Rowan Berry
¡Se actualizó el método obsoleto a este error CORREGIDO "Permiso denegado" de createNewFile para almacenamiento externo dirigido a 29! ¡Gracias!
coolcool1994
Este no es el directorio público
Irfan Ul Haq
22

Para Android Q, puede agregar android:requestLegacyExternalStorage="true"a su elemento en el manifiesto. Esto lo opta por el modelo de almacenamiento heredado y su código de almacenamiento externo existente funcionará.

<manifest ... >
<!-- This attribute is "false" by default on apps targeting
     Android 10 or higher. -->
  <application android:requestLegacyExternalStorage="true" ... >
    ...
  </application>
</manifest>

Técnicamente, solo necesita esto una vez que actualice su targetSdkVersiona 29. Las aplicaciones con targetSdkVersionvalores más bajos por defecto optan por el almacenamiento heredado y deberían android:requestLegacyExternalStorage="false"optar por no participar.

Mahmoud Mansour
fuente
1
Esta publicación ha guardado mi búsqueda de una solución para almacenar datos en almacenamiento externo en la última versión de Android Studio con un código desarrollado anteriormente, que duró 8 horas. Gracias.
Sriram Nadiminti
1
Funcionará hasta la API 29 ". Precaución: después de actualizar su aplicación para que apunte a Android 11 (nivel de API 30), el sistema ignora el atributo requestLegacyExternalStorage cuando su aplicación se ejecuta en dispositivos con Android 11, por lo que su aplicación debe estar lista para admitir el alcance. almacenamiento y migrar los datos de la aplicación para los usuarios de esos dispositivos ". developer.android.com/training/data-storage/use-cases
avisper
5

Este es un pequeño ejemplo de cómo obtener un URI para un archivo si desea tomar una foto usando la cámara predeterminada y almacenarla en una carpeta DCIM (DCIM / app_name / filename.jpg):

Cámara abierta (recuerde el permiso 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)
        }
    }
}

Y obtén 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
    )
}

No se olvide del permiso WRITE_EXTERNAL_STORAGE para pre Q y agregue un FileProvider a AndroidManifest.xml.

Y obtén un resultado:

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
fuente
también está en desuso
Leonardo Henao
publicarlo java por favor
Attaullah
5

Úselo getExternalFilesDir(), en getExternalCacheDir()lugar de Environment.getExternalStorageDirectory()cuando cree un archivo en Android 10.

Vea la siguiente línea:

val file = File(this.externalCacheDir!!.absolutePath, "/your_file_name")
Hardik Hirpara
fuente
1
Hola, ¿cómo podemos usar getExternalFilesDir()una ruta personalizada para guardar la imagen? Quiero usar esto para evitar mostrar imágenes en la galería. De forma predeterminada, se guarda enAndorid/data/packagename/...
milad salimi
Deberá guardar el archivo en el directorio de la aplicación y luego insertarlo usando Media uri
Ajith Memana
3
respuesta incorrecta ... pregunta real cómo obtener / storage / emulated / 0 / MyFolder / pero su anser /data/user/0/com.example.package/files/MyFolder
Tarif Chakder
3

Esto funcionó para mi

Agregue esta línea en la etiqueta de la aplicación del manifestarchivo

android:requestLegacyExternalStorage="true"

Ejemplo

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

El SDK de destino es 29

  defaultConfig {
        minSdkVersion 16
        targetSdkVersion 29
        multiDexEnabled true
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
Aprende rápido
fuente
1
Duplicado de este comentario: stackoverflow.com/a/61774820/7487335
Josh Correia