แอปจำนวนมากอนุญาตให้ผู้ใช้มีส่วนร่วมและเข้าถึงสื่อที่อยู่ในวอลุ่มพื้นที่เก็บข้อมูลภายนอกเพื่อมอบประสบการณ์การใช้งานที่ดียิ่งขึ้น เฟรมเวิร์ก มีดัชนีที่เพิ่มประสิทธิภาพแล้วในคอลเล็กชันสื่อ ซึ่งเรียกว่าที่เก็บสื่อ ซึ่งช่วยให้ผู้ใช้เรียกข้อมูลและอัปเดตไฟล์สื่อเหล่านี้ได้ง่ายขึ้น แม้ว่าจะถอนการติดตั้งแอปแล้ว ไฟล์เหล่านี้จะยังคงอยู่ในอุปกรณ์ของผู้ใช้
เครื่องมือเลือกรูปภาพ
เครื่องมือเลือกรูปภาพของ Android เป็นทางเลือกแทนการใช้ Media Store ซึ่งเป็นวิธีที่ปลอดภัยและมีอยู่แล้วในตัวสำหรับให้ผู้ใช้เลือกไฟล์สื่อได้โดยไม่ต้อง ให้สิทธิ์แอปของคุณเข้าถึงคลังสื่อทั้งหมด ใช้ได้เฉพาะกับอุปกรณ์ที่รองรับเท่านั้น ดูข้อมูลเพิ่มเติมได้ในคู่มือเครื่องมือเลือกรูปภาพ
ร้านขายสื่อ
หากต้องการโต้ตอบกับ MediaStore Abstraction ให้ใช้ออบเจ็กต์
ContentResolver
ที่คุณ
ดึงข้อมูลจากบริบทของแอป
Kotlin
val projection = arrayOf(media-database-columns-to-retrieve) val selection = sql-where-clause-with-placeholder-variables val selectionArgs = values-of-placeholder-variables val sortOrder = sql-order-by-clause applicationContext.contentResolver.query( MediaStore.media-type.Media.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, sortOrder )?.use { cursor -> while (cursor.moveToNext()) { // Use an ID column from the projection to get // a URI representing the media item itself. } }
Java
String[] projection = new String[] { media-database-columns-to-retrieve }; String selection = sql-where-clause-with-placeholder-variables; String[] selectionArgs = new String[] { values-of-placeholder-variables }; String sortOrder = sql-order-by-clause; Cursor cursor = getApplicationContext().getContentResolver().query( MediaStore.media-type.Media.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, sortOrder ); while (cursor.moveToNext()) { // Use an ID column from the projection to get // a URI representing the media item itself. }
ระบบจะสแกนวอลุ่มพื้นที่เก็บข้อมูลภายนอกโดยอัตโนมัติและเพิ่มไฟล์สื่อ ลงในคอลเล็กชันที่กำหนดไว้อย่างชัดเจนต่อไปนี้
- รูปภาพ รวมถึงภาพถ่ายและภาพหน้าจอที่จัดเก็บไว้ในไดเรกทอรี
DCIM/
และPictures/
ระบบจะเพิ่มไฟล์เหล่านี้ลงในตารางMediaStore.Images
- วิดีโอที่จัดเก็บไว้ในไดเรกทอรี
DCIM/
,Movies/
และPictures/
ระบบจะเพิ่มไฟล์เหล่านี้ลงในตารางMediaStore.Video
- ไฟล์เสียงซึ่งจัดเก็บไว้ในไดเรกทอรี
Alarms/
,Audiobooks/
,Music/
,Notifications/
,Podcasts/
และRingtones/
นอกจากนี้ ระบบยังจดจำเพลย์ลิสต์เสียงที่อยู่ในไดเรกทอรีMusic/
หรือMovies/
รวมถึงไฟล์บันทึกเสียงที่อยู่ในไดเรกทอรีRecordings/
ด้วย ระบบจะเพิ่มไฟล์เหล่านี้ลงในตารางMediaStore.Audio
ไดเรกทอรีRecordings/
ไม่พร้อมใช้งานใน Android 11 (API ระดับ 30) และต่ำกว่า - ไฟล์ที่ดาวน์โหลดซึ่งจัดเก็บไว้ในไดเรกทอรี
Download/
ในอุปกรณ์ที่ใช้ Android 10 (API ระดับ 29) ขึ้นไป ระบบจะจัดเก็บไฟล์เหล่านี้ไว้ในตารางMediaStore.Downloads
ตารางนี้ไม่พร้อมใช้งานใน Android 9 (API ระดับ 28) และต่ำกว่า
นอกจากนี้ ร้านค้าสื่อยังมีคอลเล็กชันชื่อ
MediaStore.Files
เนื้อหาของไฟล์นี้จะขึ้นอยู่กับว่าแอปของคุณใช้ที่เก็บข้อมูลที่จำกัดขอบเขต
การเข้าถึงหรือไม่ ซึ่งจะใช้ได้กับแอปที่กำหนดเป้าหมายเป็น
Android 10 ขึ้นไป
- หากเปิดใช้พื้นที่เก็บข้อมูลที่จำกัดขอบเขต คอลเล็กชันจะแสดงเฉพาะรูปภาพ วิดีโอ
และไฟล์เสียงที่แอปของคุณสร้างขึ้น นักพัฒนาแอปส่วนใหญ่ไม่จำเป็นต้องใช้
MediaStore.Files
เพื่อดูไฟล์สื่อจากแอปอื่นๆ แต่หากคุณมีข้อกำหนดเฉพาะ ในการดำเนินการดังกล่าว คุณสามารถประกาศสิทธิ์READ_EXTERNAL_STORAGE
ได้ อย่างไรก็ตาม เราขอแนะนำให้คุณใช้MediaStore
API เพื่อเปิดไฟล์ที่แอปไม่ได้สร้าง - หากที่เก็บข้อมูลที่จำกัดขอบเขตไม่พร้อมใช้งานหรือไม่ได้ใช้ คอลเล็กชันจะแสดงไฟล์สื่อทุกประเภท
ขอสิทธิ์ที่จำเป็น
ก่อนดำเนินการกับไฟล์สื่อ โปรดตรวจสอบว่าแอปของคุณได้ประกาศ สิทธิ์ที่จำเป็นต่อการเข้าถึงไฟล์เหล่านี้แล้ว อย่างไรก็ตาม โปรดระมัดระวังไม่ให้ ประกาศสิทธิ์ที่แอปไม่จำเป็นต้องใช้หรือไม่ได้ใช้
สิทธิ์ในพื้นที่เก็บข้อมูล
แอปของคุณต้องมีสิทธิ์เข้าถึงพื้นที่เก็บข้อมูลหรือไม่นั้นขึ้นอยู่กับว่าแอปเข้าถึง เฉพาะไฟล์สื่อของตัวเองหรือไฟล์ที่สร้างโดยแอปอื่นๆ
เข้าถึงไฟล์สื่อของคุณเอง
ในอุปกรณ์ที่ใช้ Android 10 ขึ้นไป คุณไม่จำเป็นต้องมีสิทธิ์ที่เกี่ยวข้องกับพื้นที่เก็บข้อมูลเพื่อเข้าถึงและแก้ไขไฟล์สื่อที่แอปของคุณเป็นเจ้าของ รวมถึงไฟล์ในMediaStore.Downloads
คอลเล็กชัน เช่น หากคุณกำลังพัฒนาแอปกล้อง คุณไม่จำเป็นต้องขอสิทธิ์ที่เกี่ยวข้องกับพื้นที่เก็บข้อมูลเพื่อเข้าถึงรูปภาพที่ถ่าย เนื่องจากแอปของคุณเป็นเจ้าของรูปภาพที่คุณเขียนลงในร้านค้าสื่อ
เข้าถึงไฟล์สื่อของแอปอื่นๆ
หากต้องการเข้าถึงไฟล์สื่อที่แอปอื่นๆ สร้างขึ้น คุณต้องประกาศสิทธิ์ที่เกี่ยวข้องกับการจัดเก็บที่เหมาะสม และไฟล์ต้องอยู่ในคอลเล็กชันสื่อต่อไปนี้
ตราบใดที่ดูไฟล์ได้จากคำค้นหา MediaStore.Images
MediaStore.Video
หรือ MediaStore.Audio
ก็จะดูได้โดยใช้คำค้นหา MediaStore.Files
ด้วย
ข้อมูลโค้ดต่อไปนี้แสดงวิธีประกาศสิทธิ์เข้าถึงพื้นที่เก็บข้อมูลที่เหมาะสม
<!-- Required only if your app needs to access images or photos that other apps created. --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <!-- Required only if your app needs to access videos that other apps created. --> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" /> <!-- Required only if your app needs to access audio files that other apps created. --> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
สิทธิ์เพิ่มเติมที่จำเป็นสำหรับแอปที่ทำงานบนอุปกรณ์รุ่นเดิม
หากแอปของคุณใช้ในอุปกรณ์ที่ใช้ Android 9 หรือต่ำกว่า หรือหากแอปเลือกไม่ใช้พื้นที่เก็บข้อมูลที่จำกัดขอบเขตชั่วคราว คุณต้องขอสิทธิ์ READ_EXTERNAL_STORAGE
เพื่อเข้าถึงไฟล์สื่อ หากต้องการแก้ไขไฟล์สื่อ คุณต้องขอสิทธิ์
WRITE_EXTERNAL_STORAGE
ด้วย
ต้องใช้ Storage Access Framework เพื่อเข้าถึงรายการที่ดาวน์โหลดของแอปอื่นๆ
หากแอปต้องการเข้าถึงไฟล์ภายในMediaStore.Downloads
คอลเล็กชัน
ที่แอปไม่ได้สร้าง คุณต้องใช้ Storage Access Framework ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีใช้เฟรมเวิร์กนี้ได้ที่เข้าถึงเอกสารและไฟล์อื่นๆ จากพื้นที่เก็บข้อมูลที่แชร์
สิทธิ์เข้าถึงตำแหน่งของสื่อ
หากแอปกำหนดเป้าหมายเป็น Android 10 (API ระดับ 29) ขึ้นไปและต้อง
ดึงข้อมูลเมตา EXIF ที่ไม่ได้แก้ไขออกจากรูปภาพ คุณต้องประกาศสิทธิ์
ACCESS_MEDIA_LOCATION
ในไฟล์ Manifest ของแอป จากนั้นขอสิทธิ์นี้ในรันไทม์
ตรวจสอบการอัปเดตร้านค้าสื่อ
หากต้องการเข้าถึงไฟล์สื่อได้อย่างน่าเชื่อถือมากขึ้น โดยเฉพาะอย่างยิ่งหากแอปแคช URI หรือ
ข้อมูลจากคลังสื่อ ให้ตรวจสอบว่าเวอร์ชันคลังสื่อมีการเปลี่ยนแปลงหรือไม่
เมื่อเทียบกับตอนที่คุณซิงค์ข้อมูลสื่อครั้งล่าสุด หากต้องการตรวจสอบการอัปเดต ให้โทรหา getVersion()
เวอร์ชันที่แสดงคือสตริงที่ไม่ซ้ำกันซึ่งจะเปลี่ยนแปลงเมื่อใดก็ตามที่ร้านค้าสื่อ
มีการเปลี่ยนแปลงอย่างมาก หากเวอร์ชันที่ส่งคืนแตกต่างจากเวอร์ชันที่ซิงค์ล่าสุด
ให้สแกนและซิงค์แคชสื่อของแอปอีกครั้ง
ดำเนินการตรวจสอบนี้เมื่อเริ่มต้นกระบวนการของแอป คุณไม่จำเป็นต้องตรวจสอบ เวอร์ชันทุกครั้งที่ค้นหาสื่อในร้านค้า
อย่าคาดเดารายละเอียดการติดตั้งใช้งานเกี่ยวกับหมายเลขเวอร์ชัน
ค้นหาคอลเล็กชันสื่อ
หากต้องการค้นหาสื่อที่ตรงตามชุดเงื่อนไขที่เฉพาะเจาะจง เช่น ระยะเวลา 5 นาทีขึ้นไป ให้ใช้คำสั่งการเลือกที่คล้ายกับ SQL ซึ่งคล้ายกับคำสั่งที่ แสดงในข้อมูลโค้ดต่อไปนี้
Kotlin
// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your // app didn't create. // Container for information about each video. data class Video(val uri: Uri, val name: String, val duration: Int, val size: Int ) val videoList = mutableListOf<Video>() val collection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.Video.Media.getContentUri( MediaStore.VOLUME_EXTERNAL ) } else { MediaStore.Video.Media.EXTERNAL_CONTENT_URI } val projection = arrayOf( MediaStore.Video.Media._ID, MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.DURATION, MediaStore.Video.Media.SIZE ) // Show only videos that are at least 5 minutes in duration. val selection = "${MediaStore.Video.Media.DURATION} >= ?" val selectionArgs = arrayOf( TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES).toString() ) // Display videos in alphabetical order based on their display name. val sortOrder = "${MediaStore.Video.Media.DISPLAY_NAME} ASC" val query = ContentResolver.query( collection, projection, selection, selectionArgs, sortOrder ) query?.use { cursor -> // Cache column indices. val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID) val nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME) val durationColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION) val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE) while (cursor.moveToNext()) { // Get values of columns for a given video. val id = cursor.getLong(idColumn) val name = cursor.getString(nameColumn) val duration = cursor.getInt(durationColumn) val size = cursor.getInt(sizeColumn) val contentUri: Uri = ContentUris.withAppendedId( MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id ) // Stores column values and the contentUri in a local object // that represents the media file. videoList += Video(contentUri, name, duration, size) } }
Java
// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your // app didn't create. // Container for information about each video. class Video { private final Uri uri; private final String name; private final int duration; private final int size; public Video(Uri uri, String name, int duration, int size) { this.uri = uri; this.name = name; this.duration = duration; this.size = size; } } List<Video> videoList = new ArrayList<Video>(); Uri collection; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { collection = MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL); } else { collection = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } String[] projection = new String[] { MediaStore.Video.Media._ID, MediaStore.Video.Media.DISPLAY_NAME, MediaStore.Video.Media.DURATION, MediaStore.Video.Media.SIZE }; String selection = MediaStore.Video.Media.DURATION + " >= ?"; String[] selectionArgs = new String[] { String.valueOf(TimeUnit.MILLISECONDS.convert(5, TimeUnit.MINUTES)); }; String sortOrder = MediaStore.Video.Media.DISPLAY_NAME + " ASC"; try (Cursor cursor = getApplicationContext().getContentResolver().query( collection, projection, selection, selectionArgs, sortOrder )) { // Cache column indices. int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID); int nameColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DISPLAY_NAME); int durationColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATION); int sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media.SIZE); while (cursor.moveToNext()) { // Get values of columns for a given video. long id = cursor.getLong(idColumn); String name = cursor.getString(nameColumn); int duration = cursor.getInt(durationColumn); int size = cursor.getInt(sizeColumn); Uri contentUri = ContentUris.withAppendedId( MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id); // Stores column values and the contentUri in a local object // that represents the media file. videoList.add(new Video(contentUri, name, duration, size)); } }
เมื่อทำการค้นหาดังกล่าวในแอป โปรดคำนึงถึงสิ่งต่อไปนี้
- เรียกใช้เมธอด
query()
ในเธรดของ Worker - แคชดัชนีคอลัมน์เพื่อให้คุณไม่ต้องเรียกใช้
getColumnIndexOrThrow()
ทุกครั้งที่ประมวลผลแถวจากผลการค้นหา - ต่อท้ายรหัสกับ URI ของเนื้อหาตามที่แสดงในตัวอย่างนี้
- อุปกรณ์ที่ใช้ Android 10 ขึ้นไปต้องมีชื่อคอลัมน์ที่กำหนดใน
MediaStore
API หากไลบรารีที่ขึ้นอยู่กับภายในแอปคาดหวังชื่อคอลัมน์ที่ไม่ได้กำหนดไว้ใน API เช่น"MimeType"
ให้ใช้CursorWrapper
เพื่อแปลชื่อคอลัมน์แบบไดนามิกในกระบวนการของแอป
โหลดภาพขนาดย่อของไฟล์
หากแอปแสดงไฟล์สื่อหลายรายการและขอให้ผู้ใช้เลือกไฟล์ใดไฟล์หนึ่ง ในไฟล์เหล่านี้ การโหลดเวอร์ชันตัวอย่าง หรือภาพขนาดย่อของไฟล์ แทนที่จะโหลดไฟล์เองจะมีประสิทธิภาพมากกว่า
หากต้องการโหลดภาพขนาดย่อสำหรับไฟล์สื่อที่ต้องการ ให้ใช้
loadThumbnail()
และส่งขนาดของภาพขนาดย่อที่ต้องการโหลด ดังที่แสดงใน
ข้อมูลโค้ดต่อไปนี้
Kotlin
// Load thumbnail of a specific media item. val thumbnail: Bitmap = applicationContext.contentResolver.loadThumbnail( content-uri, Size(640, 480), null)
Java
// Load thumbnail of a specific media item. Bitmap thumbnail = getApplicationContext().getContentResolver().loadThumbnail( content-uri, new Size(640, 480), null);
เปิดไฟล์สื่อ
ตรรกะเฉพาะที่คุณใช้เพื่อเปิดไฟล์สื่อจะขึ้นอยู่กับว่าเนื้อหาสื่อแสดงเป็นตัวอธิบายไฟล์ สตรีมไฟล์ หรือเส้นทางไฟล์โดยตรงได้ดีที่สุด
ตัวบอกไฟล์
หากต้องการเปิดไฟล์สื่อโดยใช้ตัวอธิบายไฟล์ ให้ใช้ตรรกะที่คล้ายกับที่แสดงใน ข้อมูลโค้ดต่อไปนี้
Kotlin
// Open a specific media item using ParcelFileDescriptor. val resolver = applicationContext.contentResolver // "rw" for read-and-write. // "rwt" for truncating or overwriting existing file contents. val readOnlyMode = "r" resolver.openFileDescriptor(content-uri, readOnlyMode).use { pfd -> // Perform operations on "pfd". }
Java
// Open a specific media item using ParcelFileDescriptor. ContentResolver resolver = getApplicationContext() .getContentResolver(); // "rw" for read-and-write. // "rwt" for truncating or overwriting existing file contents. String readOnlyMode = "r"; try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(content-uri, readOnlyMode)) { // Perform operations on "pfd". } catch (IOException e) { e.printStackTrace(); }
สตรีมไฟล์
หากต้องการเปิดไฟล์สื่อโดยใช้สตรีมไฟล์ ให้ใช้ตรรกะที่คล้ายกับที่แสดงใน ข้อมูลโค้ดต่อไปนี้
Kotlin
// Open a specific media item using InputStream. val resolver = applicationContext.contentResolver resolver.openInputStream(content-uri).use { stream -> // Perform operations on "stream". }
Java
// Open a specific media item using InputStream. ContentResolver resolver = getApplicationContext() .getContentResolver(); try (InputStream stream = resolver.openInputStream(content-uri)) { // Perform operations on "stream". }
เส้นทางไฟล์โดยตรง
Android 11 (API ระดับ 30) ขึ้นไปช่วยให้คุณใช้ API อื่นนอกเหนือจาก
MediaStore
API เพื่อเข้าถึง
ไฟล์สื่อจากพื้นที่เก็บข้อมูลที่แชร์ได้ เพื่อช่วยให้แอปทำงานร่วมกับคลังสื่อของบุคคลที่สามได้อย่างราบรื่นยิ่งขึ้น แต่คุณจะเข้าถึงไฟล์สื่อได้โดยตรง
โดยใช้ API ใด API หนึ่งต่อไปนี้
File
API- ไลบรารีที่มาพร้อมเครื่อง เช่น
fopen()
หากไม่มีสิทธิ์ที่เกี่ยวข้องกับการจัดเก็บข้อมูล คุณจะเข้าถึงไฟล์ในไดเรกทอรีเฉพาะของแอป รวมถึงไฟล์สื่อที่เชื่อมโยงกับแอปของคุณได้โดยใช้ File
API
หากแอปพยายามเข้าถึงไฟล์โดยใช้ File
API และไม่มีสิทธิ์ที่จำเป็น
จะเกิด
FileNotFoundException
ขึ้น
หากต้องการเข้าถึงไฟล์อื่นๆ ในพื้นที่เก็บข้อมูลที่ใช้ร่วมกันในอุปกรณ์ที่ใช้ Android 10 (API
ระดับ 29) เราขอแนะนำให้เลือกไม่ใช้พื้นที่เก็บข้อมูลที่จำกัดขอบเขตชั่วคราวโดยตั้งค่า
requestLegacyExternalStorage
เป็น true
ในไฟล์ Manifest ของแอป หากต้องการเข้าถึงไฟล์สื่อโดยใช้วิธีการของไฟล์ดั้งเดิมใน Android 10 คุณต้องขอสิทธิ์
READ_EXTERNAL_STORAGE
ด้วย
ข้อควรพิจารณาเมื่อเข้าถึงเนื้อหาสื่อ
เมื่อเข้าถึงเนื้อหาสื่อ โปรดคำนึงถึงข้อควรพิจารณาที่กล่าวถึงในส่วนต่อไปนี้
ข้อมูลในแคช
หากแอปแคช URI หรือข้อมูลจาก MediaStore ให้ตรวจสอบ การอัปเดต MediaStore เป็นระยะ การตรวจสอบนี้ช่วยให้ข้อมูลที่แคชไว้ฝั่งแอปซิงค์กับข้อมูลผู้ให้บริการฝั่งระบบ
ประสิทธิภาพ
เมื่ออ่านไฟล์สื่อแบบต่อเนื่องโดยใช้เส้นทางไฟล์โดยตรง ประสิทธิภาพจะเทียบเท่ากับของ API MediaStore
อย่างไรก็ตาม เมื่อคุณอ่านและเขียนไฟล์สื่อแบบสุ่มโดยใช้เส้นทางไฟล์โดยตรง
กระบวนการนี้อาจช้าลงถึง 2 เท่า ในกรณีเหล่านี้ เราขอแนะนำให้ใช้ MediaStore
API แทน
คอลัมน์ DATA
เมื่อเข้าถึงไฟล์สื่อที่มีอยู่ คุณจะใช้ค่าของคอลัมน์
DATA
ในตรรกะได้
เนื่องจากค่านี้มีเส้นทางไฟล์ที่ถูกต้อง อย่างไรก็ตาม อย่า
คิดว่าไฟล์จะพร้อมใช้งานเสมอไป เตรียมพร้อมรับมือกับข้อผิดพลาดของ I/O ที่อิงตามไฟล์ที่เกิดขึ้น
ในทางกลับกัน หากต้องการสร้างหรืออัปเดตไฟล์สื่อ อย่าใช้ค่าของคอลัมน์
DATA
ให้ใช้ค่าของคอลัมน์
DISPLAY_NAME
และ
RELATIVE_PATH
แทน
ปริมาณการจัดเก็บ
แอปที่กำหนดเป้าหมายเป็น Android 10 ขึ้นไปจะเข้าถึงชื่อที่ไม่ซ้ำกัน ซึ่งระบบกำหนดให้กับวอลุ่มที่จัดเก็บข้อมูลภายนอกแต่ละรายการได้ ระบบการตั้งชื่อนี้ ช่วยให้คุณจัดระเบียบและจัดทำดัชนีเนื้อหาได้อย่างมีประสิทธิภาพ และช่วยให้คุณควบคุม ตำแหน่งที่จัดเก็บไฟล์สื่อใหม่ได้
คุณควรทราบข้อมูลต่อไปนี้
-
VOLUME_EXTERNAL
วอลุ่มจะแสดงมุมมองของวอลุ่มพื้นที่เก็บข้อมูลที่แชร์ทั้งหมดในอุปกรณ์ คุณอ่าน เนื้อหาของวอลุ่มสังเคราะห์นี้ได้ แต่จะแก้ไขเนื้อหาไม่ได้ -
VOLUME_EXTERNAL_PRIMARY
ปริมาณแสดงปริมาณพื้นที่เก็บข้อมูลที่แชร์หลักในอุปกรณ์ คุณสามารถ อ่านและแก้ไขเนื้อหาของไดรฟ์ข้อมูลนี้ได้
คุณค้นหาเล่มอื่นๆ ได้โดยเรียกใช้
MediaStore.getExternalVolumeNames()
Kotlin
val volumeNames: Set<String> = MediaStore.getExternalVolumeNames(context) val firstVolumeName = volumeNames.iterator().next()
Java
Set<String> volumeNames = MediaStore.getExternalVolumeNames(context); String firstVolumeName = volumeNames.iterator().next();
ตำแหน่งที่บันทึกสื่อ
ภาพถ่ายและวิดีโอบางรายการมีข้อมูลตำแหน่งในข้อมูลเมตา ซึ่งแสดงสถานที่ถ่ายภาพหรือสถานที่บันทึกวิดีโอ
วิธีเข้าถึงข้อมูลตำแหน่งนี้ในแอปจะขึ้นอยู่กับว่าคุณ ต้องเข้าถึงข้อมูลตำแหน่งสำหรับรูปภาพหรือวิดีโอ
ภาพถ่าย
หากแอปใช้พื้นที่เก็บข้อมูลที่จำกัดขอบเขต ระบบจะซ่อนข้อมูลตำแหน่งโดยค่าเริ่มต้น หากต้องการเข้าถึงข้อมูลนี้ ให้ทำตามขั้นตอนต่อไปนี้
- ขอสิทธิ์
ACCESS_MEDIA_LOCATION
ในไฟล์ Manifest ของแอป จากออบเจ็กต์
MediaStore
ให้รับไบต์ที่แน่นอนของภาพถ่ายโดยการเรียกsetRequireOriginal()
และส่ง URI ของภาพถ่าย ดังที่แสดงในข้อมูลโค้ดต่อไปนี้Kotlin
val photoUri: Uri = Uri.withAppendedPath( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getString(idColumnIndex) ) // Get location data using the Exifinterface library. // Exception occurs if ACCESS_MEDIA_LOCATION permission isn't granted. photoUri = MediaStore.setRequireOriginal(photoUri) contentResolver.openInputStream(photoUri)?.use { stream -> ExifInterface(stream).run { // If lat/long is null, fall back to the coordinates (0, 0). val latLong = latLong ?: doubleArrayOf(0.0, 0.0) } }
Java
Uri photoUri = Uri.withAppendedPath( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, cursor.getString(idColumnIndex)); final double[] latLong; // Get location data using the Exifinterface library. // Exception occurs if ACCESS_MEDIA_LOCATION permission isn't granted. photoUri = MediaStore.setRequireOriginal(photoUri); InputStream stream = getContentResolver().openInputStream(photoUri); if (stream != null) { ExifInterface exifInterface = new ExifInterface(stream); double[] returnedLatLong = exifInterface.getLatLong(); // If lat/long is null, fall back to the coordinates (0, 0). latLong = returnedLatLong != null ? returnedLatLong : new double[2]; // Don't reuse the stream associated with // the instance of "ExifInterface". stream.close(); } else { // Failed to load the stream, so return the coordinates (0, 0). latLong = new double[2]; }
วิดีโอ
หากต้องการเข้าถึงข้อมูลสถานที่ตั้งภายในข้อมูลเมตาของวิดีโอ ให้ใช้คลาส
MediaMetadataRetriever
ดังที่แสดงในข้อมูลโค้ดต่อไปนี้ แอปของคุณไม่จำเป็นต้องขอสิทธิ์เพิ่มเติมเพื่อใช้คลาสนี้
Kotlin
val retriever = MediaMetadataRetriever() val context = applicationContext // Find the videos that are stored on a device by querying the video collection. val query = ContentResolver.query( collection, projection, selection, selectionArgs, sortOrder ) query?.use { cursor -> val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID) while (cursor.moveToNext()) { val id = cursor.getLong(idColumn) val videoUri: Uri = ContentUris.withAppendedId( MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id ) extractVideoLocationInfo(videoUri) } } private fun extractVideoLocationInfo(videoUri: Uri) { try { retriever.setDataSource(context, videoUri) } catch (e: RuntimeException) { Log.e(APP_TAG, "Cannot retrieve video file", e) } // Metadata uses a standardized format. val locationMetadata: String? = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION) }
Java
MediaMetadataRetriever retriever = new MediaMetadataRetriever(); Context context = getApplicationContext(); // Find the videos that are stored on a device by querying the video collection. try (Cursor cursor = context.getContentResolver().query( collection, projection, selection, selectionArgs, sortOrder )) { int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID); while (cursor.moveToNext()) { long id = cursor.getLong(idColumn); Uri videoUri = ContentUris.withAppendedId( MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id); extractVideoLocationInfo(videoUri); } } private void extractVideoLocationInfo(Uri videoUri) { try { retriever.setDataSource(context, videoUri); } catch (RuntimeException e) { Log.e(APP_TAG, "Cannot retrieve video file", e); } // Metadata uses a standardized format. String locationMetadata = retriever.extractMetadata( MediaMetadataRetriever.METADATA_KEY_LOCATION); }
การแชร์
แอปบางแอปอนุญาตให้ผู้ใช้แชร์ไฟล์สื่อกับผู้ใช้รายอื่น เช่น แอปโซเชียลมีเดียช่วยให้ผู้ใช้แชร์รูปภาพและวิดีโอกับเพื่อนๆ ได้
หากต้องการแชร์ไฟล์สื่อ ให้ใช้ content://
URI ตามที่แนะนำในคำแนะนำ
ในการสร้าง Content Provider
การระบุแหล่งที่มาของแอปสำหรับไฟล์สื่อ
เมื่อเปิดใช้ที่เก็บข้อมูลที่จำกัดขอบเขตสำหรับแอปที่กำหนดเป้าหมายเป็น Android 10 ขึ้นไป ระบบจะระบุแอปกับไฟล์สื่อแต่ละไฟล์ ซึ่งจะเป็นตัวกำหนดไฟล์ที่แอปเข้าถึงได้เมื่อแอปไม่ได้ขอสิทธิ์เข้าถึงที่เก็บข้อมูลใดๆ ไฟล์แต่ละไฟล์จะระบุแหล่งที่มาได้เพียงแอปเดียว ดังนั้นหากแอปสร้างไฟล์สื่อที่จัดเก็บไว้ในคอลเล็กชันสื่อของรูปภาพ วิดีโอ หรือไฟล์เสียง แอปของคุณจะมีสิทธิ์เข้าถึงไฟล์นั้น
อย่างไรก็ตาม หากผู้ใช้ถอนการติดตั้งและติดตั้งแอปของคุณอีกครั้ง คุณต้องขอ
READ_EXTERNAL_STORAGE
เพื่อเข้าถึงไฟล์ที่แอปของคุณสร้างขึ้นในตอนแรก คำขอสิทธิ์นี้
จำเป็นเนื่องจากระบบพิจารณาว่าไฟล์ดังกล่าวเป็นของแอปเวอร์ชันที่
ติดตั้งไว้ก่อนหน้านี้ ไม่ใช่เวอร์ชันที่ติดตั้งใหม่
เมื่อแอปที่กำหนดเป้าหมายเป็น SDK 36 ขึ้นไปในอุปกรณ์ที่ใช้ Android 16 ขึ้นไปแจ้งให้ขอสิทธิ์เข้าถึงรูปภาพและวิดีโอ ผู้ใช้ที่เลือกจำกัดการเข้าถึงสื่อที่เลือกจะเห็นรูปภาพทั้งหมดที่แอปเป็นเจ้าของซึ่งเลือกไว้ล่วงหน้าในเครื่องมือเลือกรูปภาพ ผู้ใช้สามารถยกเลิกการเลือกรายการที่เลือกไว้ล่วงหน้าเหล่านี้ ซึ่งจะเป็นการเพิกถอน สิทธิ์การเข้าถึงรูปภาพและวิดีโอเหล่านั้นของแอป
เพิ่มสินค้า
หากต้องการเพิ่มรายการสื่อลงในคอลเล็กชันที่มีอยู่ ให้ใช้โค้ดที่คล้ายกับโค้ดต่อไปนี้ ข้อมูลโค้ดนี้จะเข้าถึงVOLUME_EXTERNAL_PRIMARY
ระดับเสียง
ในอุปกรณ์ที่ใช้ Android 10 ขึ้นไป เนื่องจากในอุปกรณ์เหล่านี้ คุณจะแก้ไขเนื้อหาของวอลุ่มได้ก็ต่อเมื่อเป็นวอลุ่มหลักเท่านั้น ดังที่อธิบายไว้ในส่วนวอลุ่มพื้นที่เก็บข้อมูล
Kotlin
// Add a specific media item. val resolver = applicationContext.contentResolver // Find all audio files on the primary external storage device. val audioCollection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.Audio.Media.getContentUri( MediaStore.VOLUME_EXTERNAL_PRIMARY ) } else { MediaStore.Audio.Media.EXTERNAL_CONTENT_URI } // Publish a new song. val newSongDetails = ContentValues().apply { put(MediaStore.Audio.Media.DISPLAY_NAME, "My Song.mp3") } // Keep a handle to the new song's URI in case you need to modify it // later. val myFavoriteSongUri = resolver .insert(audioCollection, newSongDetails)
Java
// Add a specific media item. ContentResolver resolver = getApplicationContext() .getContentResolver(); // Find all audio files on the primary external storage device. Uri audioCollection; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { audioCollection = MediaStore.Audio.Media .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); } else { audioCollection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } // Publish a new song. ContentValues newSongDetails = new ContentValues(); newSongDetails.put(MediaStore.Audio.Media.DISPLAY_NAME, "My Song.mp3"); // Keep a handle to the new song's URI in case you need to modify it // later. Uri myFavoriteSongUri = resolver .insert(audioCollection, newSongDetails);
สลับสถานะรอดำเนินการสำหรับไฟล์สื่อ
หากแอปของคุณดำเนินการที่อาจใช้เวลานาน เช่น การเขียนไปยัง
ไฟล์สื่อ การมีสิทธิ์เข้าถึงไฟล์แบบพิเศษจะเป็นประโยชน์เนื่องจากไฟล์กำลัง
ประมวลผล ในอุปกรณ์ที่ใช้ Android 10 ขึ้นไป แอปของคุณจะ
รับสิทธิ์เข้าถึงพิเศษนี้ได้โดยการตั้งค่าสถานะ
IS_PENDING
เป็น 1 มีเพียงแอปของคุณเท่านั้นที่จะดูไฟล์ได้จนกว่าแอปจะเปลี่ยนค่าของ
IS_PENDING
กลับเป็น 0
ข้อมูลโค้ดต่อไปนี้สร้างขึ้นจากข้อมูลโค้ดก่อนหน้า ข้อมูลโค้ดนี้แสดงวิธีใช้แฟล็ก IS_PENDING
เมื่อจัดเก็บเพลงยาวใน
ไดเรกทอรีที่สอดคล้องกับคอลเล็กชัน MediaStore.Audio
Kotlin
// Add a media item that other apps don't see until the item is // fully written to the media store. val resolver = applicationContext.contentResolver // Find all audio files on the primary external storage device. val audioCollection = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { MediaStore.Audio.Media.getContentUri( MediaStore.VOLUME_EXTERNAL_PRIMARY ) } else { MediaStore.Audio.Media.EXTERNAL_CONTENT_URI } val songDetails = ContentValues().apply { put(MediaStore.Audio.Media.DISPLAY_NAME, "My Workout Playlist.mp3") put(MediaStore.Audio.Media.IS_PENDING, 1) } val songContentUri = resolver.insert(audioCollection, songDetails) // "w" for write. resolver.openFileDescriptor(songContentUri, "w", null).use { pfd -> // Write data into the pending audio file. } // Now that you're finished, release the "pending" status and let other apps // play the audio track. songDetails.clear() songDetails.put(MediaStore.Audio.Media.IS_PENDING, 0) resolver.update(songContentUri, songDetails, null, null)
Java
// Add a media item that other apps don't see until the item is // fully written to the media store. ContentResolver resolver = getApplicationContext() .getContentResolver(); // Find all audio files on the primary external storage device. Uri audioCollection; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { audioCollection = MediaStore.Audio.Media .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); } else { audioCollection = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } ContentValues songDetails = new ContentValues(); songDetails.put(MediaStore.Audio.Media.DISPLAY_NAME, "My Workout Playlist.mp3"); songDetails.put(MediaStore.Audio.Media.IS_PENDING, 1); Uri songContentUri = resolver .insert(audioCollection, songDetails); // "w" for write. try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(songContentUri, "w", null)) { // Write data into the pending audio file. } // Now that you're finished, release the "pending" status and let other apps // play the audio track. songDetails.clear(); songDetails.put(MediaStore.Audio.Media.IS_PENDING, 0); resolver.update(songContentUri, songDetails, null, null);
ให้คำใบ้สำหรับตำแหน่งไฟล์
เมื่อแอปจัดเก็บสื่อในอุปกรณ์ที่ใช้ Android 10 โดยค่าเริ่มต้น ระบบจะจัดระเบียบสื่อตามประเภท เช่น โดยค่าเริ่มต้น ระบบจะวางไฟล์รูปภาพใหม่
ไว้ในไดเรกทอรี
Environment.DIRECTORY_PICTURES
ซึ่งสอดคล้องกับคอลเล็กชัน
MediaStore.Images
หากแอปทราบตำแหน่งที่เฉพาะเจาะจงซึ่งจัดเก็บไฟล์ได้ เช่น
อัลบั้มรูปภาพชื่อ Pictures/MyVacationPictures
คุณสามารถตั้งค่า
MediaColumns.RELATIVE_PATH
เพื่อให้คำแนะนำแก่ระบบเกี่ยวกับตำแหน่งที่จะจัดเก็บไฟล์ที่เขียนใหม่
อัปเดตรายการ
หากต้องการอัปเดตไฟล์สื่อที่แอปของคุณเป็นเจ้าของ ให้ใช้โค้ดที่คล้ายกับโค้ดต่อไปนี้
Kotlin
// Updates an existing media item. val mediaId = // MediaStore.Audio.Media._ID of item to update. val resolver = applicationContext.contentResolver // When performing a single item update, prefer using the ID. val selection = "${MediaStore.Audio.Media._ID} = ?" // By using selection + args you protect against improper escaping of // values. val selectionArgs = arrayOf(mediaId.toString()) // Update an existing song. val updatedSongDetails = ContentValues().apply { put(MediaStore.Audio.Media.DISPLAY_NAME, "My Favorite Song.mp3") } // Use the individual song's URI to represent the collection that's // updated. val numSongsUpdated = resolver.update( myFavoriteSongUri, updatedSongDetails, selection, selectionArgs)
Java
// Updates an existing media item. long mediaId = // MediaStore.Audio.Media._ID of item to update. ContentResolver resolver = getApplicationContext() .getContentResolver(); // When performing a single item update, prefer using the ID. String selection = MediaStore.Audio.Media._ID + " = ?"; // By using selection + args you protect against improper escaping of // values. Here, "song" is an in-memory object that caches the song's // information. String[] selectionArgs = new String[] { getId().toString() }; // Update an existing song. ContentValues updatedSongDetails = new ContentValues(); updatedSongDetails.put(MediaStore.Audio.Media.DISPLAY_NAME, "My Favorite Song.mp3"); // Use the individual song's URI to represent the collection that's // updated. int numSongsUpdated = resolver.update( myFavoriteSongUri, updatedSongDetails, selection, selectionArgs);
หากที่เก็บข้อมูลที่จำกัดขอบเขตไม่พร้อมใช้งานหรือไม่ได้เปิดใช้ กระบวนการที่แสดงใน โค้ดตัวอย่างก่อนหน้าจะใช้ได้กับไฟล์ที่แอปของคุณไม่ได้เป็นเจ้าของด้วย
อัปเดตในโค้ดที่มาพร้อมเครื่อง
หากต้องการเขียนไฟล์สื่อโดยใช้ไลบรารีแบบเนทีฟ ให้ส่งตัวอธิบายไฟล์ที่เชื่อมโยงของไฟล์จากโค้ดที่ใช้ Java หรือ Kotlin ไปยังโค้ดแบบเนทีฟ
ข้อมูลโค้ดต่อไปนี้แสดงวิธีส่งตัวอธิบายไฟล์ของออบเจ็กต์สื่อ ไปยังโค้ดเนทีฟของแอป
Kotlin
val contentUri: Uri = ContentUris.withAppendedId( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cursor.getLong(BaseColumns._ID)) val fileOpenMode = "r" val parcelFd = resolver.openFileDescriptor(contentUri, fileOpenMode) val fd = parcelFd?.detachFd() // Pass the integer value "fd" into your native code. Remember to call // close(2) on the file descriptor when you're done using it.
Java
Uri contentUri = ContentUris.withAppendedId( MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, cursor.getLong(Integer.parseInt(BaseColumns._ID))); String fileOpenMode = "r"; ParcelFileDescriptor parcelFd = resolver.openFileDescriptor(contentUri, fileOpenMode); if (parcelFd != null) { int fd = parcelFd.detachFd(); // Pass the integer value "fd" into your native code. Remember to call // close(2) on the file descriptor when you're done using it. }
อัปเดตไฟล์สื่อของแอปอื่นๆ
หากแอปใช้พื้นที่เก็บข้อมูลที่จำกัดขอบเขต โดยปกติแล้ว แอปจะอัปเดตไฟล์สื่อที่แอปอื่นมีส่วนร่วมใน ที่เก็บสื่อไม่ได้
คุณขอความยินยอมจากผู้ใช้เพื่อแก้ไขไฟล์ได้โดยการตรวจหาRecoverableSecurityException
ที่แพลตฟอร์มส่งมา จากนั้นคุณสามารถขอให้ผู้ใช้ให้สิทธิ์แอปของคุณ
ในการเข้าถึงแบบเขียนไปยังรายการนั้นๆ ดังที่แสดงในข้อมูลโค้ดต่อไปนี้
Kotlin
// Apply a grayscale filter to the image at the given content URI. try { // "w" for write. contentResolver.openFileDescriptor(image-content-uri, "w")?.use { setGrayscaleFilter(it) } } catch (securityException: SecurityException) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val recoverableSecurityException = securityException as? RecoverableSecurityException ?: throw RuntimeException(securityException.message, securityException) val intentSender = recoverableSecurityException.userAction.actionIntent.intentSender intentSender?.let { startIntentSenderForResult(intentSender, image-request-code, null, 0, 0, 0, null) } } else { throw RuntimeException(securityException.message, securityException) } }
Java
try { // "w" for write. ParcelFileDescriptor imageFd = getContentResolver() .openFileDescriptor(image-content-uri, "w"); setGrayscaleFilter(imageFd); } catch (SecurityException securityException) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { RecoverableSecurityException recoverableSecurityException; if (securityException instanceof RecoverableSecurityException) { recoverableSecurityException = (RecoverableSecurityException)securityException; } else { throw new RuntimeException( securityException.getMessage(), securityException); } IntentSender intentSender =recoverableSecurityException.getUserAction() .getActionIntent().getIntentSender(); startIntentSenderForResult(intentSender, image-request-code, null, 0, 0, 0, null); } else { throw new RuntimeException( securityException.getMessage(), securityException); } }
ทำกระบวนการนี้ทุกครั้งที่แอปต้องแก้ไขไฟล์สื่อที่ไม่ได้สร้างขึ้นเอง
หรือหากแอปทำงานบน Android 11 ขึ้นไป คุณสามารถ
อนุญาตให้ผู้ใช้ให้สิทธิ์การเข้าถึงแบบเขียนแก่แอปของคุณในกลุ่มไฟล์สื่อได้ ใช้วิธี
createWriteRequest()
ตามที่อธิบายไว้ในส่วนเกี่ยวกับวิธีจัดการกลุ่มไฟล์สื่อ
หากแอปมี Use Case อื่นที่ไม่ได้ครอบคลุมโดยที่เก็บข้อมูลที่จำกัดขอบเขต ให้ยื่นคำขอฟีเจอร์และเลือกไม่ใช้ที่เก็บข้อมูลที่จำกัดขอบเขตชั่วคราว
นำรายการออก
หากต้องการนำรายการที่แอปไม่ต้องการในร้านค้าสื่อออก ให้ใช้ตรรกะ ที่คล้ายกับที่แสดงในข้อมูลโค้ดต่อไปนี้
Kotlin
// Remove a specific media item. val resolver = applicationContext.contentResolver // URI of the image to remove. val imageUri = "..." // WHERE clause. val selection = "..." val selectionArgs = "..." // Perform the actual removal. val numImagesRemoved = resolver.delete( imageUri, selection, selectionArgs)
Java
// Remove a specific media item. ContentResolver resolver = getApplicationContext() getContentResolver(); // URI of the image to remove. Uri imageUri = "..."; // WHERE clause. String selection = "..."; String[] selectionArgs = "..."; // Perform the actual removal. int numImagesRemoved = resolver.delete( imageUri, selection, selectionArgs);
หากที่เก็บข้อมูลที่จำกัดขอบเขตไม่พร้อมใช้งานหรือไม่ได้เปิดใช้ คุณสามารถใช้โค้ดที่ยกมาข้างต้นเพื่อนำไฟล์ที่แอปอื่นเป็นเจ้าของออกได้ อย่างไรก็ตาม หากเปิดใช้พื้นที่เก็บข้อมูลที่จำกัดขอบเขต คุณจะต้องแคช RecoverableSecurityException
สำหรับแต่ละไฟล์ที่แอปต้องการนำออก ตามที่อธิบายไว้ในส่วนเกี่ยวกับการอัปเดตรายการสื่อ
หากแอปทำงานบน Android 11 ขึ้นไป คุณสามารถอนุญาตให้ผู้ใช้
เลือกกลุ่มไฟล์สื่อที่จะนำออกได้ ใช้วิธี createTrashRequest()
หรือวิธี
createDeleteRequest()
ตามที่อธิบายไว้ในส่วนเกี่ยวกับวิธีจัดการกลุ่มไฟล์สื่อ
หากแอปมี Use Case อื่นที่ไม่ได้ครอบคลุมโดยที่เก็บข้อมูลที่จำกัดขอบเขต ให้ยื่นคำขอฟีเจอร์และเลือกไม่ใช้ที่เก็บข้อมูลที่จำกัดขอบเขตชั่วคราว
ตรวจหาการอัปเดตไฟล์สื่อ
แอปอาจต้องระบุวอลุ่มพื้นที่เก็บข้อมูลที่มีไฟล์สื่อที่แอป
เพิ่มหรือแก้ไข เมื่อเทียบกับช่วงเวลาก่อนหน้า หากต้องการตรวจหาการเปลี่ยนแปลงเหล่านี้
ได้อย่างน่าเชื่อถือที่สุด ให้ส่งระดับเสียงของพื้นที่เก็บข้อมูลที่ต้องการไปยัง
getGeneration()
ตราบใดที่เวอร์ชันของ MediaStore ไม่เปลี่ยนแปลง ค่าที่ส่งคืนของเมธอดนี้จะเพิ่มขึ้นอย่างต่อเนื่องเมื่อเวลาผ่านไป
โดยเฉพาะอย่างยิ่ง getGeneration()
มีความเสถียรมากกว่าวันที่ในคอลัมน์สื่อ
เช่น
DATE_ADDED
และ DATE_MODIFIED
เนื่องจากค่าคอลัมน์สื่อเหล่านั้นอาจเปลี่ยนแปลงได้เมื่อแอปเรียกใช้
setLastModified()
หรือเมื่อ
ผู้ใช้เปลี่ยนนาฬิกาของระบบ
จัดการกลุ่มไฟล์สื่อ
ใน Android 11 ขึ้นไป คุณสามารถขอให้ผู้ใช้เลือกกลุ่ม ไฟล์สื่อ แล้วอัปเดตไฟล์สื่อเหล่านี้ในการดำเนินการครั้งเดียว ซึ่ง วิธีเหล่านี้ช่วยให้มีความสอดคล้องกันมากขึ้นในอุปกรณ์ต่างๆ และช่วยให้ผู้ใช้จัดการคอลเล็กชันสื่อได้ง่ายขึ้น
วิธีการที่ให้ฟังก์ชัน "อัปเดตแบบเป็นชุด" นี้ ได้แก่ รายการต่อไปนี้
createWriteRequest()
- ขอให้ผู้ใช้ให้สิทธิ์การเขียนไฟล์สื่อในกลุ่มที่ระบุแก่แอปของคุณ
createFavoriteRequest()
- ขอให้ผู้ใช้ทำเครื่องหมายไฟล์สื่อที่ระบุเป็นสื่อ "รายการโปรด" ในอุปกรณ์ แอปใดก็ตามที่มีสิทธิ์เข้าถึงแบบอ่านไฟล์นี้จะ เห็นว่าผู้ใช้ได้ทำเครื่องหมายไฟล์เป็น "รายการโปรด"
createTrashRequest()
ขอให้ผู้ใช้นำไฟล์สื่อที่ระบุไปไว้ในถังขยะของอุปกรณ์ ระบบจะลบรายการในถังขยะออกอย่างถาวรหลังจากระยะเวลาที่ระบบกำหนด
createDeleteRequest()
ขอให้ผู้ใช้ลบไฟล์สื่อที่ระบุอย่างถาวร ทันทีโดยไม่ต้องนำไปใส่ในถังขยะก่อน
หลังจากเรียกใช้เมธอดใดเมธอดหนึ่งเหล่านี้แล้ว ระบบจะสร้างออบเจ็กต์ PendingIntent
หลังจากที่แอปของคุณเรียกใช้ Intent นี้ ผู้ใช้จะเห็นกล่องโต้ตอบที่ขอความยินยอมให้แอปของคุณอัปเดตหรือลบไฟล์สื่อที่ระบุ
เช่น วิธีจัดโครงสร้างการเรียกใช้ createWriteRequest()
มีดังนี้
Kotlin
val urisToModify = /* A collection of content URIs to modify. */ val editPendingIntent = MediaStore.createWriteRequest(contentResolver, urisToModify) // Launch a system prompt requesting user permission for the operation. startIntentSenderForResult(editPendingIntent.intentSender, EDIT_REQUEST_CODE, null, 0, 0, 0)
Java
List<Uri> urisToModify = /* A collection of content URIs to modify. */ PendingIntent editPendingIntent = MediaStore.createWriteRequest(contentResolver, urisToModify); // Launch a system prompt requesting user permission for the operation. startIntentSenderForResult(editPendingIntent.getIntentSender(), EDIT_REQUEST_CODE, null, 0, 0, 0);
ประเมินคำตอบของผู้ใช้ หากผู้ใช้ให้ความยินยอม ให้ดำเนินการต่อด้วย การดำเนินการกับสื่อ มิฉะนั้น ให้อธิบายให้ผู้ใช้ทราบว่าทำไมแอปของคุณจึงต้องการ สิทธิ์
Kotlin
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { ... when (requestCode) { EDIT_REQUEST_CODE -> if (resultCode == Activity.RESULT_OK) { /* Edit request granted; proceed. */ } else { /* Edit request not granted; explain to the user. */ } } }
Java
@Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { ... if (requestCode == EDIT_REQUEST_CODE) { if (resultCode == Activity.RESULT_OK) { /* Edit request granted; proceed. */ } else { /* Edit request not granted; explain to the user. */ } } }
คุณใช้รูปแบบทั่วไปเดียวกันนี้กับ
createFavoriteRequest()
createTrashRequest()
และ
createDeleteRequest()
ได้
สิทธิ์การจัดการสื่อ
ผู้ใช้อาจเชื่อถือแอปหนึ่งๆ ในการจัดการสื่อ เช่น การแก้ไขไฟล์สื่อบ่อยๆ หากแอปของคุณกำหนดเป้าหมายเป็น Android 11 ขึ้นไปและไม่ใช่แอปแกลเลอรีเริ่มต้นของอุปกรณ์ คุณต้องแสดงกล่องโต้ตอบการยืนยันต่อผู้ใช้ทุกครั้งที่แอปพยายาม แก้ไขหรือลบไฟล์
หากแอปกำหนดเป้าหมายเป็น Android 12 (API ระดับ 31) ขึ้นไป คุณสามารถขอให้ ผู้ใช้ให้สิทธิ์เข้าถึงแบบพิเศษสำหรับการจัดการสื่อแก่แอปได้ สิทธิ์นี้ช่วยให้แอปของคุณทำสิ่งต่อไปนี้ได้โดยไม่ต้องแจ้งให้ผู้ใช้ทราบสำหรับการดำเนินการกับไฟล์แต่ละรายการ
- แก้ไขไฟล์โดยใช้
createWriteRequest()
- ย้ายไฟล์เข้าและออกจากถังขยะโดยใช้
createTrashRequest()
- ลบไฟล์โดยใช้
createDeleteRequest()
โดยทำตามขั้นตอนต่อไปนี้
ประกาศสิทธิ์
MANAGE_MEDIA
และสิทธิ์READ_EXTERNAL_STORAGE
ในไฟล์ Manifest ของแอปหากต้องการเรียกใช้
createWriteRequest()
โดยไม่แสดงกล่องโต้ตอบการยืนยัน ให้ประกาศสิทธิ์ACCESS_MEDIA_LOCATION
ด้วยในแอป ให้แสดง UI แก่ผู้ใช้เพื่ออธิบายเหตุผลที่ผู้ใช้อาจต้องการให้สิทธิ์ การเข้าถึงการจัดการสื่อแก่แอปของคุณ
เรียกใช้การดำเนินการของ
ACTION_REQUEST_MANAGE_MEDIA
Intent ซึ่งจะนำผู้ใช้ไปยังหน้าจอแอปการจัดการสื่อในการตั้งค่าระบบ จากที่นี่ ผู้ใช้สามารถให้สิทธิ์เข้าถึงแอปพิเศษได้
Use Case ที่ต้องใช้ทางเลือกอื่นแทนร้านค้าสื่อ
หากแอปของคุณมีบทบาทอย่างใดอย่างหนึ่งต่อไปนี้เป็นหลัก ให้พิจารณาใช้MediaStore
API อื่นแทน
การทำงานกับไฟล์ประเภทอื่นๆ
หากแอปของคุณทำงานกับเอกสารและไฟล์ที่ไม่ได้มีเฉพาะเนื้อหาสื่อ เช่น ไฟล์ที่ใช้นามสกุลไฟล์ EPUB หรือ PDF ให้ใช้ACTION_OPEN_DOCUMENT
การดำเนินการของ Intent ตามที่อธิบายไว้ในคำแนะนำเกี่ยวกับการจัดเก็บและการเข้าถึงเอกสารและไฟล์อื่นๆ
การแชร์ไฟล์ในแอปที่ใช้ร่วมกัน
ในกรณีที่คุณมีชุดแอปคู่กัน เช่น แอปส่งข้อความและ
แอปโปรไฟล์ ให้ตั้งค่าการแชร์ไฟล์
โดยใช้ URI ของ content://
นอกจากนี้ เราขอแนะนำให้ใช้วิธีนี้เป็นแนวทางปฏิบัติแนะนำด้านความปลอดภัยด้วย
แหล่งข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับวิธีจัดเก็บและเข้าถึงสื่อได้จากแหล่งข้อมูลต่อไปนี้
ตัวอย่าง
- MediaStore พร้อมใช้งานบน GitHub