เข้าถึงเอกสารและไฟล์อื่นๆ จากพื้นที่เก็บข้อมูลที่ใช้ร่วมกัน

ในอุปกรณ์ที่ใช้ Android 4.4 (API ระดับ 19) ขึ้นไป แอปของคุณจะโต้ตอบกับผู้ให้บริการเอกสาร รวมถึงวอลุ่มพื้นที่เก็บข้อมูลภายนอกและพื้นที่เก็บข้อมูลบนระบบคลาวด์ได้โดยใช้ Storage Access Framework เฟรมเวิร์กนี้ช่วยให้ผู้ใช้โต้ตอบกับเครื่องมือเลือกของระบบ เพื่อเลือกผู้ให้บริการเอกสาร รวมถึงเลือกเอกสารและไฟล์อื่นๆ ที่เฉพาะเจาะจง เพื่อให้แอปของคุณสร้าง เปิด หรือแก้ไขได้

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

การใช้เฟรมเวิร์กมีขั้นตอนต่อไปนี้

  1. แอปเรียกใช้ Intent ที่มีการดำเนินการที่เกี่ยวข้องกับพื้นที่เก็บข้อมูล การดำเนินการนี้ สอดคล้องกับกรณีการใช้งานที่เฉพาะเจาะจงซึ่งเฟรมเวิร์ก ทำให้พร้อมใช้งาน
  2. ผู้ใช้จะเห็นตัวเลือกของระบบ ซึ่งช่วยให้ผู้ใช้เรียกดูผู้ให้บริการเอกสาร และเลือกตำแหน่งหรือเอกสารที่จะดำเนินการที่เกี่ยวข้องกับพื้นที่เก็บข้อมูลได้
  3. แอปจะได้รับสิทธิ์อ่านและเขียนไปยัง URI ที่แสดงถึงตำแหน่งหรือเอกสารที่ผู้ใช้เลือก เมื่อใช้ URI นี้ แอปจะดำเนินการกับ ตำแหน่งที่เลือกได้

หากต้องการรองรับการเข้าถึงไฟล์สื่อในอุปกรณ์ที่ใช้ Android 9 (API ระดับ 28) หรือต่ำกว่า ให้ประกาศสิทธิ์ READ_EXTERNAL_STORAGE และตั้งค่า maxSdkVersion เป็น 28

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

กรณีการใช้งานสำหรับการเข้าถึงเอกสารและไฟล์อื่นๆ

เฟรมเวิร์กการเข้าถึงพื้นที่เก็บข้อมูลรองรับกรณีการใช้งานต่อไปนี้สำหรับการเข้าถึงไฟล์และเอกสารอื่นๆ

สร้างไฟล์ใหม่
การดำเนินการตาม Intent ของ ACTION_CREATE_DOCUMENT ช่วยให้ผู้ใช้บันทึกไฟล์ในตำแหน่งที่เฉพาะเจาะจงได้
เปิดเอกสารหรือไฟล์
การดำเนินการตาม Intent ของ ACTION_OPEN_DOCUMENT ช่วยให้ผู้ใช้เลือกเอกสารหรือไฟล์ที่ต้องการเปิดได้
ให้สิทธิ์เข้าถึงเนื้อหาของไดเรกทอรี
การดำเนินการของ ACTION_OPEN_DOCUMENT_TREE Intent ซึ่งพร้อมใช้งานใน Android 5.0 (API ระดับ 21) ขึ้นไป จะช่วยให้ผู้ใช้ เลือกไดเรกทอรีที่เฉพาะเจาะจงได้ ซึ่งจะให้สิทธิ์แอปของคุณเข้าถึงไฟล์และ ไดเรกทอรีย่อยทั้งหมดภายในไดเรกทอรีนั้น

ส่วนต่อไปนี้จะให้คำแนะนำเกี่ยวกับวิธีกำหนดค่าแต่ละกรณีการใช้งาน

สร้างไฟล์ใหม่

ใช้การดำเนินการของ Intent ACTION_CREATE_DOCUMENT เพื่อโหลดเครื่องมือเลือกไฟล์ของระบบและอนุญาตให้ผู้ใช้เลือก ตำแหน่งที่จะเขียนเนื้อหาของไฟล์ กระบวนการนี้คล้ายกับกระบวนการ ที่ใช้ในกล่องโต้ตอบ "บันทึกเป็น" ที่ระบบปฏิบัติการอื่นๆ ใช้

