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เพื่อให้สามารถทำงานกับ a Uriจากนั้น:

  • ใช้ACTION_CREATE_DOCUMENTเพื่อไปUriยังตำแหน่งที่ผู้ใช้เลือกหรือ

  • การใช้งานMediaStore, ContentResolverและinsert()ที่จะได้รับUriสำหรับประเภทเฉพาะของสื่อ (เช่นภาพ) - เห็นนี้ app ตัวอย่างที่แสดงให้เห็นถึงการทำเช่นนี้สำหรับการดาวน์โหลดวิดีโอ MP4 จากเว็บไซต์

นอกจากนี้ทราบว่าคุณUri.fromFileมีACTION_MEDIA_SCANNER_SCAN_FILEควรได้รับการกระแทกบน Android 7.0 FileUriExposedExceptionขึ้นไปด้วย ใน Android Q มีเพียงตัวเลือกMediaStore/ เท่านั้นที่insert()จะได้รับการจัดทำดัชนีเนื้อหาของคุณMediaStoreอย่างรวดเร็ว

โปรดทราบว่าคุณสามารถเลือกไม่ใช้การเปลี่ยนแปลง "พื้นที่เก็บข้อมูลที่กำหนดขอบเขต" เหล่านี้ใน Android 10 และ 11 ได้หากคุณtargetSdkVersionอายุต่ำกว่า 30 ปีโดยใช้android:requestLegacyExternalStorage="true"ใน<application>องค์ประกอบของไฟล์ Manifest นี่ไม่ใช่วิธีแก้ปัญหาระยะยาวเนื่องจากคุณtargetSdkVersionจะต้องมีอายุ 30 ปีขึ้นไปในปี 2021 หากคุณเผยแพร่แอปผ่าน Play Store (และที่อื่น ๆ )


12
เรียน @CommonsWare เราควรจะได้พบกันในชีวิตจริงโปรดเตือนฉันว่าฉันเป็นหนี้คุณเบียร์สำหรับโพสต์บล็อกของคุณ ฉันใช้เวลาทั้งบ่ายเพื่อค้นหาสาเหตุที่ห้องสมุดภายนอกบางแห่งหยุดทำงานทันทีที่ฉันเพิ่มระดับ targetSdk เป็น 29 ตอนนี้ฉันรู้แล้วว่าทำไม ...
Nantoka

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()ได้
หรี่

1
ไม่เกี่ยวกับ usecase ของฉัน แต่เป็น usecase ของ topicstarter ด้านบน
หรี่

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)
Rowan Berry

อัปเดตวิธีการที่เลิกใช้เป็นข้อผิดพลาด "การอนุญาตถูกปฏิเสธ" คงที่จาก createNewFile สำหรับการจัดเก็บข้อมูลภายนอกที่กำหนดเป้าหมาย 29 ขอขอบคุณ!
coolcool1994

นี่ไม่ใช่ไดเรกทอรีสาธารณะ
Irfan Ul Haq

25

สำหรับ Android Q คุณสามารถเพิ่มลงandroid:requestLegacyExternalStorage="true"ในองค์ประกอบของคุณในไฟล์ Manifest การดำเนินการนี้จะเลือกให้คุณใช้โมเดลพื้นที่เก็บข้อมูลเดิมและรหัสพื้นที่เก็บข้อมูลภายนอกที่คุณมีอยู่จะใช้งานได้

<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
โพสต์นี้บันทึกการค้นหาของฉันสำหรับวิธีการจัดเก็บข้อมูลในที่จัดเก็บข้อมูลภายนอกใน Android Studio เวอร์ชันล่าสุดด้วยรหัสที่พัฒนาก่อนหน้านี้ซึ่งใช้เวลา 8 ชั่วโมง ขอขอบคุณ.
Sriram Nadiminti

1
จะใช้งานได้จนถึง API 29 "ข้อควรระวัง: หลังจากที่คุณอัปเดตแอปของคุณเพื่อกำหนดเป้าหมายเป็น Android 11 (API ระดับ 30) ระบบจะเพิกเฉยต่อแอตทริบิวต์ requestLegacyExternalStorage เมื่อแอปของคุณทำงานบนอุปกรณ์ Android 11 ดังนั้นแอปของคุณต้องพร้อมที่จะรองรับขอบเขต พื้นที่เก็บข้อมูลและเพื่อย้ายข้อมูลแอปสำหรับผู้ใช้บนอุปกรณ์เหล่านั้น " developer.android.com/training/data-storage/use-cases
avisper

ยังคงได้รับข้อผิดพลาดเดิม
olajide

6

กรุณาใช้getExternalFilesDir(), getExternalCacheDir()แทนEnvironment.getExternalStorageDirectory()เมื่อคุณสร้างไฟล์ใน Android 10

ดูบรรทัดด้านล่าง:

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

1
สวัสดีเราจะใช้getExternalFilesDir()กับเส้นทางที่กำหนดเองเพื่อบันทึกภาพได้อย่างไรฉันต้องการใช้สิ่งนี้เพื่อป้องกันไม่ให้แสดงภาพในคลังภาพ โดยค่าเริ่มต้นจะบันทึกเป็นAndorid/data/packagename/...
milad salimi

คุณจะต้องบันทึกไฟล์ลงในไดเรกทอรีแอปจากนั้นแทรกโดยใช้ Media uri
Ajith Memana

6
ตอบผิด .. คำถามจริงวิธีรับ / storage / emulated / 0 / MyFolder / แต่ anser /data/user/0/com.example.package/files/MyFolder
Tarif Chakder

5

นี่เป็นตัวอย่างเล็กน้อยในการรับ URI สำหรับไฟล์หากคุณต้องการถ่ายภาพโดยใช้กล้องเริ่มต้นและจัดเก็บไว้ในโฟลเดอร์ DCIM (DCIM / app_name / filename.jpg):

เปิดกล้อง (จำเกี่ยวกับการอนุญาต 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
    )
}

อย่าลืมเกี่ยวกับสิทธิ์ WRITE_EXTERNAL_STORAGE สำหรับ pre Q และเพิ่ม FileProvider ไปยัง AndroidManifest.xml

และรับผลลัพธ์:

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

มันเลิกใช้แล้วเช่นกัน
Leonardo Henao

1
โพสต์มัน java
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"
    }

1
สำเนาความคิดเห็นนี้: stackoverflow.com/a/61774820/7487335
Josh Correia
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.