ในอุปกรณ์ที่ใช้ 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_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(); }
แหล่งข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีจัดเก็บและเข้าถึงเอกสารและไฟล์อื่นๆ ได้จากแหล่งข้อมูลต่อไปนี้
ตัวอย่าง
- ActionOpenDocument พร้อมใช้งานใน GitHub
- ActionOpenDocumentTree พร้อมใช้งานใน GitHub