หมายเหตุ: ACTION_CREATE_DOCUMENT ไม่สามารถเขียนทับไฟล์ที่มีอยู่ หากแอปพยายามบันทึกไฟล์ที่มีชื่อเดียวกัน ระบบจะ ต่อท้ายชื่อไฟล์ด้วยตัวเลขในวงเล็บ

เช่น หากแอปพยายามบันทึกไฟล์ชื่อ confirmation.pdf ในไดเรกทอรีที่มีไฟล์ชื่อเดียวกันอยู่แล้ว ระบบจะบันทึกไฟล์ใหม่โดยใช้ชื่อ confirmation(1).pdf

เมื่อกำหนดค่า Intent ให้ระบุชื่อและประเภท MIME ของไฟล์ และระบุ URI ของไฟล์หรือไดเรกทอรีที่ตัวเลือกไฟล์ควรแสดงเมื่อโหลดครั้งแรกโดยใช้ส่วนเพิ่มเติมของ Intent EXTRA_INITIAL_URI (ไม่บังคับ)

ข้อมูลโค้ดต่อไปนี้แสดงวิธีสร้างและเรียกใช้ Intent สำหรับ สร้างไฟล์

Kotlin

// Request code for creating a PDF document.
const val CREATE_FILE = 1

private fun createFile(pickerInitialUri: Uri) {
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "application/pdf"
        putExtra(Intent.EXTRA_TITLE, "invoice.pdf")

        // Optionally, specify a URI for the directory that should be opened in
        // the system file picker before your app creates the document.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }
    startActivityForResult(intent, CREATE_FILE)
}

Java

// Request code for creating a PDF document.
private static final int CREATE_FILE = 1;

private void createFile(Uri pickerInitialUri) {
    Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("application/pdf");
    intent.putExtra(Intent.EXTRA_TITLE, "invoice.pdf");

    // Optionally, specify a URI for the directory that should be opened in
    // the system file picker when your app creates the document.
    intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);

    startActivityForResult(intent, CREATE_FILE);
}

เปิดไฟล์

แอปอาจใช้เอกสารเป็นหน่วยจัดเก็บที่ผู้ใช้ป้อนข้อมูล ซึ่งอาจต้องการแชร์กับเพื่อนร่วมงานหรือนำเข้าไปยังเอกสารอื่นๆ ตัวอย่าง เช่น ผู้ใช้เปิดเอกสารเพื่อการทำงานหรือเปิดหนังสือที่ บันทึกเป็นไฟล์ EPUB

ในกรณีเหล่านี้ ให้ผู้ใช้เลือกไฟล์ที่จะเปิดโดยเรียกใช้ ACTION_OPEN_DOCUMENT Intent ซึ่งจะเปิดแอปตัวเลือกไฟล์ของระบบ หากต้องการแสดงเฉพาะประเภท ไฟล์ที่แอปของคุณรองรับ ให้ระบุประเภท MIME นอกจากนี้ คุณยังเลือก ระบุ URI ของไฟล์ที่เครื่องมือเลือกไฟล์ควรแสดงเมื่อโหลดเป็นครั้งแรกได้ โดยใช้ EXTRA_INITIAL_URI ส่วนเพิ่มเติมของ Intent

ข้อมูลโค้ดต่อไปนี้แสดงวิธีสร้างและเรียกใช้ Intent สำหรับการเปิดเอกสาร PDF

Kotlin

// Request code for selecting a PDF document.
const val PICK_PDF_FILE = 2

fun openFile(pickerInitialUri: Uri) {
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "application/pdf"

        // Optionally, specify a URI for the file that should appear in the
        // system file picker when it loads.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }

    startActivityForResult(intent, PICK_PDF_FILE)
}

Java

// Request code for selecting a PDF document.
private static final int PICK_PDF_FILE = 2;

private void openFile(Uri pickerInitialUri) {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.setType("application/pdf");

    // Optionally, specify a URI for the file that should appear in the
    // system file picker when it loads.
    intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri);

    startActivityForResult(intent, PICK_PDF_FILE);
}

การจำกัดการเข้าถึง

