บทความนี้สำหรับนักพัฒนาที่พัฒนาแอปที่จำเป็นต้องบันทึกรูปลงในเครื่อง แต่พอบันทึกเสร็จแล้ว กลับเปิดหาใน Image Gallery ไม่เจอซะงั้น
ผู้ที่หลงเข้ามาอ่านก็ประสบปัญหาแบบนี้เหมือนกันหรอ? อ่านบทความนี้ได้เลย
ปัญหานี้จะเกิดขึ้นก็ต่อเมื่อนักพัฒนาบันทึกไฟล์ภาพด้วยวิธีอื่นที่ไม่ได้ใช้ MediaStore โดยตรง แต่ถ้าปัญหานี้เกิดขึ้นบนแอนดรอยด์เวอร์ชันสูงกว่า API 29 ขึ้นไป แปลว่ากำลังทำผิดวิธีอยู่นะ เพราะว่าบนเวอร์ชันดังกล่าวให้เปลี่ยนไปใช้วิธีบันทึกภาพด้วย MediaStore โดยตรงแล้ว
ทำไมถึงเกิดปัญหาแบบนี้ได้?
ต้องเข้าใจก่อนว่าถ้าแอปที่ใช้แสดงรูปในเครื่องต้องสแกนหาไฟล์รูปในเครื่องทั้งหมด มันจะทำให้เครื่องต้องทำงานหนักและใช้เวลานานมาก (และผู้ใช้ก็จะไม่แฮปปี้ถ้าจะต้องรอ)
ดังนั้นแอนดรอยด์จึงมีตัวกลางในการจัดการเรื่องนี้ให้ ซึ่งมีชื่อเรียกว่า MediaStore ที่คอยเก็บข้อมูลจำพวก Media (ไฟล์รูป, ไฟล์วีดีโอ หรือไฟล์เสียง) แล้วเวลาแอปตัวไหนก็ตามต้องการแสดงไฟล์ Media พวกนี้ภายในเครื่อง ก็จะใช้ข้อมูลที่มาจาก MediaStore นั่นเอง
![](https://akexorcist.dev/content/images/2020/06/update_image_android_media_scanner-001-1.png)
แต่ MediaStore ก็ไม่ได้คอยเช็คไฟล์ที่อยู่ในเครื่องตลอดเวลา (อย่างที่บอกว่ามันจะทำให้เครื่องทำงานหนัก) เมื่อแอปตัวไหนทำอะไรกับไฟล์ Media ภายในเครื่องก็ตาม ไม่ว่าจะเพิ่มไฟล์ ย้ายไฟล์ หรือลบไฟล์ก็ตาม แอปจะต้องส่งข้อมูลไปบอก MediaStore ด้วย เพื่อให้ MediaStore รู้ว่ามีไฟล์ใหม่เพิ่มเข้ามานั่งเอง
![](https://akexorcist.dev/content/images/2020/06/update_image_android_media_scanner-002-1.png)
ดังนั้นหัวใจสำคัญไม่ได้มีแค่ 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 จะอัปเดตข้อมูลโดยอัตโนมัติ