ในอุปกรณ์ที่ใช้ Android 4.4 (API ระดับ 19) ขึ้นไป แอปของคุณจะโต้ตอบกับผู้ให้บริการเอกสารได้ ซึ่งรวมถึงวอลุ่มพื้นที่เก็บข้อมูลภายนอกและพื้นที่เก็บข้อมูลบนระบบคลาวด์ โดยใช้ Storage Access Framework เฟรมเวิร์กนี้ช่วยให้ผู้ใช้โต้ตอบกับเครื่องมือเลือกของระบบ เพื่อเลือกผู้ให้บริการเอกสาร รวมถึงเลือกเอกสารและไฟล์อื่นๆ ที่เฉพาะเจาะจง เพื่อให้แอปของคุณสร้าง เปิด หรือแก้ไขได้
เนื่องจากผู้ใช้มีส่วนร่วมในการเลือกไฟล์หรือไดเรกทอรีที่แอปของคุณเข้าถึงได้ กลไกนี้จึงไม่จำเป็นต้องมีสิทธิ์ของระบบ และช่วยเพิ่มการควบคุมและความเป็นส่วนตัวของผู้ใช้ นอกจากนี้ ไฟล์เหล่านี้ซึ่งจัดเก็บอยู่นอกไดเรกทอรีเฉพาะของแอปและนอกที่เก็บสื่อจะยังคงอยู่ในอุปกรณ์หลังจากที่ถอนการติดตั้งแอปแล้ว
การใช้เฟรมเวิร์กมีขั้นตอนต่อไปนี้
- แอปเรียกใช้ Intent ที่มีการดำเนินการที่เกี่ยวข้องกับพื้นที่เก็บข้อมูล การดำเนินการนี้ สอดคล้องกับกรณีการใช้งานที่เฉพาะเจาะจงซึ่งเฟรมเวิร์ก ทำให้พร้อมใช้งาน
- ผู้ใช้จะเห็นตัวเลือกของระบบ ซึ่งช่วยให้ผู้ใช้เรียกดูผู้ให้บริการเอกสาร และเลือกตำแหน่งหรือเอกสารที่จะดำเนินการที่เกี่ยวข้องกับพื้นที่เก็บข้อมูลได้
- แอปจะได้รับสิทธิ์เข้าถึงแบบอ่านและเขียนไปยัง URI ที่แสดงถึงตำแหน่งหรือเอกสารที่ผู้ใช้เลือก เมื่อใช้ URI นี้ แอปจะดำเนินการกับ ตำแหน่งที่เลือกได้
หากต้องการรองรับการเข้าถึงไฟล์สื่อในอุปกรณ์ที่ใช้ Android 9 (API ระดับ 28) หรือต่ำกว่า ให้ประกาศสิทธิ์
READ_EXTERNAL_STORAGE
และตั้งค่า maxSdkVersion เป็น 28
คู่มือนี้อธิบาย Use Case ต่างๆ ที่เฟรมเวิร์กรองรับสำหรับการ ทำงานกับไฟล์และเอกสารอื่นๆ นอกจากนี้ยังอธิบายวิธีดำเนินการ ในตำแหน่งที่ผู้ใช้เลือกด้วย
กรณีการใช้งานสำหรับการเข้าถึงเอกสารและไฟล์อื่นๆ
เฟรมเวิร์กการเข้าถึงพื้นที่เก็บข้อมูลรองรับกรณีการใช้งานต่อไปนี้สำหรับการเข้าถึงไฟล์และเอกสารอื่นๆ
- สร้างไฟล์ใหม่
- การดำเนินการตาม Intent ของ
ACTION_CREATE_DOCUMENTช่วยให้ผู้ใช้บันทึกไฟล์ในตำแหน่งที่เฉพาะเจาะจงได้ - เปิดเอกสารหรือไฟล์
- การดำเนินการตาม Intent ของ
ACTION_OPEN_DOCUMENTช่วยให้ผู้ใช้เลือกเอกสารหรือไฟล์ที่ต้องการเปิดได้ - ให้สิทธิ์เข้าถึงเนื้อหาของไดเรกทอรี
- การดำเนินการของ
ACTION_OPEN_DOCUMENT_TREEIntent ซึ่งพร้อมใช้งานใน 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 ของ MediaStore จะช่วยให้คุณเข้าถึงไฟล์สื่อจากพื้นที่เก็บข้อมูลที่ใช้ร่วมกันได้ง่ายขึ้น
เมธอด getMediaUri() รองรับ URI ของ ExternalStorageProvider ใน Android 12 (API ระดับ 31) ขึ้นไป เมธอดนี้ยังรองรับ URI ของ MediaDocumentsProvider ด้วย
เปิดไฟล์เสมือน
ใน Android 7.0 (API ระดับ 25) ขึ้นไป แอปจะใช้ไฟล์เสมือน
ที่ Storage Access Framework จัดเตรียมไว้ให้ได้ แม้ว่าไฟล์เสมือนจะไม่มีการแสดงผลแบบไบนารี แต่แอปก็สามารถเปิดเนื้อหาของไฟล์ได้โดยการบังคับให้เป็นไฟล์ประเภทอื่น หรือโดยการดูไฟล์เหล่านั้นโดยใช้การดำเนินการของ 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(); }
แหล่งข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีจัดเก็บและเข้าถึงเอกสารและไฟล์อื่นๆ ได้จากแหล่งข้อมูลต่อไปนี้
ตัวอย่าง
- ActionOpenDocument พร้อมใช้งานใน GitHub
- ActionOpenDocumentTree พร้อมใช้งานใน GitHub