ใน Android 11 (API ระดับ 30) ขึ้นไป คุณจะใช้ ACTION_OPEN_DOCUMENT การดำเนินการ Intent เพื่อขอให้ผู้ใช้เลือกไฟล์แต่ละไฟล์ จากไดเรกทอรีต่อไปนี้ไม่ได้

  • ไดเรกทอรี Android/data/ และไดเรกทอรีย่อยทั้งหมด
  • ไดเรกทอรี Android/obb/ และไดเรกทอรีย่อยทั้งหมด

ให้สิทธิ์เข้าถึงเนื้อหาของไดเรกทอรี

โดยปกติแล้ว แอปการจัดการไฟล์และการสร้างสื่อจะจัดการกลุ่มไฟล์ใน ลำดับชั้นของไดเรกทอรี หากต้องการมอบความสามารถนี้ในแอป ให้ใช้การดำเนินการ Intent ของ ACTION_OPEN_DOCUMENT_TREE ซึ่งจะช่วยให้ผู้ใช้ให้สิทธิ์เข้าถึงทั้งโครงสร้างไดเรกทอรี ได้ โดยมีข้อยกเว้นบางอย่างตั้งแต่ Android 11 (API ระดับ 30) เป็นต้นไป จากนั้นแอปของคุณจะเข้าถึงไฟล์ใดก็ได้ในไดเรกทอรีที่เลือกและไดเรกทอรีย่อยของไดเรกทอรีนั้น

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

คุณเลือกที่จะระบุ URI ของไดเรกทอรีที่ตัวเลือกไฟล์ควร แสดงเมื่อโหลดครั้งแรกได้โดยใช้ EXTRA_INITIAL_URI ส่วนเพิ่มเติมของ Intent

ข้อมูลโค้ดต่อไปนี้แสดงวิธีสร้างและเรียกใช้ Intent สำหรับการเปิดไดเรกทอรี

Kotlin

fun openDirectory(pickerInitialUri: Uri) {
    // Choose a directory using the system's file picker.
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
        // Optionally, specify a URI for the directory that should be opened in
        // the system file picker when it loads.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }

    startActivityForResult(intent, your-request-code)
}

Java

public void openDirectory(Uri uriToLoad) {
    // Choose a directory using the system's file picker.
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);

    // Optionally, specify a URI for the directory that should be opened in
    // the system file picker when it loads.
    intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uriToLoad);

    startActivityForResult(intent, your-request-code);
}

การจำกัดการเข้าถึง

ใน Android 11 (API ระดับ 30) ขึ้นไป คุณจะใช้ ACTION_OPEN_DOCUMENT_TREE การดำเนินการของ Intent เพื่อขอสิทธิ์เข้าถึงไดเรกทอรีต่อไปนี้ไม่ได้

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

นอกจากนี้ ใน Android 11 (API ระดับ 30) ขึ้นไป คุณจะใช้ ACTION_OPEN_DOCUMENT_TREE การดำเนินการของ Intent เพื่อขอให้ผู้ใช้เลือก ไฟล์แต่ละไฟล์จากไดเรกทอรีต่อไปนี้ไม่ได้

  • ไดเรกทอรี Android/data/ และไดเรกทอรีย่อยทั้งหมด
  • ไดเรกทอรี Android/obb/ และไดเรกทอรีย่อยทั้งหมด

ดำเนินการในตำแหน่งที่เลือก

หลังจากที่ผู้ใช้เลือกไฟล์หรือไดเรกทอรีโดยใช้เครื่องมือเลือกไฟล์ของระบบแล้ว คุณจะดึง URI ของรายการที่เลือกได้โดยใช้โค้ดต่อไปนี้ใน onActivityResult():

Kotlin

override fun onActivityResult(
        requestCode: Int, resultCode: Int, resultData: Intent?) {
    if (requestCode == your-request-code
            && resultCode == Activity.RESULT_OK) {
        // The result data contains a URI for the document or directory that
        // the user selected.
        resultData?.data?.also { uri ->
            // Perform operations on the document using its URI.
        }
    }
}

Java

@Override
public void onActivityResult(int requestCode, int resultCode,
        Intent resultData) {
    if (requestCode == your-request-code
            && resultCode == Activity.RESULT_OK) {
        // The result data contains a URI for the document or directory that
        // the user selected.
        Uri uri = null;
        if (resultData != null) {
            uri = resultData.getData();
            // Perform operations on the document using its URI.
        }
    }
}

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

ส่วนต่อไปนี้จะแสดงวิธีดำเนินการกับไฟล์ที่ผู้ใช้เลือก

