Environment.getExternalStorageDirectory () ist in Java der API-Ebene 29 veraltet


102

Bei der Arbeit mit Android Java, kürzlich aktualisiertes SDK auf API-Level 29, wird jetzt eine Warnung angezeigt, die dies besagt

Environment.getExternalStorageDirectory() ist in API-Level 29 veraltet

Mein Code ist

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

Was wird die Alternative dafür sein?

Antworten:


94

Verwendung getExternalFilesDir(), getExternalCacheDir()oder getExternalMediaDirs()(Methoden Context) anstelle von Environment.getExternalStorageDirectory().

Oder ändern Sie mPhotoEditor, um mit a arbeiten zu können Uri, und dann:

  • Verwenden Sie ACTION_CREATE_DOCUMENTdiese Option , um Urian einen Ort zu gelangen , den der Benutzer ausgewählt hat, oder

  • Verwendung MediaStore, ContentResolverund insert()einen erhalten Urifür einen bestimmten Typen von Medien (zB ein Bild) - sehen Sie diese Beispielanwendung , die diese zum Download MP4 - Videos von einer Website demonstriert tun

Beachten Sie außerdem, dass Ihr Uri.fromFilewith ACTION_MEDIA_SCANNER_SCAN_FILEunter Android 7.0+ mit a abstürzen sollte FileUriExposedException. Unter Android Q werden Ihre Inhalte nur mit der Option MediaStore/ schnell insert()indiziert MediaStore.

Beachten Sie, dass Sie diese Änderungen am "Speicherbereich" unter Android 10 und 11 deaktivieren können, wenn Sie targetSdkVersionunter 30 sind, indem Sie android:requestLegacyExternalStorage="true"das <application>Element des Manifests verwenden. Dies ist keine langfristige Lösung , da Sie targetSdkVersionirgendwann im Jahr 2021 mindestens 30 Jahre alt sein müssen, wenn Sie Ihre App über den Play Store (und möglicherweise auch anderswo) vertreiben.


11
Sehr geehrte @CommonsWare, sollten wir uns jemals in einem echten Leben treffen, erinnern Sie mich bitte daran, dass ich Ihnen ein Bier für Ihren Blog-Beitrag schulde. Ich habe einen ganzen Nachmittag damit verbracht herauszufinden, warum eine bestimmte externe Bibliothek nicht mehr funktioniert, sobald ich das targetSdk-Level auf 29 erhöht habe. Jetzt weiß ich warum ...
Nantoka

1
Die Verwendung von getExternalFilesDir () und getExternalCacheDir () kann mit API 29 funktionieren. Wenn die App nicht deinstalliert ist, werden alle Daten mit diesen Methoden entfernt. Wenn Sie dieses Attribut im Anwendungstag "android: requestLegacyExternalStorage =" true "" verwenden, kann dies auch für API funktionieren 29. Wenn der nicht kristallisierte Dateiname der App beendet wird, aber die Daten entfernt werden
Ucdemir

1
@miladsalimi: Sorry, aber ich verstehe deine Frage nicht. Ich schlage vor, dass Sie eine separate Frage zum Stapelüberlauf stellen, in der Sie ausführlich erläutern, was Ihr Anliegen ist.
CommonsWare

3
Es gibt kein getExternalMediaDirIn Context, sehen Sie es selbst: developer.android.com/reference/android/content/Context Und getExternalMediaDirsist veraltet. Schauen Sie auch: developer.android.com/reference/android/content/… Beide getExternalFilesDir()und getExternalCacheDir()werden bei der Deinstallation der Anwendung gelöscht ( siehe die gleiche URL). Keines der oben genannten kann es ersetzen Environment.getExternalStorageDirectory().
Dims

1
Es geht nicht um meinen Anwendungsfall, sondern um den oben genannten Anwendungsfall von topicstarter.
Dims

29

Holen Sie sich das destPathmit dem neuen API-Aufruf:

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

