Environment.getExternalStorageDirectory ()는 API 레벨 29 Java에서 더 이상 사용되지 않습니다.


110

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

이것에 대한 대안은 무엇입니까?

답변:


109

사용 getExternalFilesDir(), getExternalCacheDir()또는 getExternalMediaDirs()(방법에 대한 Context대신) Environment.getExternalStorageDirectory().

또는에서 mPhotoEditor작업 할 수 있도록 수정 Uri한 다음 :

  • 사용자가 선택한 위치 ACTION_CREATE_DOCUMENT로 이동하는 Uri데 사용 합니다.

  • 사용 MediaStore, ContentResolverinsert()를 얻기 위해 Uri미디어의 특정 유형 (예를 들어, 이미지)에 대한 - 볼 이 샘플 응용 프로그램 웹 사이트에서 MP4 비디오를 다운로드하는이 일을 보여줍니다

또한, 참고 귀하 Uri.fromFileACTION_MEDIA_SCANNER_SCAN_FILE로모그래퍼 7.0 이상 안드로이드에 충돌한다 FileUriExposedException. Android Q에서는 MediaStore/ insert()옵션 만MediaStore 빠르게 .

Android 10 및 11에서 targetSdkVersion30 미만인 경우 매니페스트 android:requestLegacyExternalStorage="true"<application>요소를 사용하여 이러한 '범위가 지정된 저장소'변경 사항을 선택 해제 할 수 있습니다 . 이는 장기적인 해결책이 아니다 당신으로, targetSdkVersion당신이 (다른 곳에서 아마와) Play 스토어를 통해 응용 프로그램을 배포하는 경우 2021 년 30 이상 언젠가 될 의지가 필요합니다.


12
친애하는 @CommonsWare에게, 우리가 실제 라이브로 만나야한다면, 당신의 블로그 포스트를 위해 맥주를 빚지고 있다는 것을 상기시켜주십시오. 나는 targetSdk 레벨을 29로 올리 자마자 특정 외부 라이브러리가 작동을 멈춘 이유를 찾는 데 오후 내내 보냈습니다. 이제 그 이유를 알았습니다 ...
Nantoka 19

1
getExternalFilesDir () 및 getExternalCacheDir ()을 사용하면 api 29에서 작동 할 수 있지만 앱이 제거되면이 메서드를 사용하여 모든 데이터가 제거됩니다.이 속성을 애플리케이션 태그 "android : requestLegacyExternalStorage ="true ""내에서 사용하면 api에서도 작동 할 수 있습니다. 29. 앱이 제거되지 않은 파일 이름이 종료되지만 데이터가 제거 된 경우
Ucdemir

1
@miladsalimi : 죄송하지만 질문을 이해하지 못합니다. 우려 사항이 무엇인지 자세히 설명하는 별도의 Stack Overflow 질문을하는 것이 좋습니다.
CommonsWare

3
아무 없습니다 getExternalMediaDir에서 Context스스로를 참조하십시오 : developer.android.com/reference/android/content/Context 그리고 getExternalMediaDirs: 사용되지 않으며, 또한 본다 developer.android.com/reference/android/content/...를 모두 getExternalFilesDir()하고 getExternalCacheDir()(응용 프로그램 제거시 삭제됩니다 동일한 URL 참조). 따라서 위의 어느 것도 Environment.getExternalStorageDirectory().
Dims July

1
내 사용 사례가 아니라 위의 topicstarter 사용 사례입니다.
Dims

33

가져 오기 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)를 대체
로완 베리

더 이상 사용되지 않는 메소드를 29!를 대상으로하는 외부 저장소에 대한 createNewFile에서이 수정 된 "Permission denied"오류로 업데이트했습니다. 감사합니다!
coolcool1994

이것은 대중 디렉토리가 아니다
이르판 울 하크

25

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
이 게시물은 이전에 개발 된 코드를 사용하여 8 시간 동안 지속되는 최신 버전의 Android Studio에서 외부 저장소에 데이터를 저장하는 솔루션에 대한 검색을 저장했습니다. 감사합니다.
Sriram Nadiminti

1
API 29까지 작동합니다. "주의 : Android 11 (API 레벨 30)을 대상으로 앱을 업데이트 한 후 앱이 Android 11 기기에서 실행될 때 시스템은 requestLegacyExternalStorage 속성을 무시하므로 앱이 범위 지정을 지원할 준비가되어 있어야합니다. 저장하고 해당 기기의 사용자를 위해 앱 데이터를 마이그레이션합니다. " developer.android.com/training/data-storage/use-cases
avisper

여전히 같은 오류가
olajide

6

사용하십시오 getExternalFilesDir(), getExternalCacheDir()대신 Environment.getExternalStorageDirectory()당신은 안드로이드 (10)에 파일을 생성 할 때.

아래 줄을 참조하십시오.

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

1
안녕하세요, getExternalFilesDir()이미지 저장에 대한 사용자 지정 경로를 어떻게 사용할 수 있습니까? 갤러리에 이미지 표시를 방지하기 위해 이것을 사용하고 싶습니다. 기본적으로 그것은으로 저장Andorid/data/packagename/...
밀라 드 salimi

파일을 app 디렉토리에 저장 한 다음 Media uri
Ajith Memana를

6
오답 ..actual 질문하는 방법을 / 저장 / 에뮬레이트 / 0 / MyFolder에 / 그러나 당신의 답변에 /data/user/0/com.example.package/files/MyFolder
Tarif Chakder

5

다음은 기본 카메라를 사용하여 사진을 찍고 DCIM 폴더 (DCIM / app_name / filename.jpg)에 저장하려는 경우 파일의 URI를 가져 오는 방법의 작은 예입니다.

카메라 열기 (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)
        }
    }
}

그리고 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
    )
}

사전 Q에 대한 WRITE_EXTERNAL_STORAGE 권한을 잊지 말고 AndroidManifest.xml에 FileProvider를 추가하십시오.

결과를 얻으십시오.

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

그것은 또한 더 이상 사용되지 않습니다
레오나르도 Henao을

1
그것을 자바 제발 게시
Attaullah

5

이것은 나를 위해 일했습니다.

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.