บทความนี้สำหรับนักพัฒนาที่พัฒนาแอปที่จำเป็นต้องบันทึกรูปลงในเครื่อง แต่พอบันทึกเสร็จแล้ว กลับเปิดหาใน Image Gallery ไม่เจอซะงั้น

ผู้ที่หลงเข้ามาอ่านก็ประสบปัญหาแบบนี้เหมือนกันหรอ? อ่านบทความนี้ได้เลย

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

ทำไมถึงเกิดปัญหาแบบนี้ได้?

ต้องเข้าใจก่อนว่าถ้าแอปที่ใช้แสดงรูปในเครื่องต้องสแกนหาไฟล์รูปในเครื่องทั้งหมด มันจะทำให้เครื่องต้องทำงานหนักและใช้เวลานานมาก (และผู้ใช้ก็จะไม่แฮปปี้ถ้าจะต้องรอ)

ดังนั้นแอนดรอยด์จึงมีตัวกลางในการจัดการเรื่องนี้ให้ ซึ่งมีชื่อเรียกว่า MediaStore ที่คอยเก็บข้อมูลจำพวก Media (ไฟล์รูป, ไฟล์วีดีโอ หรือไฟล์เสียง) แล้วเวลาแอปตัวไหนก็ตามต้องการแสดงไฟล์ Media พวกนี้ภายในเครื่อง ก็จะใช้ข้อมูลที่มาจาก MediaStore นั่นเอง

แต่ MediaStore ก็ไม่ได้คอยเช็คไฟล์ที่อยู่ในเครื่องตลอดเวลา (อย่างที่บอกว่ามันจะทำให้เครื่องทำงานหนัก) เมื่อแอปตัวไหนทำอะไรกับไฟล์ Media ภายในเครื่องก็ตาม ไม่ว่าจะเพิ่มไฟล์ ย้ายไฟล์ หรือลบไฟล์ก็ตาม แอปจะต้องส่งข้อมูลไปบอก MediaStore ด้วย เพื่อให้ MediaStore รู้ว่ามีไฟล์ใหม่เพิ่มเข้ามานั่งเอง

ดังนั้นหัวใจสำคัญไม่ได้มีแค่ MediaStore เท่านั้น แต่รวมไปถึงแอปที่มีการจัดการกับไฟล์ในเครื่องด้วย

และนั่นก็คือเหตุผลที่ว่าทำไมเวลาผู้ที่หลงเข้ามาอ่านทำแอปที่มีการเพิ่มไฟล์ภาพลงในเครื่อง แล้วเวลาเปิดหาในแอปจำพวก Image Gallery กลับหาไม่เจอ นั่นก็เพราะว่าไม่ได้ส่งข้อมูลบอก MediaStore น่ะสิ!

แต่สำหรับการส่งข้อมูลบอก MediaStore นั้น ในปัจจุบันแบ่งออกเป็น 2 วิธีด้วยกัน โดยแยกระหว่างเวอร์ชันที่ต่ำกว่า API 29 และเวอร์ชันตั้งแต่ API 29 ขึ้นไป

ต่ำกว่า API 29 - ส่งข้อมูลผ่าน Broadcast

เป็นวิธีมาตรฐานบนแอนดรอยด์นั่นเอง โดยแนบข้อมูลไว้ใน Intent เพื่อระบุที่อยู่ของไฟล์ภาพ

fun updateImagePath(context: Context, path: String) {
    val file = File(path)
	updateImagePath(context, file)
}

fun updateImagePath(context: Context, file: File) {
    val uri = Uri.fromFile(file)
	updateImagePath(context, uri)
}

fun updateImagePath(context: Context, uri: Uri) {
    val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
    intent.data = uri
    context.sendBroadcast(intent)
}

โค้ดข้างบนนี้ทำไว้ทั้งหมด 3 แบบด้วยกัน เพื่อให้สะดวกต่อการรับข้อมูลได้ทั้งแบบ Path แบบ String, File และ Uri

จะเห็นว่ามีการกำหนด Action ของ Intent เป็น ACTION_MEDIA_SCANNER_SCAN_FILE ซึ่งเป็น Action สำหรับ MediaStore นั่นเอง และแนบข้อมูลไว้ใน Data ของ Intent

ตั้งแต่ API 29 ขึ้นไป - ส่งข้อมูลเข้า MediaStore โดยตรง

บนแอนดรอยด์ตั้งแต่ API 29 ขึ้นไป ทีมพัฒนาแอนดรอยด์ได้เปลี่ยนรูปแบบในการเข้าถึงไฟล์ภายในเครื่องทั้งหมด จึงส่งผลให้วิธีเก่าๆอย่างการส่ง Broadcast ถูกยกเลิกไปด้วย เพราะว่าไม่ได้บักทึกไฟล์ภาพลงในเครื่องด้วยคำสั่งแบบเดิมๆกันแล้ว

โดยการบันทึกไฟล์รูปลงในเครื่องจะต้องทำผ่าน ContentResolver เท่านั้น ซึ่งจะมีการกำหนดสิ่งที่เรียกว่า ContentValues ด้วย ซึ่งในนั้นจะสามารถกำหนด Directory ที่จะบันทึกไฟล์รูปได้

val directory = "Pictures/YourAppAlbums"
val values = ContentValues().apply {
    ...
    put(MediaStore.Images.Media.RELATIVE_PATH, directory)
}

นอกจากนี้สำหรับ API 29 ขึ้นไป ถ้าผู้ที่หลงเข้ามาอ่านบันทึกภาพลงใน Default Directory ของไฟล์รูปอย่าง Environment.DIRECTORY_PICTURES อยู่แล้ว ก็ไม่จำเป็นต้องกำหนด RELATIVE_PATH ก็ได้ เพราะว่า MediaStore จะอัปเดตข้อมูลโดยอัตโนมัติ