3
Wir können diese Methode verwenden: developer.android.com/training/data-storage/… ? Was denkst du darüber ? :)
aeroxr1

2
Die Umgebungsvariable getExternalFilesDir (Environment.DIRECTORY_DOWNLOADS) ersetzt weiterhin Environment.getExternalStoragePublicDirectory (Environment.DIRECTORY_DOWNLOADS)
Rowan Berry

Die veraltete Methode wurde auf diesen BEHOBENEN Fehler "Berechtigung verweigert" von createNewFile für externes Speicher-Targeting 29 aktualisiert! Vielen Dank!
coolcool1994

Dies ist nicht das öffentliche Verzeichnis
Irfan Ul Haq

22

Für Android Q können Sie android:requestLegacyExternalStorage="true"Ihrem Element im Manifest hinzufügen . Dies setzt Sie in das alte Speichermodell und Ihre vorhandenen externen Speicher Code funktioniert.

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

Technisch gesehen benötigen Sie dies erst, wenn Sie Ihr Update targetSdkVersionauf 29 aktualisiert haben . Apps mit niedrigeren targetSdkVersionWerten aktivieren standardmäßig den Legacy-Speicher und müssen android:requestLegacyExternalStorage="false"sich abmelden.


1
Dieser Beitrag hat meine Suche nach einer Lösung zum Speichern von Daten auf externem Speicher in der neuesten Version von Android Studio mit einem früher entwickelten Code gespeichert - der 8 Stunden dauerte. Vielen Dank.
Sriram Nadiminti

1
Es funktioniert bis API 29. "Achtung: Nachdem Sie Ihre App auf Android 11 (API-Stufe 30) aktualisiert haben, ignoriert das System das Attribut requestLegacyExternalStorage, wenn Ihre App auf Android 11-Geräten ausgeführt wird. Daher muss Ihre App für die Unterstützung des Gültigkeitsbereichs bereit sein Speichern und Migrieren von App-Daten für Benutzer auf diesen Geräten. " developer.android.com/training/data-storage/use-cases
avisper

5

Dies ist ein kleines Beispiel zum Abrufen des URI für eine Datei, wenn Sie ein Foto mit der Standardkamera aufnehmen und in einem DCIM-Ordner (DCIM / app_name / filename.jpg) speichern möchten:

Öffnen Sie die Kamera (denken Sie an die Erlaubnis von 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)
        }
    }
}

Und URI erhalten:

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

Vergessen Sie nicht die WRITE_EXTERNAL_STORAGE-Berechtigung für Pre-Q und fügen Sie AndroidManifest.xml einen FileProvider hinzu.

Und ein Ergebnis erhalten:

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

es ist auch veraltet
Leonardo Henao

post it java bitte
Attaullah

5

Bitte verwenden Sie getExternalFilesDir(), getExternalCacheDir()anstatt Environment.getExternalStorageDirectory()beim Erstellen einer Datei in Android 10.

Siehe folgende Zeile:

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

1
Hallo, wie können wir getExternalFilesDir()mit benutzerdefiniertem Pfad zum Speichern von Bildern verwenden, ich möchte dies verwenden, um zu verhindern, dass Bilder in die Galerie gezeigt werden? Standardmäßig wird es Andorid/data/packagename/...
gespeichert

Sie müssen die Datei im App-Verzeichnis speichern und diese dann mit Media uri
Ajith Memana

3
falsche Antwort ..aktuelle Frage, wie man / storage / emulated / 0 / MyFolder / aber Ihre anser /data/user/0/com.example.package/files/MyFolder
Tarif Chakder

3

Das hat bei mir funktioniert

Fügen Sie diese Zeile in das Anwendungs-Tag der manifestDatei ein

android:requestLegacyExternalStorage="true"

Beispiel

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

Das Ziel-SDK ist 29

  defaultConfig {
        minSdkVersion 16
        targetSdkVersion 29
        multiDexEnabled true
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

1
Duplikat dieses Kommentars: stackoverflow.com/a/61774820/7487335
Josh Correia
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.