Môi trường.getExternalStorageDirectory () không được dùng trong java API cấp 29


102

Làm việc trên Android Java, SDK được cập nhật gần đây lên API cấp 29 hiện có một cảnh báo hiển thị cho biết rằng

Environment.getExternalStorageDirectory() không được dùng nữa trong API cấp 29

Mã của tôi là

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

Đâu sẽ là giải pháp thay thế cho điều này?

Câu trả lời:


94

Sử dụng getExternalFilesDir(), getExternalCacheDir()hoặc getExternalMediaDirs()(các phương thức trên Context) thay vì Environment.getExternalStorageDirectory().

Hoặc, sửa đổi mPhotoEditorđể có thể làm việc với Uri, sau đó:

  • Sử dụng ACTION_CREATE_DOCUMENTđể Uriđến một vị trí mà người dùng lựa chọn, hoặc

  • Sử dụng MediaStore, ContentResolverinsert()để có được một Uricho một loại phương tiện truyền thông (ví dụ, một hình ảnh) - xem ứng dụng mẫu này thể hiện làm điều này để tải về video MP4 từ một trang Web

Ngoài ra, lưu ý rằng Uri.fromFilevới của bạn ACTION_MEDIA_SCANNER_SCAN_FILEsẽ bị lỗi trên Android 7.0+ với FileUriExposedException. Trên Android Q, chỉ có tùy chọn MediaStore/ insert()mới giúp nội dung của bạn được lập chỉ mục MediaStorenhanh chóng.

Lưu ý rằng bạn có thể chọn không tham gia các thay đổi "bộ nhớ trong phạm vi" này trên Android 10 và 11, nếu bạn targetSdkVersiondưới 30 tuổi, bằng cách sử dụng android:requestLegacyExternalStorage="true"trong <application>phần tử của tệp kê khai. Đây không phải là một giải pháp lâu dài , vì bạn targetSdkVersionsẽ cần phải từ 30 tuổi trở lên vào năm 2021 nếu bạn đang phân phối ứng dụng của mình thông qua Cửa hàng Play (và có thể ở những nơi khác).


11
@CommonsWare thân mến, nếu chúng ta gặp nhau trực tiếp, hãy nhắc tôi rằng tôi nợ bạn một ly bia cho bài đăng blog của bạn. Tôi đã dành cả buổi chiều tìm hiểu lý do tại sao một thư viện bên ngoài nhất định ngừng làm việc, ngay sau khi tôi nâng mức targetSdk đến 29. Bây giờ tôi biết tại sao ...
Nantoka

1
sử dụng getExternalFilesDir () và getExternalCacheDir () có thể hoạt động với api 29 nhưng khi ứng dụng không bị cấm, tất cả dữ liệu sẽ bị xóa bằng phương pháp này ... Nếu bạn sử dụng thuộc tính này bên trong thẻ ứng dụng "android: requestLegacyExternalStorage =" true "" cũng có thể hoạt động với api 29. Khi tên tệp không bị
chặn của

1
@miladsalimi: Xin lỗi, nhưng tôi không hiểu câu hỏi của bạn. Tôi khuyên bạn nên hỏi một câu hỏi Stack Overflow riêng biệt trong đó bạn giải thích chi tiết mối quan tâm của bạn là gì.
CommonsWare

3
Không có getExternalMediaDirtrong đó Context, hãy tự xem: developer.android.com/reference/android/content/ContextgetExternalMediaDirskhông được dùng nữa, hãy xem: developer.android.com/reference/android/content/… Cả hai getExternalFilesDir()getExternalCacheDir()đều bị xóa sau khi gỡ cài đặt ứng dụng ( xem cùng một URL). Vì vậy, không điều gì ở trên có thể thay thế được Environment.getExternalStorageDirectory().
Dims

1
Nó không phải về usecase của tôi, mà là usecase của topicstarter ở trên.
Dims

29

Nhận destPathlệnh gọi API mới:

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

3
Chúng tôi có thể sử dụng phương pháp này: developer.android.com/training/data-storage/… ? Bạn nghĩ gì về nó ? :)
aeroxr1