กำหนดการดำเนินการที่ผู้ให้บริการรองรับ

ผู้ให้บริการเนื้อหาแต่ละรายอนุญาตให้ดำเนินการต่างๆ กับเอกสารได้แตกต่างกัน เช่น การคัดลอกเอกสารหรือการดูภาพขนาดย่อของเอกสาร หากต้องการ ดูว่าผู้ให้บริการรายใดรองรับการดำเนินการใด ให้ตรวจสอบค่าของ Document.COLUMN_FLAGS จากนั้น UI ของแอปจะแสดงเฉพาะตัวเลือกที่ผู้ให้บริการรองรับ

คงสิทธิ์ไว้

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

หากต้องการรักษาสิทธิ์เข้าถึงไฟล์เมื่อรีสตาร์ทอุปกรณ์และสร้างประสบการณ์การใช้งานที่ดีขึ้น แอปของคุณสามารถ "รับ" สิทธิ์ URI ที่คงอยู่ได้ที่ระบบมอบให้ ดังที่แสดงในข้อมูลโค้ดต่อไปนี้

Kotlin

val contentResolver = applicationContext.contentResolver

val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or
        Intent.FLAG_GRANT_WRITE_URI_PERMISSION
// Check for the freshest data.
contentResolver.takePersistableUriPermission(uri, takeFlags)

Java

final int takeFlags = intent.getFlags()
            & (Intent.FLAG_GRANT_READ_URI_PERMISSION
            | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// Check for the freshest data.
getContentResolver().takePersistableUriPermission(uri, takeFlags);

ตรวจสอบข้อมูลเมตาของเอกสาร

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

Kotlin

val contentResolver = applicationContext.contentResolver

fun dumpImageMetaData(uri: Uri) {

    // The query, because it only applies to a single document, returns only
    // one row. There's no need to filter, sort, or select fields,
    // because we want all fields for one document.
    val cursor: Cursor? = contentResolver.query(
            uri, null, null, null, null, null)

    cursor?.use {
        // moveToFirst() returns false if the cursor has 0 rows. Very handy for
        // "if there's anything to look at, look at it" conditionals.
        if (it.moveToFirst()) {

            // Note it's called "Display Name". This is
            // provider-specific, and might not necessarily be the file name.
            val displayName: String =
                    it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME))
            Log.i(TAG, "Display Name: $displayName")

            val sizeIndex: Int = it.getColumnIndex(OpenableColumns.SIZE)
            // If the size is unknown, the value stored is null. But because an
            // int can't be null, the behavior is implementation-specific,
            // and unpredictable. So as
            // a rule, check if it's null before assigning to an int. This will
            // happen often: The storage API allows for remote files, whose
            // size might not be locally known.
            val size: String = if (!it.isNull(sizeIndex)) {
                // Technically the column stores an int, but cursor.getString()
                // will do the conversion automatically.
                it.getString(sizeIndex)
            } else {
                "Unknown"
            }
            Log.i(TAG, "Size: $size")
        }
    }
}

Java

public void dumpImageMetaData(Uri uri) {

    // The query, because it only applies to a single document, returns only
    // one row. There's no need to filter, sort, or select fields,
    // because we want all fields for one document.
    Cursor cursor = getActivity().getContentResolver()
            .query(uri, null, null, null, null, null);

    try {
        // moveToFirst() returns false if the cursor has 0 rows. Very handy for
        // "if there's anything to look at, look at it" conditionals.
        if (cursor != null && cursor.moveToFirst()) {

            // Note it's called "Display Name". This is
            // provider-specific, and might not necessarily be the file name.
            String displayName = cursor.getString(
                    cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
            Log.i(TAG, "Display Name: " + displayName);

            int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
            // If the size is unknown, the value stored is null. But because an
            // int can't be null, the behavior is implementation-specific,
            // and unpredictable. So as
            // a rule, check if it's null before assigning to an int. This will
            // happen often: The storage API allows for remote files, whose
            // size might not be locally known.
            String size = null;
            if (!cursor.isNull(sizeIndex)) {
                // Technically the column stores an int, but cursor.getString()
                // will do the conversion automatically.
                size = cursor.getString(sizeIndex);
            } else {
                size = "Unknown";
            }
            Log.i(TAG, "Size: " + size);
        }
    } finally {
        cursor.close();
    }
}

เปิดเอกสาร

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

บิตแมป

ข้อมูลโค้ดต่อไปนี้แสดงวิธีเปิดไฟล์ Bitmapเมื่อระบุ URI ของไฟล์

Kotlin

val contentResolver = applicationContext.contentResolver

@Throws(IOException::class)
private fun getBitmapFromUri(uri: Uri): Bitmap {
    val parcelFileDescriptor: ParcelFileDescriptor =
            contentResolver.openFileDescriptor(uri, "r")
    val fileDescriptor: FileDescriptor = parcelFileDescriptor.fileDescriptor
    val image: Bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor)
    parcelFileDescriptor.close()
    return image
}

