Environment.getExternalStorageDirectory () została uznana za przestarzałą w interfejsie API 29 poziomu java

110

Pracując na Android Java, ostatnio zaktualizowanym SDK do poziomu API 29, teraz jest wyświetlane ostrzeżenie, które to stwierdza

Environment.getExternalStorageDirectory() jest przestarzały na poziomie interfejsu API 29

Mój kod to

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

Jaka będzie alternatywa dla tego?

Noaman Akram
źródło

Odpowiedzi:

109

Użyj getExternalFilesDir(), getExternalCacheDir()lub getExternalMediaDirs()(metody włączone Context) zamiast Environment.getExternalStorageDirectory().

Lub zmodyfikuj, mPhotoEditoraby móc pracować z Uri, a następnie:

  • Użyj, ACTION_CREATE_DOCUMENTaby przejść Urido lokalizacji wybranej przez użytkownika, lub

  • Użyj MediaStore, ContentResolveri, insert()aby uzyskać Uridla określonego typu multimediów (np. Obrazu) - zobacz tę przykładową aplikację, która demonstruje, jak to zrobić, aby pobrać filmy MP4 z witryny internetowej

Również pamiętać, że Uri.fromFileze ACTION_MEDIA_SCANNER_SCAN_FILEnależy upaść na Androida 7.0+ z FileUriExposedException. W systemie Android Q tylko opcja MediaStore/ insert()spowoduje MediaStoreszybkie zindeksowanie treści .

Należy pamiętać, że można zrezygnować z tych „przechowywanie” lunetą zmian na Androida 10 i 11, jeśli targetSdkVersionjest poniżej 30, stosując android:requestLegacyExternalStorage="true"w <application>elemencie manifestu. Nie jest to rozwiązanie długoterminowe , ponieważ targetSdkVersionw 2021 roku będziesz musiał mieć co najmniej 30 lat, jeśli rozpowszechniasz swoją aplikację w Sklepie Play (i być może gdzie indziej).

CommonsWare
źródło
12
Drogi @CommonsWare, gdybyśmy kiedykolwiek spotkali się na żywo, przypomnij mi, że jestem ci winien piwo za twój wpis na blogu. Spędziłem całe popołudnie, próbując dowiedzieć się, dlaczego pewna zewnętrzna biblioteka przestała działać, gdy tylko podniosłem docelowy poziom SDK do 29. Teraz wiem, dlaczego ...
Nantoka
1
używanie getExternalFilesDir () i getExternalCacheDir () może działać z api 29, ale gdy aplikacja nie zostanie zainstalowana, wszystkie dane zostaną usunięte przy użyciu tych metod ... Jeśli użyjesz tego atrybutu wewnątrz tagu aplikacji "android: requestLegacyExternalStorage =" true "" może działać również dla API 29. Gdy aplikacja unistalled nazwa pliku kończy działanie, ale dane są usuwane
Ucdemir
1
@miladsalimi: Przepraszamy, ale nie rozumiem twojego pytania. Proponuję zadać osobne pytanie dotyczące przepełnienia stosu, w którym szczegółowo wyjaśnisz swoje obawy.
CommonsWare
3
Nie ma getExternalMediaDirw Contextzobacz to sam: developer.android.com/reference/android/content/Context I getExternalMediaDirsjest przestarzała, również spojrzeć: developer.android.com/reference/android/content/... Zarówno getExternalFilesDir()i getExternalCacheDir()są usuwane po odinstalowaniu aplikacji ( zobacz ten sam adres URL). Tak więc żadne z powyższych nie może zastąpić Environment.getExternalStorageDirectory().
Przyciemnia
1
Nie chodzi o mój przypadek użycia, ale powyższy przypadek użycia tematu.
Przyciemnia
33

Pobierz za destPathpomocą nowego wywołania interfejsu API:

