จะใช้ API การเข้าถึงการ์ด SD ใหม่ที่นำเสนอสำหรับ Android 5.0 (Lollipop) ได้อย่างไร


118

พื้นหลัง

บนAndroid 4.4 (KitKat) Google ได้ให้การเข้าถึงการ์ด SD ค่อนข้าง จำกัด

ในฐานะของ Android Lollipop (5.0) นักพัฒนาสามารถใช้ API ใหม่ที่ขอให้ผู้ใช้ยืนยันเพื่อให้สามารถเข้าถึงโฟลเดอร์ที่เฉพาะเจาะจงตามที่เขียนไว้ในโพสต์นี้ของ Google Groups

ปัญหา

โพสต์นี้แนะนำให้คุณเยี่ยมชมเว็บไซต์สองแห่ง:

นี่ดูเหมือนตัวอย่างภายใน (อาจจะแสดงในการสาธิต API ในภายหลัง) แต่มันค่อนข้างยากที่จะเข้าใจว่าเกิดอะไรขึ้น

นี่เป็นเอกสารอย่างเป็นทางการของ API ใหม่ แต่ไม่ได้บอกรายละเอียดเกี่ยวกับวิธีการใช้งานเพียงพอ

นี่คือสิ่งที่จะบอกคุณ:

หากคุณต้องการเข้าถึงแผนผังย่อยทั้งหมดของเอกสารจริงๆให้เริ่มด้วยการเรียกใช้ ACTION_OPEN_DOCUMENT_TREE เพื่อให้ผู้ใช้เลือกไดเร็กทอรี จากนั้นส่ง getData () ที่เป็นผลลัพธ์ไปยัง fromTreeUri (Context, Uri) เพื่อเริ่มทำงานกับทรีที่ผู้ใช้เลือก

ในขณะที่คุณสำรวจโครงสร้างของอินสแตนซ์ DocumentFile คุณสามารถใช้ getUri () เพื่อรับ Uri ที่เป็นตัวแทนของเอกสารพื้นฐานสำหรับอ็อบเจ็กต์นั้นสำหรับใช้กับ openInputStream (Uri) เป็นต้น

เพื่อลดความซับซ้อนของโค้ดของคุณบนอุปกรณ์ที่ใช้ KITKAT หรือรุ่นก่อนหน้าคุณสามารถใช้ fromFile (ไฟล์) ซึ่งเลียนแบบพฤติกรรมของ DocumentsProvider

คำถาม

ฉันมีคำถามสองสามข้อเกี่ยวกับ API ใหม่:

  1. คุณใช้มันอย่างไร?
  2. ตามโพสต์ OS จะจำได้ว่าแอพได้รับอนุญาตให้เข้าถึงไฟล์ / โฟลเดอร์ คุณจะตรวจสอบได้อย่างไรว่าคุณสามารถเข้าถึงไฟล์ / โฟลเดอร์ได้หรือไม่? มีฟังก์ชันที่ส่งคืนรายการไฟล์ / โฟลเดอร์ที่ฉันสามารถเข้าถึงได้หรือไม่?
  3. คุณจัดการปัญหานี้ใน Kitkat อย่างไร? เป็นส่วนหนึ่งของไลบรารีสนับสนุนหรือไม่
  4. มีหน้าจอการตั้งค่าบนระบบปฏิบัติการที่แสดงว่าแอปใดสามารถเข้าถึงไฟล์ / โฟลเดอร์ใดได้บ้าง
  5. จะเกิดอะไรขึ้นหากติดตั้งแอปสำหรับผู้ใช้หลายคนในอุปกรณ์เดียวกัน
  6. มีเอกสาร / บทช่วยสอนอื่น ๆ เกี่ยวกับ API ใหม่นี้หรือไม่?
  7. สามารถเพิกถอนสิทธิ์ได้หรือไม่ ถ้าเป็นเช่นนั้นมีเจตนาที่ส่งไปยังแอปหรือไม่
  8. การขออนุญาตจะทำงานซ้ำในโฟลเดอร์ที่เลือกหรือไม่
  9. การใช้การอนุญาตจะอนุญาตให้ผู้ใช้มีโอกาสเลือกหลายรายการตามตัวเลือกของผู้ใช้หรือไม่ หรือแอปจำเป็นต้องบอกเจตนาโดยเฉพาะว่าอนุญาตให้ใช้ไฟล์ / โฟลเดอร์ใด
  10. มีวิธีในโปรแกรมจำลองเพื่อลองใช้ API ใหม่หรือไม่? ฉันหมายถึงมีพาร์ติชันการ์ด SD แต่ทำงานเป็นที่เก็บข้อมูลภายนอกหลักดังนั้นการเข้าถึงทั้งหมดจึงได้รับแล้ว (โดยใช้การอนุญาตอย่างง่าย)
  11. จะเกิดอะไรขึ้นเมื่อผู้ใช้แทนที่การ์ด SD ด้วยการ์ดอื่น