Java

private Bitmap getBitmapFromUri(Uri uri) throws IOException {
    ParcelFileDescriptor parcelFileDescriptor =
            getContentResolver().openFileDescriptor(uri, "r");
    FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
    Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
    parcelFileDescriptor.close();
    return image;
}

หลังจากเปิดบิตแมปแล้ว คุณจะแสดงบิตแมปใน ImageView ได้

สตรีมอินพุต

ข้อมูลโค้ดต่อไปนี้แสดงวิธีเปิดออบเจ็กต์ InputStream เมื่อระบุ URI ในข้อมูลโค้ดนี้ ระบบจะอ่านบรรทัดของไฟล์เป็นสตริง

Kotlin

val contentResolver = applicationContext.contentResolver

@Throws(IOException::class)
private fun readTextFromUri(uri: Uri): String {
    val stringBuilder = StringBuilder()
    contentResolver.openInputStream(uri)?.use { inputStream ->
        BufferedReader(InputStreamReader(inputStream)).use { reader ->
            var line: String? = reader.readLine()
            while (line != null) {
                stringBuilder.append(line)
                line = reader.readLine()
            }
        }
    }
    return stringBuilder.toString()
}

Java

private String readTextFromUri(Uri uri) throws IOException {
    StringBuilder stringBuilder = new StringBuilder();
    try (InputStream inputStream =
            getContentResolver().openInputStream(uri);
            BufferedReader reader = new BufferedReader(
            new InputStreamReader(Objects.requireNonNull(inputStream)))) {
        String line;
        while ((line = reader.readLine()) != null) {
            stringBuilder.append(line);
        }
    }
    return stringBuilder.toString();
}

แก้ไขเอกสาร

คุณสามารถใช้ Storage Access Framework เพื่อแก้ไขเอกสารข้อความได้โดยตรง

ข้อมูลโค้ดต่อไปนี้จะเขียนทับเนื้อหาของเอกสารที่แสดงโดย URI ที่ระบุ

Kotlin

val contentResolver = applicationContext.contentResolver

private fun alterDocument(uri: Uri) {
    try {
        contentResolver.openFileDescriptor(uri, "w")?.use {
            FileOutputStream(it.fileDescriptor).use {
                it.write(
                    ("Overwritten at ${System.currentTimeMillis()}\n")
                        .toByteArray()
                )
            }
        }
    } catch (e: FileNotFoundException) {
        e.printStackTrace()
    } catch (e: IOException) {
        e.printStackTrace()
    }
}

Java