String destPath = mContext.getExternalFilesDir(null).getAbsolutePath();
Russell A.
źródło
3
Możemy skorzystać z tej metody: developer.android.com/training/data-storage/… ? Co o tym myślisz ? :)
aeroxr1
2
Nadal można pobrać zmienną środowiskową getExternalFilesDir (Environment.DIRECTORY_DOWNLOADS) zastępuje Environment.getExternalStoragePublicDirectory (Environment.DIRECTORY_DOWNLOADS)
Rowan Berry
Zaktualizowano przestarzałą metodę na ten NAPRAWIONY błąd „Odmowa uprawnień” z createNewFile dla zewnętrznego magazynu docelowego dla 29! Dziękuję Ci!
coolcool1994
To nie jest katalog publiczny
Irfan Ul Haq
25

W przypadku Androida Q możesz dodać android:requestLegacyExternalStorage="true"do swojego elementu w manifeście. Ten opowiada cię do starszego modelu pamięci, a istniejący zewnętrzny kod przechowywania zadziała.

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

Z technicznego punktu widzenia jest to potrzebne tylko po zaktualizowaniu targetSdkVersiondo 29. Aplikacje o niższych targetSdkVersionwartościach domyślnie przechodzą na starszą pamięć i musiałyby android:requestLegacyExternalStorage="false"z niej zrezygnować.

mahmoud mansour
źródło
1
Ten post zapisał moje poszukiwania rozwiązania przechowywania danych na zewnętrznej pamięci w najnowszej wersji Android Studio z wcześniej opracowanym kodem - które trwało 8 godzin. Dziękuję Ci.
Sriram Nadiminti
1
Będzie działać do API 29. ”Uwaga: po zaktualizowaniu aplikacji do docelowego systemu Android 11 (poziom interfejsu API 30) system ignoruje atrybut requestLegacyExternalStorage, gdy aplikacja jest uruchomiona na urządzeniach z systemem Android 11, więc aplikacja musi być gotowa do obsługi w zakresie przechowywania i migracji danych aplikacji dla użytkowników na tych urządzeniach ”. developer.android.com/training/data-storage/use-cases
avisper
Nadal
pojawia
6

Proszę używać getExternalFilesDir(), getExternalCacheDir()zamiast Environment.getExternalStorageDirectory()podczas tworzenia pliku w Androidzie 10.

Zobacz poniżej wiersz:

val file = File(this.externalCacheDir!!.absolutePath, "/your_file_name")
Hardik Hirpara
źródło
1
Cześć, jak możemy używać getExternalFilesDir()niestandardowej ścieżki do zapisywania obrazu, chcę tego użyć, aby zapobiec wyświetlaniu obrazów w Galerii? Domyślnie zapisz doAndorid/data/packagename/...
milad salimi
Będziesz musiał zapisać plik w katalogu aplikacji, a następnie wstawić go za pomocą Media uri
Ajith Memana
6
zła odpowiedź .. rzeczywiste pytanie, jak uzyskać / przechowywanie / emulowane / 0 / MyFolder / ale twoja odpowiedź /data/user/0/com.example.package/files/MyFolder
Tarif Chakder
5

To jest mały przykład, jak uzyskać identyfikator URI dla pliku, jeśli chcesz zrobić zdjęcie przy użyciu domyślnego aparatu i zapisać je w folderze DCIM (DCIM / nazwa_aplikacji / nazwa_pliku.jpg):

Otwórz kamerę (pamiętaj o uprawnieniu KAMERA):

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

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

Nie zapomnij o uprawnieniu WRITE_EXTERNAL_STORAGE dla pre Q i dodaj FileProvider do AndroidManifest.xml.

I uzyskaj wynik:

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
źródło
jest również przestarzały
Leonardo Henao
1
opublikuj java, proszę
Attaullah
5

To zadziałało dla mnie

Dodaj tę linię w tagu aplikacji manifestpliku

android:requestLegacyExternalStorage="true"

Przykład

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

Docelowy SDK to 29

  defaultConfig {
        minSdkVersion 16
        targetSdkVersion 29
        multiDexEnabled true
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
Szybki uczeń
źródło
1
Duplikat tego komentarza: stackoverflow.com/a/61774820/7487335
Josh Correia