ในอุปกรณ์ที่ใช้ Android 4.4 (ระดับ API 19) ขึ้นไป แอปของคุณสามารถโต้ตอบกับผู้ให้บริการเอกสาร รวมถึงวอลุ่มพื้นที่จัดเก็บข้อมูลภายนอกและพื้นที่เก็บข้อมูลในระบบคลาวด์ โดยใช้เฟรมเวิร์กการเข้าถึงพื้นที่เก็บข้อมูล เฟรมเวิร์กนี้ช่วยให้ผู้ใช้โต้ตอบกับเครื่องมือเลือกของระบบเพื่อเลือกผู้ให้บริการเอกสาร รวมถึงเลือกเอกสารและไฟล์อื่นๆ ที่เฉพาะเจาะจงเพื่อให้แอปสร้าง เปิด หรือแก้ไขได้
เนื่องจากผู้ใช้มีส่วนร่วมในการเลือกไฟล์หรือไดเรกทอรีที่แอปของคุณเข้าถึงได้ กลไกนี้จึงไม่จำเป็นต้องใช้สิทธิ์ของระบบ และการควบคุมและความเป็นส่วนตัวของผู้ใช้จะได้รับการปรับปรุง นอกจากนี้ ไฟล์เหล่านี้ซึ่งจัดเก็บไว้นอกไดเรกทอรีเฉพาะแอปและนอก Media Store จะยังคงอยู่ในอุปกรณ์หลังจากถอนการติดตั้งแอปแล้ว
การใช้เฟรมเวิร์กประกอบด้วยขั้นตอนต่อไปนี้
- แอปเรียกใช้ Intent ที่มีการดำเนินการที่เกี่ยวข้องกับพื้นที่เก็บข้อมูล การดำเนินการนี้สอดคล้องกับกรณีการใช้งานที่เฉพาะเจาะจงซึ่งเฟรมเวิร์กมีให้ใช้งาน
- ผู้ใช้จะเห็นเครื่องมือเลือกของระบบ ซึ่งช่วยให้เรียกดูผู้ให้บริการเอกสารและเลือกตำแหน่งหรือเอกสารที่เกิดการดำเนินการที่เกี่ยวข้องกับพื้นที่เก็บข้อมูลได้
- แอปจะได้รับสิทธิ์อ่านและเขียนใน URI ที่แสดงถึงตำแหน่งหรือเอกสารที่ผู้ใช้เลือก เมื่อใช้ URI นี้ แอปจะดำเนินการกับตำแหน่งที่เลือกได้
หากต้องการรองรับการเข้าถึงไฟล์สื่อในอุปกรณ์ที่ใช้ Android 9 (ระดับ API 28) หรือต่ำกว่า ให้ประกาศสิทธิ์ READ_EXTERNAL_STORAGE และตั้งค่า maxSdkVersion เป็น 28
คู่มือนี้อธิบาย Use Case ต่างๆ ที่เฟรมเวิร์กรองรับสำหรับการทำงานกับไฟล์และเอกสารอื่นๆ นอกจากนี้ยังอธิบายวิธีดำเนินการในตำแหน่งที่ผู้ใช้เลือกด้วย
กรณีการใช้งานสำหรับการเข้าถึงเอกสารและไฟล์อื่นๆ
เฟรมเวิร์กการเข้าถึงพื้นที่เก็บข้อมูลรองรับกรณีการใช้งานต่อไปนี้สำหรับการเข้าถึงไฟล์และเอกสารอื่นๆ
- สร้างไฟล์ใหม่
- การดำเนินการตาม Intent
ACTION_CREATE_DOCUMENTช่วยให้ผู้ใช้บันทึกไฟล์ในตำแหน่งที่เฉพาะเจาะจงได้ - เปิดเอกสารหรือไฟล์
- การดำเนินการตาม Intent
ACTION_OPEN_DOCUMENTช่วยให้ผู้ใช้เลือกเอกสารหรือไฟล์ที่ต้องการเปิดได้ - ให้สิทธิ์เข้าถึงเนื้อหาของไดเรกทอรี
- การดำเนินการตาม Intent
ACTION_OPEN_DOCUMENT_TREEซึ่งพร้อมใช้งานใน Android 5.0 (ระดับ API 21) ขึ้นไป ช่วยให้ผู้ใช้เลือกไดเรกทอรีที่เฉพาะเจาะจงได้ ซึ่งจะให้สิทธิ์แอปของคุณเข้าถึงไฟล์และไดเรกทอรีย่อยทั้งหมดภายในไดเรกทอรีนั้น
ส่วนต่อไปนี้จะให้คำแนะนำเกี่ยวกับวิธีกำหนดค่า Use Case แต่ละรายการ
สร้างไฟล์ใหม่
ใช้การดำเนินการตาม Intent ACTION_CREATE_DOCUMENT เพื่อโหลดเครื่องมือเลือกไฟล์ของระบบและอนุญาตให้ผู้ใช้เลือกตำแหน่งที่จะเขียนเนื้อหาของไฟล์ กระบวนการนี้คล้ายกับกระบวนการที่ใช้ในกล่องโต้ตอบ "บันทึกเป็น" ที่ระบบปฏิบัติการอื่นๆ ใช้
หมายเหตุ: ACTION_CREATE_DOCUMENT ไม่สามารถเขียนทับไฟล์ที่มีอยู่ได้ หากแอปพยายามบันทึกไฟล์ที่มีชื่อเดียวกัน ระบบจะเพิ่มตัวเลขในวงเล็บต่อท้ายชื่อไฟล์
ตัวอย่างเช่น หากแอปพยายามบันทึกไฟล์ชื่อ confirmation.pdf ในไดเรกทอรีที่มีไฟล์ชื่อนั้นอยู่แล้ว ระบบจะบันทึกไฟล์ใหม่โดยใช้ชื่อ confirmation(1).pdf
เมื่อกำหนดค่า Intent ให้ระบุชื่อและประเภท MIME ของไฟล์ และระบุ URI ของไฟล์หรือไดเรกทอรีที่เครื่องมือเลือกไฟล์ควรแสดงเมื่อโหลดครั้งแรกโดยใช้ Intent Extra 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
ในกรณีเหล่านี้ ให้อนุญาตผู้ใช้เลือกไฟล์ที่จะเปิดโดยเรียกใช้ Intent ACTION_OPEN_DOCUMENT ซึ่งจะเปิดแอปเครื่องมือเลือกไฟล์ของระบบ หากต้องการแสดงเฉพาะประเภทไฟล์ที่แอปรองรับ ให้ระบุประเภท MIME นอกจากนี้ คุณยังเลือกที่จะระบุ URI ของไฟล์ที่เครื่องมือเลือกไฟล์ควรแสดงเมื่อโหลดครั้งแรกได้โดยใช้ Intent Extra EXTRA_INITIAL_URI
ข้อมูลโค้ดต่อไปนี้แสดงวิธีสร้างและเรียกใช้ 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) ขึ้นไป คุณไม่สามารถใช้การดำเนินการตาม Intent ACTION_OPEN_DOCUMENT เพื่อขอให้ผู้ใช้เลือกไฟล์แต่ละไฟล์จากไดเรกทอรีต่อไปนี้
- ไดเรกทอรี
Android/data/และไดเรกทอรีย่อยทั้งหมด - ไดเรกทอรี
Android/obb/และไดเรกทอรีย่อยทั้งหมด
ให้สิทธิ์เข้าถึงเนื้อหาของไดเรกทอรี
โดยปกติแล้ว แอปการจัดการไฟล์และการสร้างสื่อจะจัดการกลุ่มไฟล์ในลำดับชั้นของไดเรกทอรี หากต้องการให้แอปมีความสามารถนี้ ให้ใช้การดำเนินการตาม Intent ACTION_OPEN_DOCUMENT_TREE ซึ่งช่วยให้ผู้ใช้ให้สิทธิ์เข้าถึงโครงสร้างไดเรกทอรีทั้งหมดได้ โดยมีข้อยกเว้นบางประการตั้งแต่ Android 11 (ระดับ API 30) เป็นต้นไป จากนั้นแอปจะเข้าถึงไฟล์ในไดเรกทอรีที่เลือกและไดเรกทอรีย่อยของไดเรกทอรีนั้นได้
เมื่อใช้ ACTION_OPEN_DOCUMENT_TREE แอปของคุณจะเข้าถึงได้เฉพาะไฟล์ในไดเรกทอรีที่ผู้ใช้เลือกเท่านั้น คุณไม่มีสิทธิ์เข้าถึงไฟล์ของแอปอื่นๆ ที่อยู่นอกไดเรกทอรีที่ผู้ใช้เลือกนี้ การเข้าถึงที่ผู้ใช้ควบคุมได้นี้ช่วยให้ผู้ใช้เลือกเนื้อหาที่ต้องการแชร์กับแอปของคุณได้อย่างเจาะจง
คุณสามารถระบุ URI ของไดเรกทอรีที่เครื่องมือเลือกไฟล์ควรแสดงเมื่อโหลดครั้งแรกได้โดยใช้ Intent Extra EXTRA_INITIAL_URI (ไม่บังคับ)
ข้อมูลโค้ดต่อไปนี้แสดงวิธีสร้างและเรียกใช้ 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) ขึ้นไป คุณไม่สามารถใช้การดำเนินการผ่าน Intent ACTION_OPEN_DOCUMENT_TREE เพื่อขอสิทธิ์เข้าถึงไดเรกทอรีต่อไปนี้
- ไดเรกทอรีรากของวอลุ่มที่จัดเก็บข้อมูลภายใน
- ไดเรกทอรีรากของวอลุ่มการ์ด SD แต่ละรายการที่ผู้ผลิตอุปกรณ์พิจารณาว่าเชื่อถือได้ ไม่ว่าการ์ดนั้นจะเป็นแบบจำลองหรือถอดออกได้ก็ตาม ไดรฟ์ข้อมูลที่เชื่อถือได้คือไดรฟ์ข้อมูลที่แอปสามารถเข้าถึงได้สำเร็จเกือบตลอดเวลา
- ไดเรกทอรี
Download
นอกจากนี้ ใน Android 11 (ระดับ API 30) ขึ้นไป คุณไม่สามารถใช้การดำเนินการผ่าน Intent ACTION_OPEN_DOCUMENT_TREE เพื่อขอให้ผู้ใช้เลือกไฟล์แต่ละไฟล์จากไดเรกทอรีต่อไปนี้
- ไดเรกทอรี
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 ของ Media Store ที่เทียบเท่ากับ 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