private void alterDocument(Uri uri) {
    try {
        ParcelFileDescriptor pfd = getActivity().getContentResolver().
                openFileDescriptor(uri, "w");
        FileOutputStream fileOutputStream =
                new FileOutputStream(pfd.getFileDescriptor());
        fileOutputStream.write(("Overwritten at " + System.currentTimeMillis() +
                "\n").getBytes());
        // Let the document provider know you're done by closing the stream.
        fileOutputStream.close();
        pfd.close();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

ลบเอกสาร

หากมี URI ของเอกสารและเอกสาร Document.COLUMN_FLAGS มี SUPPORTS_DELETE คุณจะลบเอกสารได้ เช่น

Kotlin

DocumentsContract.deleteDocument(applicationContext.contentResolver, uri)

Java

DocumentsContract.deleteDocument(applicationContext.contentResolver, uri);

เรียก URI ของสื่อที่เทียบเท่า

เมธอด getMediaUri() จะระบุ URI ของที่เก็บสื่อที่เทียบเท่ากับ URI ของผู้ให้บริการเอกสารที่ระบุ URI ทั้ง 2 รายการอ้างอิงถึงรายการพื้นฐานเดียวกัน การใช้ URI ของที่เก็บสื่อจะช่วยให้คุณเข้าถึงไฟล์สื่อจากพื้นที่เก็บข้อมูลที่ใช้ร่วมกันได้ง่ายขึ้น

เมธอด getMediaUri() รองรับ URI ของ ExternalStorageProvider ใน Android 12 (API ระดับ 31) ขึ้นไป เมธอดนี้ยังรองรับ URI ของ MediaDocumentsProvider ด้วย

เปิดไฟล์เสมือน

ใน Android 7.0 (API ระดับ 25) ขึ้นไป แอปของคุณจะใช้ไฟล์เสมือน ที่เฟรมเวิร์กการเข้าถึงพื้นที่เก็บข้อมูลจัดเตรียมไว้ให้ได้ แม้ว่าไฟล์เสมือนจะไม่มีการแสดงผลแบบไบนารี แต่แอปก็สามารถเปิดเนื้อหาของไฟล์ได้โดยการบังคับให้เป็นไฟล์ประเภทอื่น หรือโดยการดูไฟล์เหล่านั้นโดยใช้การดำเนินการ Intent ACTION_VIEW

หากต้องการเปิดไฟล์เสมือน แอปไคลเอ็นต์ต้องมีตรรกะพิเศษเพื่อจัดการไฟล์เหล่านั้น หากต้องการรับการแสดงไบต์ของไฟล์ เช่น เพื่อดูตัวอย่างไฟล์ คุณต้องขอประเภท MIME อื่นจากผู้ให้บริการเอกสาร

หลังจากที่ผู้ใช้เลือกแล้ว ให้ใช้ URI ในข้อมูลผลลัพธ์เพื่อพิจารณา ว่าไฟล์เป็นไฟล์เสมือนหรือไม่ ดังที่แสดงในข้อมูลโค้ดต่อไปนี้

Kotlin

private fun isVirtualFile(uri: Uri): Boolean {
    if (!DocumentsContract.isDocumentUri(this, uri)) {
        return false
    }

    val cursor: Cursor? = contentResolver.query(
            uri,
            arrayOf(DocumentsContract.Document.COLUMN_FLAGS),
            null,
            null,
            null
    )

    val flags: Int = cursor?.use {
        if (cursor.moveToFirst()) {
            cursor.getInt(0)
        } else {
            0
        }
    } ?: 0

    return flags and DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT != 0
}

Java

private boolean isVirtualFile(Uri uri) {
    if (!DocumentsContract.isDocumentUri(this, uri)) {
        return false;
    }

    Cursor cursor = getContentResolver().query(
        uri,
        new String[] { DocumentsContract.Document.COLUMN_FLAGS },
        null, null, null);

    int flags = 0;
    if (cursor.moveToFirst()) {
        flags = cursor.getInt(0);
    }
    cursor.close();

    return (flags & DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT) != 0;
}

หลังจากยืนยันว่าเอกสารเป็นไฟล์เสมือนแล้ว คุณจะบังคับให้ไฟล์เป็นประเภท MIME อื่นได้ เช่น "image/png" ข้อมูลโค้ดต่อไปนี้ แสดงวิธีตรวจสอบว่าไฟล์เสมือนแสดงเป็นรูปภาพได้หรือไม่ หากแสดงได้ ก็จะรับสตรีมอินพุตจากไฟล์เสมือน

Kotlin

@Throws(IOException::class)
private fun getInputStreamForVirtualFile(
        uri: Uri, mimeTypeFilter: String): InputStream {

    val openableMimeTypes: Array<String>? =
            contentResolver.getStreamTypes(uri, mimeTypeFilter)

    return if (openableMimeTypes?.isNotEmpty() == true) {
        contentResolver
                .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null)
                .createInputStream()
    } else {
        throw FileNotFoundException()
    }
}

Java

private InputStream getInputStreamForVirtualFile(Uri uri, String mimeTypeFilter)
    throws IOException {

    ContentResolver resolver = getContentResolver();

    String[] openableMimeTypes = resolver.getStreamTypes(uri, mimeTypeFilter);

    if (openableMimeTypes == null ||
        openableMimeTypes.length < 1) {
        throw new FileNotFoundException();
    }

    return resolver
        .openTypedAssetFileDescriptor(uri, openableMimeTypes[0], null)
        .createInputStream();
}

แหล่งข้อมูลเพิ่มเติม

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

ตัวอย่าง

วิดีโอ