FWIW, AndroidPolice มีบทความเล็กน้อยเกี่ยวกับเรื่องนี้ในวันนี้
fattire

@fattire ขอบคุณครับ. แต่พวกเขากำลังแสดงเกี่ยวกับสิ่งที่ฉันได้อ่านไปแล้ว แม้ว่าจะเป็นข่าวดี
นักพัฒนา Android

32
ทุกครั้งที่ OS ใหม่ออกมามันทำให้ชีวิตเราซับซ้อนขึ้น ...
Phantômaxx

@Funkystein จริง. หวังว่าพวกเขาจะทำใน Kitkat ตอนนี้มีพฤติกรรม 3 ประเภทที่ต้องจัดการ
นักพัฒนา Android

1
@Funkystein ฉันไม่รู้ ฉันใช้มันเมื่อนานมาแล้ว ฉันคิดว่าการตรวจสอบนี้ไม่ใช่เรื่องเลวร้าย คุณต้องจำไว้ว่าพวกเขาก็เป็นมนุษย์เช่นกันดังนั้นพวกเขาจึงสามารถทำผิดพลาดและเปลี่ยนใจได้เป็นครั้งคราว ...
นักพัฒนา Android

คำตอบ:


143

คำถามดีๆมากมายมาดูกัน :)

คุณใช้มันอย่างไร?

นี่คือบทช่วยสอนที่ยอดเยี่ยมสำหรับการโต้ตอบกับ Storage Access Framework ใน KitKat:

https://developer.android.com/guide/topics/providers/document-provider.html#client

การโต้ตอบกับ API ใหม่ใน Lollipop นั้นคล้ายกันมาก ในการแจ้งให้ผู้ใช้เลือกแผนผังไดเร็กทอรีคุณสามารถเริ่มต้นเจตนาดังนี้:

    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    startActivityForResult(intent, 42);

จากนั้นใน onActivityResult () ของคุณคุณสามารถส่ง Uri ที่ผู้ใช้เลือกไปยังคลาสตัวช่วย DocumentFile ใหม่ได้ นี่คือตัวอย่างสั้น ๆ ที่แสดงรายการไฟล์ในไดเร็กทอรีที่เลือกจากนั้นสร้างไฟล์ใหม่:

public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
    if (resultCode == RESULT_OK) {
        Uri treeUri = resultData.getData();
        DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);

        // List all existing files inside picked directory
        for (DocumentFile file : pickedDir.listFiles()) {
            Log.d(TAG, "Found file " + file.getName() + " with size " + file.length());
        }

        // Create a new file and write into it
        DocumentFile newFile = pickedDir.createFile("text/plain", "My Novel");
        OutputStream out = getContentResolver().openOutputStream(newFile.getUri());
        out.write("A long time ago...".getBytes());
        out.close();
    }
}

Uri ที่ส่งคืนDocumentFile.getUri()มีความยืดหยุ่นเพียงพอที่จะใช้กับ API แพลตฟอร์มที่แตกต่างกัน ตัวอย่างเช่นคุณสามารถแบ่งปันโดยใช้Intent.setData()กับIntent.FLAG_GRANT_READ_URI_PERMISSIONไฟล์.

หากคุณต้องการเข้าถึง Uri นั้นจากโค้ดเนทีฟคุณสามารถเรียกContentResolver.openFileDescriptor()ใช้แล้วใช้ParcelFileDescriptor.getFd()หรือdetachFd()รับจำนวนเต็มตัวอธิบายไฟล์ POSIX แบบดั้งเดิม

คุณจะตรวจสอบได้อย่างไรว่าคุณสามารถเข้าถึงไฟล์ / โฟลเดอร์ได้หรือไม่?