2
Vẫn có thể lấy biến môi trường getExternalFilesDir (Environment.DIRECTORY_DOWNLOADS) thay thế Environment.getExternalStoragePublicDirectory (Môi trường.DIRECTORY_DOWNLOADS)
Rowan Berry

Đã cập nhật phương pháp không dùng nữa thành lỗi "Quyền bị từ chối" CỐ ĐỊNH này từ createNewFile cho nhắm mục tiêu bộ nhớ ngoài 29! Cảm ơn bạn!
coolcool1994

Đây không phải là thư mục công cộng
Irfan Ul Haq

22

Đối với Android Q, bạn có thể thêm android:requestLegacyExternalStorage="true"vào phần tử của mình trong tệp kê khai. Điều này chọn bạn tham gia mô hình lưu trữ kế thừa và mã bộ nhớ ngoài hiện có của bạn sẽ hoạt động.

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

Về mặt kỹ thuật, bạn chỉ cần điều này sau khi cập nhật targetSdkVersionlên 29. Các ứng dụng có targetSdkVersiongiá trị thấp hơn được mặc định chọn sử dụng bộ nhớ cũ và sẽ cần android:requestLegacyExternalStorage="false"chọn không tham gia.


1
Bài đăng này đã lưu tìm kiếm của tôi về giải pháp lưu trữ dữ liệu trên bộ nhớ ngoài trong phiên bản Android Studio mới nhất với mã được phát triển trước đó - kéo dài 8 giờ. Cảm ơn bạn.
Sriram Nadiminti

1
Nó sẽ hoạt động cho đến API 29. "Lưu ý: Sau khi bạn cập nhật ứng dụng của mình để nhắm mục tiêu đến Android 11 (API cấp 30), hệ thống sẽ bỏ qua thuộc tính requestLegacyExternalStorage khi ứng dụng của bạn đang chạy trên thiết bị Android 11, vì vậy ứng dụng của bạn phải sẵn sàng hỗ trợ trong phạm vi bộ nhớ và để di chuyển dữ liệu ứng dụng cho người dùng trên các thiết bị đó. " developer.android.com/training/data-storage/use-case
avisper

5

Đây là một ví dụ nhỏ về cách lấy URI cho tệp nếu bạn muốn chụp ảnh bằng máy ảnh mặc định và lưu trữ trong thư mục DCIM (DCIM / app_name / filename.jpg):

Mở máy ảnh (hãy nhớ về quyền 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)
        }
    }
}

Và nhậ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
    )
}

Đừng quên về quyền WRITE_EXTERNAL_STORAGE cho pre Q và thêm FileProvider vào AndroidManifest.xml.

Và nhận được một kết quả:

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

nó cũng không được dùng nữa
Leonardo Henao

làm ơn đăng nó lên java
Attaullah

5

Vui lòng sử dụng getExternalFilesDir(), getExternalCacheDir()thay vì Environment.getExternalStorageDirectory()khi bạn tạo tệp trong Android 10.

Xem dòng dưới đây:

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

1
Xin chào, làm cách nào chúng ta có thể sử dụng getExternalFilesDir()đường dẫn tùy chỉnh để lưu hình ảnh, tôi muốn sử dụng đường dẫn này để ngăn hiển thị hình ảnh vào Thư viện? Theo mặc định, nó lưu vàoAndorid/data/packagename/...
milad salimi

Bạn sẽ cần lưu tệp vào thư mục ứng dụng và sau đó chèn tệp này bằng Media uri
Ajith Memana

3
câu trả lời sai .. câu hỏi thực tế làm thế nào để tải / storage / emulated / 0 / MyFolder / but anser /data/user/0/com.example.package/files/MyFolder
Tarif Chakder của bạn

3

Điều này đã làm việc cho tôi

Thêm dòng này trong thẻ ứng dụng của manifesttệp

android:requestLegacyExternalStorage="true"

Thí dụ

 <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 mục tiêu là 29

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

1
Bản sao của nhận xét này: stackoverflow.com/a/61774820/7487335
Josh Correia
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.