เพื่อมอบประสบการณ์ที่ดียิ่งขึ้นให้แก่ผู้ใช้ แอปจำนวนมากอนุญาตให้ผู้ใช้มีส่วนร่วม และเข้าถึงสื่อที่มีอยู่ในวอลุ่มที่จัดเก็บข้อมูลภายนอก เฟรมเวิร์ก ให้ดัชนีที่เพิ่มประสิทธิภาพลงในคอลเล็กชันสื่อ ซึ่งเรียกว่าร้านค้าสื่อ ซึ่งช่วยให้ผู้ใช้เรียกและอัปเดตไฟล์สื่อเหล่านี้ได้ง่ายขึ้น เท่ากัน หลังจากที่ถอนการติดตั้งแอปแล้ว ไฟล์เหล่านี้จะยังคงอยู่ในอุปกรณ์ของผู้ใช้
เครื่องมือเลือกรูปภาพ
นอกจากการใช้ Media Store แล้ว เครื่องมือเลือกรูปภาพของ Android มอบวิธีในตัวที่ปลอดภัยสำหรับผู้ใช้ในการเลือกไฟล์สื่อโดยไม่ต้อง เพื่อให้สิทธิ์แอปของคุณเข้าถึงคลังสื่อทั้งหมด พร้อมให้ใช้งานเท่านั้น บนอุปกรณ์ที่รองรับ สำหรับข้อมูลเพิ่มเติม โปรดดู เครื่องมือเลือกรูปภาพ
ร้านค้าสื่อ
หากต้องการโต้ตอบกับกระบวนการจัดเก็บสื่อ ให้ใช้
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
สิทธิ์ของคุณได้เช่นกัน
ต้องมีเฟรมเวิร์กการเข้าถึงพื้นที่เก็บข้อมูลสำหรับการเข้าถึงแอปอื่น ดาวน์โหลด
หากแอปต้องการเข้าถึงไฟล์ภายในคอลเล็กชัน MediaStore.Downloads
ที่แอปไม่ได้สร้างขึ้น คุณต้องใช้เฟรมเวิร์กการเข้าถึงพื้นที่เก็บข้อมูล เพื่อเรียนรู้
เพิ่มเติมเกี่ยวกับวิธีการใช้เฟรมเวิร์กนี้ โปรดดูที่เข้าถึงเอกสารและไฟล์อื่นๆ จาก
พื้นที่เก็บข้อมูลที่ใช้ร่วมกัน
สิทธิ์เข้าถึงตำแหน่งสื่อ
หากแอปกำหนดเป้าหมายเป็น 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()
ในชุดข้อความของผู้ปฏิบัติงาน - แคชดัชนีคอลัมน์เพื่อให้คุณไม่ต้องเรียก
getColumnIndexOrThrow()
ทุกครั้งที่คุณประมวลผลแถวจากผลการค้นหา - เพิ่มรหัสต่อท้าย URI เนื้อหาดังที่แสดงในตัวอย่างนี้
- อุปกรณ์ที่ใช้ Android 10 ขึ้นไปต้องมี คอลัมน์
ชื่อที่กำหนดใน
API
MediaStore
หากไลบรารีที่เกี่ยวข้องภายในแอปควรมีคอลัมน์ ที่ไม่ได้กำหนดไว้ใน 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 อย่างใดอย่างหนึ่งต่อไปนี้
File
API- ไลบรารีที่มาพร้อมเครื่อง เช่น
fopen()
หากไม่มีสิทธิ์เกี่ยวกับพื้นที่เก็บข้อมูล คุณจะเข้าถึงไฟล์ใน
ไดเรกทอรีเฉพาะแอปและสื่อ
ไฟล์ที่มาจากแอปของคุณโดยใช้ File
API
หากแอปพยายามเข้าถึงไฟล์โดยใช้ File
API และไฟล์ไม่มี
สิทธิ์ที่จำเป็น
FileNotFoundException
เกิดขึ้น
วิธีเข้าถึงไฟล์อื่นๆ ในพื้นที่เก็บข้อมูลที่ใช้ร่วมกันในอุปกรณ์ที่ใช้ Android 10 (API)
ระดับ 29) เราขอแนะนำให้คุณเลือกไม่ใช้การกำหนดขอบเขตชั่วคราว
พื้นที่เก็บข้อมูลตามการตั้งค่า
requestLegacyExternalStorage
เป็น true
ในไฟล์ Manifest ของแอป วิธีเข้าถึงไฟล์สื่อโดยใช้
เมธอดไฟล์แบบเนทีฟใน Android 10 คุณต้องส่งคำขอ
READ_EXTERNAL_STORAGE
สิทธิ์
ข้อควรพิจารณาเมื่อเข้าถึงเนื้อหาสื่อ
เมื่อเข้าถึงเนื้อหาสื่อ ให้คํานึงถึงข้อควรพิจารณาที่จะกล่าวถึงใน ส่วนต่างๆ ต่อไปนี้
ข้อมูลในแคช
หากแอปแคช URI หรือข้อมูลจากร้านค้าสื่อ ให้ตรวจหา อัปเดตในที่เก็บสื่อ การตรวจสอบนี้จะทำให้ ข้อมูลที่แคชไว้ฝั่งแอปจะซิงค์กับข้อมูลผู้ให้บริการฝั่งระบบ
ประสิทธิภาพ
เมื่อคุณอ่านไฟล์สื่อตามลำดับโดยใช้เส้นทางไฟล์โดยตรง
ประสิทธิภาพเทียบได้กับ
MediaStore
API
เมื่อคุณสุ่มอ่านและเขียนไฟล์สื่อโดยใช้เส้นทางไฟล์โดยตรง
แต่กระบวนการอาจช้าลงถึง 2 เท่า ในสถานการณ์เหล่านี้ เราจะ
แนะนำให้ใช้ MediaStore
API แทน
คอลัมน์ข้อมูล
เมื่อเข้าถึงไฟล์สื่อที่มีอยู่ คุณสามารถใช้ค่าของ
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); }
การแชร์
แอปบางแอปให้ผู้ใช้แชร์ไฟล์สื่อระหว่างกันได้ เช่น โซเชียล แอปสื่อช่วยให้ผู้ใช้แชร์รูปภาพและวิดีโอกับเพื่อนได้
หากต้องการแชร์ไฟล์สื่อ ให้ใช้ URI content://
ตามที่แนะนำในคำแนะนำสำหรับ
การสร้างผู้ให้บริการเนื้อหา
การระบุแหล่งที่มาของแอปของไฟล์สื่อ
เมื่อเปิดใช้พื้นที่เก็บข้อมูลที่กำหนดขอบเขตสำหรับ แอปที่กำหนดเป้าหมายเป็น Android 10 ขึ้นไป ระบบจะระบุแหล่งที่มา ลงในไฟล์สื่อแต่ละไฟล์ ซึ่งจะกําหนดไฟล์ที่แอปของคุณเข้าถึงได้เมื่อ แอปยังไม่ได้ขอสิทธิ์ใดๆ ในพื้นที่เก็บข้อมูล แต่ละไฟล์สามารถระบุแหล่งที่มาเป็น แอปเดียว ดังนั้นหากแอปของคุณสร้างไฟล์สื่อที่จัดเก็บไว้ใน คอลเล็กชันสื่อรูปภาพ วิดีโอ หรือไฟล์เสียง แอปของคุณมีสิทธิ์เข้าถึง
แต่หากผู้ใช้ถอนการติดตั้งและติดตั้งแอปอีกครั้ง คุณจะต้องส่งคำขอ
READ_EXTERNAL_STORAGE
เพื่อเข้าถึงไฟล์ที่แอปของคุณสร้างขึ้นในตอนแรก คำขอสิทธิ์นี้
เนื่องจากระบบพิจารณาว่าไฟล์เป็น
เวอร์ชันที่ติดตั้งก่อนหน้านี้ แทนที่จะเป็นเวอร์ชันที่ติดตั้งใหม่
เพิ่มสินค้า
หากต้องการเพิ่มรายการสื่อในคอลเล็กชันที่มีอยู่ ให้ใช้โค้ดที่คล้ายกับ
กำลังติดตาม ข้อมูลโค้ดนี้เข้าถึงวอลุ่ม 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()
ตามที่อธิบายไว้ในส่วนเกี่ยวกับวิธีจัดการกลุ่มสื่อ
ไฟล์
หากแอปมีกรณีการใช้งานอื่นที่ไม่รวมอยู่ในพื้นที่เก็บข้อมูลที่กำหนดขอบเขต ให้ส่ง คำขอฟีเจอร์ และ เลือกไม่ใช้ขอบเขตพื้นที่ชั่วคราว พื้นที่เก็บข้อมูล
นำรายการออก
หากต้องการนำรายการที่แอปไม่จำเป็นต้องใช้ในที่เก็บสื่อแล้วออก ให้ใช้ตรรกะ คล้ายกับที่แสดงในข้อมูลโค้ดต่อไปนี้
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()
ตามที่อธิบายไว้ในส่วนเกี่ยวกับวิธีจัดการกลุ่มสื่อ
ไฟล์
หากแอปมีกรณีการใช้งานอื่นที่ไม่รวมอยู่ในพื้นที่เก็บข้อมูลที่กำหนดขอบเขต ให้ส่ง คำขอฟีเจอร์ และ เลือกไม่ใช้ขอบเขตพื้นที่ชั่วคราว พื้นที่เก็บข้อมูล
ตรวจหาการอัปเดตไฟล์สื่อ
แอปของคุณอาจต้องระบุวอลุ่มพื้นที่เก็บข้อมูลที่มีไฟล์สื่อที่แอป
มีการเพิ่มหรือแก้ไขเมื่อเทียบกับช่วงเวลาก่อนหน้านี้ เพื่อตรวจหาการเปลี่ยนแปลงเหล่านี้
ส่งปริมาณพื้นที่เก็บข้อมูลที่สนใจไปยัง
getGeneration()
ตราบใดที่เวอร์ชันที่เก็บสื่อไม่มีการเปลี่ยนแปลง ค่าที่ส่งกลับ
เพิ่มขึ้นเรื่อยๆ เมื่อเวลาผ่านไป
โดยเฉพาะอย่างยิ่ง getGeneration()
มีประสิทธิภาพมากกว่าวันที่ในคอลัมน์สื่อ
เช่น
DATE_ADDED
และ DATE_MODIFIED
เนื่องจากค่าคอลัมน์ของสื่อเหล่านั้นสามารถเปลี่ยนแปลงได้เมื่อแอปเรียกใช้
setLastModified()
หรือเมื่อ
ผู้ใช้เปลี่ยนนาฬิกาของระบบ
จัดการกลุ่มไฟล์สื่อ
ใน Android 11 ขึ้นไป คุณขอให้ผู้ใช้เลือกกลุ่มได้ ไฟล์สื่อแล้วอัปเดตไฟล์สื่อเหล่านี้ในครั้งเดียว เหล่านี้ จะทำให้ข้อมูลสอดคล้องกันมากขึ้นในอุปกรณ์ต่างๆ และวิธีการทำให้ง่ายขึ้น สำหรับให้ผู้ใช้จัดการคอลเล็กชันสื่อ
วิธีการที่มี "การอัปเดตเป็นกลุ่ม" นี้ มีฟังก์ชัน ดังต่อไปนี้:
createWriteRequest()
- คำขอให้ผู้ใช้ให้สิทธิ์แอปในการเขียนแก่กลุ่มที่ระบุ ไฟล์สื่อต่างๆ
createFavoriteRequest()
- ขอให้ผู้ใช้ทำเครื่องหมายไฟล์สื่อที่ระบุเป็นบางไฟล์ "รายการโปรด" สื่อในอุปกรณ์ แอปทั้งหมดที่มีสิทธิ์อ่านไฟล์นี้สามารถ เห็นว่าผู้ใช้ทำเครื่องหมายไฟล์เป็น "รายการโปรด"
createTrashRequest()
ขอให้ผู้ใช้วางไฟล์สื่อที่ระบุในถังขยะของอุปกรณ์ ระบบจะลบรายการในถังขยะอย่างถาวรหลังจากผ่านไปที่ระบบกำหนด 6 เดือน
createDeleteRequest()
ขอให้ผู้ใช้ลบไฟล์สื่อที่ระบุอย่างถาวร ทันที โดยไม่ต้องทิ้งลงถังขยะล่วงหน้า
หลังจากเรียกใช้วิธีการใดๆ เหล่านี้ ระบบจะสร้าง
PendingIntent
หลังจากแอป
เรียกใช้ความตั้งใจนี้ ผู้ใช้จะเห็นกล่องโต้ตอบที่ขอความยินยอมสำหรับแอปของคุณ
เพื่ออัปเดตหรือลบไฟล์สื่อที่ระบุ
ตัวอย่างเช่น วิธีจัดโครงสร้างการโทรไปยัง 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 ซึ่งจะนำผู้ใช้ไปยังหน้าจอแอปการจัดการสื่อใน การตั้งค่าระบบ ผู้ใช้จะให้สิทธิ์เข้าถึงพิเศษแก่แอปได้จากที่นี่
กรณีการใช้งานที่จำเป็นต้องมีทางเลือกอื่นแทนการจัดเก็บสื่อ
หากแอปทำบทบาทใดบทบาทหนึ่งต่อไปนี้เป็นหลัก ให้พิจารณา
ที่ใช้แทน MediaStore
API
การทำงานกับไฟล์ประเภทอื่น
หากแอปทำงานกับเอกสารและไฟล์ที่ไม่มีสื่อเพียงอย่างเดียว
เช่น ไฟล์ที่ใช้นามสกุลไฟล์ EPUB หรือ PDF ให้ใช้
ACTION_OPEN_DOCUMENT
การดำเนินการ ตามที่อธิบายไว้ในคำแนะนำเกี่ยวกับการจัดเก็บ
และเข้าถึงเอกสารและอื่นๆ
การแชร์ไฟล์ในแอปที่ใช้ร่วมกัน
ในกรณีที่คุณจัดหาชุดแอปที่ใช้ร่วมกัน เช่น แอปรับส่งข้อความ
แอปโปรไฟล์ ตั้งค่าการแชร์ไฟล์
โดยใช้ URI แบบ content://
และเราขอแนะนำเวิร์กโฟลว์นี้ เนื่องจากความปลอดภัยที่ดีที่สุด
ฝึกหัด
แหล่งข้อมูลเพิ่มเติม
หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับวิธีจัดเก็บและเข้าถึงสื่อ โปรดดูข้อมูลต่อไปนี้ ที่ไม่ซับซ้อน
ตัวอย่าง
- MediaStore มีใน GitHub