ตามค่าเริ่มต้น Uris ที่ส่งคืนผ่าน Intent Storage Access Frameworks จะไม่คงอยู่ในระหว่างการรีบูต แพลตฟอร์ม "เสนอ" ความสามารถในการคงสิทธิ์ แต่คุณยังต้อง "รับ" การอนุญาตหากคุณต้องการ ในตัวอย่างของเราด้านบนคุณจะเรียก:

    getContentResolver().takePersistableUriPermission(treeUri,
            Intent.FLAG_GRANT_READ_URI_PERMISSION |
            Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

คุณสามารถทราบได้ตลอดเวลาว่าแอปของคุณมีสิทธิ์เข้าถึงอะไรบ้างผ่านContentResolver.getPersistedUriPermissions()API ถ้าคุณไม่จำเป็นต้องเข้าถึงไปยังยืนกราน Uri ContentResolver.releasePersistableUriPermission()คุณสามารถปล่อยมันด้วย

มีให้ใน KitKat หรือไม่

ไม่เราไม่สามารถเพิ่มฟังก์ชันการทำงานใหม่ย้อนหลังให้กับแพลตฟอร์มเวอร์ชันเก่าได้

ฉันสามารถดูว่าแอปใดบ้างที่สามารถเข้าถึงไฟล์ / โฟลเดอร์ได้

ขณะนี้ยังไม่มี UI ที่แสดงสิ่งนี้ แต่คุณสามารถดูรายละเอียดได้ในส่วน "สิทธิ์ Uri ที่ได้รับ" ของadb shell dumpsys activity providersเอาต์พุต

จะเกิดอะไรขึ้นหากติดตั้งแอปสำหรับผู้ใช้หลายคนในอุปกรณ์เดียวกัน

การให้สิทธิ์ Uri แยกตามผู้ใช้แต่ละคนเช่นเดียวกับฟังก์ชันแพลตฟอร์มผู้ใช้หลายคนอื่น ๆ นั่นคือแอปเดียวกันที่ทำงานภายใต้ผู้ใช้สองคนที่แตกต่างกันไม่มีการให้สิทธิ์ Uri ที่ทับซ้อนกันหรือใช้ร่วมกัน

สามารถเพิกถอนสิทธิ์ได้หรือไม่

DocumentProvider ที่สำรองไว้สามารถเพิกถอนสิทธิ์ได้ตลอดเวลาเช่นเมื่อเอกสารบนคลาวด์ถูกลบ วิธีที่พบบ่อยที่สุดในการค้นหาสิทธิ์ที่ถูกเพิกถอนเหล่านี้คือเมื่อพวกเขาหายไปจากที่ContentResolver.getPersistedUriPermissions()กล่าวมาข้างต้น

สิทธิ์จะถูกเพิกถอนเมื่อใดก็ตามที่มีการล้างข้อมูลแอปสำหรับแอปใดก็ตามที่เกี่ยวข้องกับการให้สิทธิ์

การขออนุญาตจะทำงานซ้ำในโฟลเดอร์ที่เลือกหรือไม่

ใช่ACTION_OPEN_DOCUMENT_TREEเจตนาช่วยให้คุณสามารถเข้าถึงไฟล์และไดเร็กทอรีทั้งที่มีอยู่และที่สร้างขึ้นใหม่

อนุญาตให้เลือกหลายรายการหรือไม่

ใช่การเลือกหลายรายการได้รับการสนับสนุนตั้งแต่ KitKat และคุณสามารถอนุญาตได้โดยการตั้งค่าEXTRA_ALLOW_MULTIPLEเมื่อเริ่มACTION_OPEN_DOCUMENTจุดประสงค์ของคุณ คุณสามารถใช้Intent.setType()หรือEXTRA_MIME_TYPESจำกัด ประเภทไฟล์ที่สามารถเลือกได้:

http://developer.android.com/reference/android/content/Intent.html#ACTION_OPEN_DOCUMENT

มีวิธีในโปรแกรมจำลองเพื่อลองใช้ API ใหม่หรือไม่?

ใช่อุปกรณ์จัดเก็บข้อมูลหลักที่ใช้ร่วมกันควรปรากฏในเครื่องมือเลือกแม้ในโปรแกรมจำลอง ถ้า app ของคุณใช้เฉพาะการจัดเก็บข้อมูลการเข้าถึงกรอบสำหรับการเข้าถึงที่จัดเก็บข้อมูลที่ใช้ร่วมกันคุณไม่จำเป็นต้องมีREAD/WRITE_EXTERNAL_STORAGEสิทธิ์ที่ทุกคนและสามารถลบออกหรือใช้android:maxSdkVersionคุณลักษณะเพียงขอให้พวกเขาบนแพลตฟอร์มรุ่นเก่า

จะเกิดอะไรขึ้นเมื่อผู้ใช้แทนที่การ์ด SD ด้วยการ์ดอื่น

เมื่อมีส่วนเกี่ยวข้องกับสื่อทางกายภาพ UUID (เช่นหมายเลขประจำเครื่อง FAT) ของสื่อที่อยู่เบื้องหลังจะถูกเบิร์นลงใน Uri ที่ส่งคืนเสมอ ระบบใช้สิ่งนี้เพื่อเชื่อมต่อคุณกับสื่อที่ผู้ใช้เลือกไว้ในตอนแรกแม้ว่าผู้ใช้จะสลับสื่อระหว่างช่องหลายช่อง

หากผู้ใช้สลับการ์ดใบที่สองคุณจะต้องแจ้งให้เข้าถึงการ์ดใหม่ เนื่องจากระบบจำการให้สิทธิ์ตาม UUID คุณจะยังคงได้รับสิทธิ์การเข้าถึงการ์ดเดิมก่อนหน้านี้หากผู้ใช้ใส่เข้าไปใหม่ในภายหลัง

http://en.wikipedia.org/wiki/Volume_serial_number


2
ฉันเห็น. ดังนั้นการตัดสินใจคือแทนที่จะเพิ่มมากขึ้นใน API ที่รู้จัก (ของไฟล์) ให้ใช้อันอื่น ตกลง. คุณช่วยตอบคำถามอื่น ๆ (เขียนไว้ในความคิดเห็นแรก) ได้ไหม BTW ขอขอบคุณสำหรับการตอบรับทั้งหมดนี้
นักพัฒนา Android

7
@JeffSharkey มีวิธีใดในการให้ OPEN_DOCUMENT_TREE พร้อมคำแนะนำสถานที่เริ่มต้นหรือไม่? ผู้ใช้ไม่ได้นำทางไปยังสิ่งที่แอปพลิเคชันต้องการเข้าถึงได้ดีที่สุดเสมอไป
Justin

2
มีวิธีการเปลี่ยน Last Modified Timestamp ( setLastModified () อย่างไรในคลาส File) อย่างไร มันเป็นพื้นฐานสำหรับแอปพลิเคชันเช่น archivers
Quark

1
สมมติว่าคุณมีเอกสารโฟลเดอร์ Uri ที่เก็บไว้และคุณต้องการแสดงรายการไฟล์ในภายหลังในบางจุดหลังจากรีบูตอุปกรณ์ DocumentFile.fromTreeUri จะแสดงรายการไฟล์รูทเสมอไม่ว่าคุณจะให้ Uri (แม้แต่ Uri ลูกก็ตาม) ดังนั้นคุณจะสร้าง DocumentFile ที่คุณสามารถเรียก listfiles ได้อย่างไรโดยที่ DocumentFile ไม่ได้แสดงถึงรูทหรือ SingleDocument
AndersC

1
@JeffSharkey จะใช้ URI นี้ใน MediaMuxer ได้อย่างไรเนื่องจากยอมรับสตริงเป็นพา ธ ไฟล์เอาต์พุต MediaMuxer (java.lang.String, int
Petrakeas

45

ในโปรเจ็กต์ Android ของฉันใน Github ที่ลิงก์ด้านล่างคุณจะพบโค้ดการทำงานที่อนุญาตให้เขียนบน extSdCard ใน Android 5 โดยถือว่าผู้ใช้สามารถเข้าถึงการ์ด SD ทั้งหมดจากนั้นให้คุณเขียนได้ทุกที่บนการ์ดนี้ (หากคุณต้องการเข้าถึงไฟล์เดียวเท่านั้นสิ่งต่างๆจะง่ายขึ้น)

ข้อมูลโค้ดหลัก

การเรียกใช้ Storage Access Framework:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void triggerStorageAccessFramework() {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    startActivityForResult(intent, REQUEST_CODE_STORAGE_ACCESS);
}

การจัดการการตอบสนองจาก Storage Access Framework:

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@Override
public final void onActivityResult(final int requestCode, final int resultCode, final Intent resultData) {
    if (requestCode == SettingsFragment.REQUEST_CODE_STORAGE_ACCESS) {
        Uri treeUri = null;
        if (resultCode == Activity.RESULT_OK) {
            // Get Uri from Storage Access Framework.
            treeUri = resultData.getData();

            // Persist URI in shared preference so that you can use it later.
            // Use your own framework here instead of PreferenceUtil.
            PreferenceUtil.setSharedPreferenceUri(R.string.key_internal_uri_extsdcard, treeUri);

            // Persist access permissions.
            final int takeFlags = resultData.getFlags()
                & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            getActivity().getContentResolver().takePersistableUriPermission(treeUri, takeFlags);
        }
    }
}

การรับ outputStream สำหรับไฟล์ผ่าน Storage Access Framework (ใช้ URL ที่จัดเก็บโดยสมมติว่านี่คือ URL ของโฟลเดอร์รากของการ์ด SD ภายนอก)

DocumentFile targetDocument = getDocumentFile(file, false);
OutputStream outStream = Application.getAppContext().
    getContentResolver().openOutputStream(targetDocument.getUri());

สิ่งนี้ใช้วิธีการช่วยเหลือดังต่อไปนี้:

public static DocumentFile getDocumentFile(final File file, final boolean isDirectory) {
    String baseFolder = getExtSdCardFolder(file);

    if (baseFolder == null) {
        return null;
    }

    String relativePath = null;
    try {
        String fullPath = file.getCanonicalPath();
        relativePath = fullPath.substring(baseFolder.length() + 1);
    }
    catch (IOException e) {
        return null;
    }

    Uri treeUri = PreferenceUtil.getSharedPreferenceUri(R.string.key_internal_uri_extsdcard);

    if (treeUri == null) {
        return null;
    }

    // start with root of SD card and then parse through document tree.
    DocumentFile document = DocumentFile.fromTreeUri(Application.getAppContext(), treeUri);

    String[] parts = relativePath.split("\\/");
    for (int i = 0; i < parts.length; i++) {
        DocumentFile nextDocument = document.findFile(parts[i]);

        if (nextDocument == null) {
            if ((i < parts.length - 1) || isDirectory) {
                nextDocument = document.createDirectory(parts[i]);
            }
            else {
                nextDocument = document.createFile("image", parts[i]);
            }
        }
        document = nextDocument;
    }

    return document;
}

public static String getExtSdCardFolder(final File file) {
    String[] extSdPaths = getExtSdCardPaths();
    try {
        for (int i = 0; i < extSdPaths.length; i++) {
            if (file.getCanonicalPath().startsWith(extSdPaths[i])) {
                return extSdPaths[i];
            }
        }
    }
    catch (IOException e) {
        return null;
    }
    return null;
}

/**
 * Get a list of external SD card paths. (Kitkat or higher.)
 *
 * @return A list of external SD card paths.
 */
@TargetApi(Build.VERSION_CODES.KITKAT)
private static String[] getExtSdCardPaths() {
    List<String> paths = new ArrayList<>();
    for (File file : Application.getAppContext().getExternalFilesDirs("external")) {
        if (file != null && !file.equals(Application.getAppContext().getExternalFilesDir("external"))) {
            int index = file.getAbsolutePath().lastIndexOf("/Android/data");
            if (index < 0) {
                Log.w(Application.TAG, "Unexpected external file dir: " + file.getAbsolutePath());
            }
            else {
                String path = file.getAbsolutePath().substring(0, index);
                try {
                    path = new File(path).getCanonicalPath();
                }
                catch (IOException e) {
                    // Keep non-canonical path.
                }
                paths.add(path);
            }
        }
    }
    return paths.toArray(new String[paths.size()]);
}

 /**
 * Retrieve the application context.
 *
 * @return The (statically stored) application context
 */
public static Context getAppContext() {
    return Application.mApplication.getApplicationContext();
}

อ้างอิงถึงรหัสเต็ม

https://github.com/jeisfeld/Augendiagnose/blob/master/AugendiagnoseIdea/augendiagnoseLib/src/main/java/de/jeisfeld/augendiagnoselib/fragments/SettingsFragment.java#L521

และ

https://github.com/jeisfeld/Augendiagnose/blob/master/AugendiagnoseIdea/augendiagnoseLib/src/main/java/de/jeisfeld/augendiagnoselib/util/imagefile/FileUtil.java


1
คุณช่วยวางไว้ในโปรเจ็กต์ย่อขนาดที่จัดการเฉพาะการ์ด SD ได้ไหม นอกจากนี้คุณรู้หรือไม่ว่าฉันจะตรวจสอบได้อย่างไรว่าพื้นที่เก็บข้อมูลภายนอกทั้งหมดที่มีอยู่นั้นสามารถเข้าถึงได้หรือไม่เพื่อที่ฉันจะไม่ขออนุญาตโดยไม่มีอะไรเลย
นักพัฒนา Android

1
หวังว่าฉันจะโหวตให้มากกว่านี้ ฉันมาถึงครึ่งทางของโซลูชันนี้แล้วและพบว่าการนำทางเอกสารแย่มากจนฉันคิดว่าฉันทำผิด ดีที่ได้รับการยืนยันเกี่ยวกับเรื่องนี้ ขอบคุณ Google ...
Anthony

1
ใช่สำหรับการเขียนบน SD ภายนอกคุณไม่สามารถใช้วิธีการไฟล์ปกติได้อีกต่อไป ในทางกลับกันสำหรับ Android เวอร์ชันเก่าและสำหรับ SD หลัก File ยังคงเป็นแนวทางที่มีประสิทธิภาพสูงสุด ดังนั้นคุณควรใช้คลาสยูทิลิตี้ที่กำหนดเองสำหรับการเข้าถึงไฟล์
Jörg Eisfeld

15
@ JörgEisfeld: ฉันมีแอพที่ใช้File254 ครั้ง คุณนึกภาพออกไหม ?? Android กำลังกลายเป็นฝันร้ายสำหรับนักพัฒนาที่ไม่มีความเข้ากันได้แบบย้อนหลังโดยสิ้นเชิง ฉันยังไม่พบที่ใดที่พวกเขาอธิบายว่าทำไม Google ถึงตัดสินใจโง่ ๆ เกี่ยวกับพื้นที่จัดเก็บข้อมูลภายนอก บางคนอ้างว่า "ความปลอดภัย" แต่แน่นอนว่าเป็นเรื่องไร้สาระเนื่องจากแอปใด ๆ อาจทำให้ที่จัดเก็บข้อมูลภายในเสียหายได้ ฉันเดาว่าพยายามบังคับให้เราใช้บริการคลาวด์ของพวกเขา โชคดีที่การรูทช่วยแก้ปัญหาได้ ... อย่างน้อยสำหรับ Android <6 ....
Luis A. Florit

1
ตกลง. น่าอัศจรรย์หลังจากที่ฉันรีสตาร์ทโทรศัพท์มันใช้งานได้ :)
sancho21

0

มันเป็นเพียงคำตอบเสริม

หลังจากสร้างไฟล์ใหม่แล้วคุณอาจต้องบันทึกตำแหน่งของไฟล์ลงในฐานข้อมูลของคุณและอ่านในวันพรุ่งนี้ คุณสามารถอ่านดึงข้อมูลอีกครั้งโดยใช้วิธีนี้:

/**
 * Get {@link DocumentFile} object from SD card.
 * @param directory SD card ID followed by directory name, for example {@code 6881-2249:Download/Archive},
 *                 where ID for SD card is {@code 6881-2249}
 * @param fileName for example {@code intel_haxm.zip}
 * @return <code>null</code> if does not exist
 */
public static DocumentFile getExternalFile(Context context, String directory, String fileName){
    Uri uri = Uri.parse("content://com.android.externalstorage.documents/tree/" + directory);
    DocumentFile parent = DocumentFile.fromTreeUri(context, uri);
    return parent != null ? parent.findFile(fileName) : null;
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == SettingsFragment.REQUEST_CODE_STORAGE_ACCESS && resultCode == RESULT_OK) {
        int takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
        getContentResolver().takePersistableUriPermission(data.getData(), takeFlags);
        String sdcard = data.getDataString().replace("content://com.android.externalstorage.documents/tree/", "");
        try {
            sdcard = URLDecoder.decode(sdcard, "ISO-8859-1");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        // for example, sdcardId results "6312-2234"
        String sdcardId = sdcard.substring(0, sdcard.indexOf(':'));
        // save to preferences if you want to use it later
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
        preferences.edit().putString("sdcard", sdcardId).apply();
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.