เข้าถึงไฟล์สื่อจากพื้นที่เก็บข้อมูลที่ใช้ร่วมกัน

เพื่อมอบประสบการณ์ที่ดียิ่งขึ้นให้แก่ผู้ใช้ แอปจำนวนมากอนุญาตให้ผู้ใช้มีส่วนร่วม และเข้าถึงสื่อที่มีอยู่ในวอลุ่มที่จัดเก็บข้อมูลภายนอก เฟรมเวิร์ก ให้ดัชนีที่เพิ่มประสิทธิภาพลงในคอลเล็กชันสื่อ ซึ่งเรียกว่าร้านค้าสื่อ ซึ่งช่วยให้ผู้ใช้เรียกและอัปเดตไฟล์สื่อเหล่านี้ได้ง่ายขึ้น เท่ากัน หลังจากที่ถอนการติดตั้งแอปแล้ว ไฟล์เหล่านี้จะยังคงอยู่ในอุปกรณ์ของผู้ใช้

เครื่องมือเลือกรูปภาพ

นอกจากการใช้ 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);

เปิดไฟล์สื่อ

ตรรกะเฉพาะที่คุณใช้เปิดไฟล์สื่อจะขึ้นอยู่กับว่าเนื้อหาสื่อแสดงเป็น File Descriptor, File Stream หรือเส้นทางไฟล์โดยตรงได้ดีที่สุดหรือไม่

ข้อบ่งชี้ไฟล์

หากต้องการเปิดไฟล์สื่อโดยใช้ข้อบ่งชี้ไฟล์ ให้ใช้ตรรกะที่คล้ายกับที่แสดงใน ข้อมูลโค้ดต่อไปนี้

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();

ตำแหน่งที่มีการบันทึกสื่อ

ภาพถ่ายและวิดีโอบางรายการมีข้อมูลตำแหน่งอยู่ในข้อมูลเมตา ซึ่งแสดงสถานที่ถ่ายภาพหรือวิดีโอ บันทึกไว้

วิธีเข้าถึงข้อมูลตำแหน่งนี้ในแอปขึ้นอยู่กับว่าคุณ จำเป็นต้องเข้าถึงข้อมูลตำแหน่งสำหรับภาพถ่ายหรือวิดีโอ

ภาพถ่าย

หากแอปใช้พื้นที่เก็บข้อมูลแบบจำกัด ระบบจะซ่อนข้อมูลตำแหน่งโดยค่าเริ่มต้น วิธีเข้าถึงข้อมูลนี้ ทำตามขั้นตอนต่อไปนี้

  1. ขอ ACCESS_MEDIA_LOCATION สิทธิ์ในไฟล์ Manifest ของแอป
  2. จากออบเจ็กต์ 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

ข้อมูลโค้ดต่อไปนี้สร้างขึ้นจากข้อมูลโค้ดก่อนหน้า ตัวอย่างนี้แสดงวิธีใช้ Flag 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()

ขอให้ผู้ใช้วางไฟล์สื่อที่ระบุในถังขยะของอุปกรณ์ ระบบจะลบรายการในถังขยะออกอย่างถาวรหลังจากผ่านไปตามระยะเวลาที่ระบบกำหนด

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) ขึ้นไป คุณขอให้ดำเนินการต่อไปนี้ได้ ผู้ใช้ให้สิทธิ์แอปของคุณในการเข้าถึงสิทธิ์พิเศษในการจัดการสื่อ ช่วงเวลานี้ ทำให้แอปของคุณสามารถทำสิ่งต่อไปนี้ได้โดยไม่จำเป็นต้องแจ้ง ผู้ใช้สำหรับการดำเนินการไฟล์แต่ละรายการ:

โดยทำตามขั้นตอนต่อไปนี้

  1. ประกาศแอตทริบิวต์ สิทธิ์ MANAGE_MEDIA และ READ_EXTERNAL_STORAGE ในไฟล์ Manifest ของแอป

    หากต้องการโทรหา createWriteRequest() โดยไม่แสดงกล่องโต้ตอบการยืนยัน ให้ประกาศสิทธิ์ ACCESS_MEDIA_LOCATION ด้วย

  2. ในแอป ให้แสดง UI แก่ผู้ใช้เพื่ออธิบายเหตุผลที่ควรให้สิทธิ์ สิทธิ์การจัดการสื่อไปยังแอปของคุณ

  3. เรียกใช้การดำเนินการตามACTION_REQUEST_MANAGE_MEDIA Intent ซึ่งจะนำผู้ใช้ไปยังหน้าจอแอปการจัดการสื่อในการตั้งค่าระบบ ผู้ใช้จะให้สิทธิ์เข้าถึงพิเศษแก่แอปได้จากที่นี่

Use Case ที่ต้องอาศัยทางเลือกอื่นนอกเหนือจากที่เก็บสื่อ

หากแอปทำบทบาทใดบทบาทหนึ่งต่อไปนี้เป็นหลัก ให้พิจารณา ที่ใช้แทน MediaStore API

การทำงานกับไฟล์ประเภทอื่น

หากแอปทำงานกับเอกสารและไฟล์ที่ไม่มีสื่อเพียงอย่างเดียว เช่น ไฟล์ที่ใช้นามสกุลไฟล์ EPUB หรือ PDF ให้ใช้ ACTION_OPEN_DOCUMENT การดำเนินการ ตามที่อธิบายไว้ในคำแนะนำเกี่ยวกับการจัดเก็บ และเข้าถึงเอกสารและอื่นๆ

การแชร์ไฟล์ในแอปที่ใช้ร่วมกัน

ในกรณีที่คุณจัดหาชุดแอปที่ใช้ร่วมกัน เช่น แอปรับส่งข้อความ แอปโปรไฟล์ ตั้งค่าการแชร์ไฟล์ โดยใช้ URI แบบ content:// และเราขอแนะนำเวิร์กโฟลว์นี้ เนื่องจากความปลอดภัยที่ดีที่สุด ฝึกหัด

แหล่งข้อมูลเพิ่มเติม

หากต้องการข้อมูลเพิ่มเติมเกี่ยวกับวิธีจัดเก็บและเข้าถึงสื่อ โปรดดูข้อมูลต่อไปนี้ ที่ไม่ซับซ้อน

ตัวอย่าง

  • MediaStore ซึ่งมีอยู่ใน GitHub

วิดีโอ