Android 11 การเปลี่ยนแปลงที่นักพัฒนาควรปรับตัว

Chaiwiwat Koksantia
Nextzy
Published in
3 min readMar 23, 2020

สวัสดีครับ ผมกลับมารายงานตัวอีกครั้งกับบทความใหม่ เรื่องที่ผมจะเล่าในบทความนี้มันเกิดมาจาก เมื่อวัน 20 กุมภาพันธ์ที่ผ่านมา Google ได้ปล่อย Android 11 เวอร์ชัน Developer Preview ออกมา เพื่อให้นักพัฒนาทั่วโลกได้ลองเล่นและทดสอบแอปกัน

โดยผมจะแบ่งออกเป็น 2 บทความ อันได้แก่

  • บทความแรก (Android 11 กับของใหม่ที่มาแบบเงียบๆ) จะอธิบายถึงฟีเจอร์ใหม่ ๆ ให้มุมมองของผู้ใช้งาน ว่าเราจะได้ของเล่นอะไรเพิ่มมาบ้าง ทั้งแบบเปิดตัวออกมาและแบบที่ซ่อนไว้ มีคนขุดคุ้ยเข้าไปเจอ
  • บทความที่สอง(Android 11 การเปลี่ยนแปลงที่นักพัฒนาควรปรับตัว) จะอธิบายถึงสิ่งที่นักพัฒนาแอปควรปรับตัวให้แอปที่มีอยู่ สามารถรองรับกับ Android 11

นักพัฒนาจะต้องเจออะไรบ้าง?

ใน Android 11 หรือ API 30 ใช่ว่าทุกอย่างจะเป็นของใหม่เอี่ยมทั้งหมด บางอันใหม่ถอดด้าม บางอันเจอกันมาแล้ว ยกตัวอย่างเช่น Scoped Storage มีการประกาศเปิดตัวไว้ตั้งแต่ Android 10 Developer Preview แต่เนื่องจากความไม่พร้อมของนักพัฒนาที่เรียกร้องเข้าไป จึงมีการเลื่อนมาใน Android 11 แทน

ปีนี้เข้าโหมดบังคับใช้ แบบจริงจัง ..

โดยสิ่งที่เปลี่ยนไปและอาจจะส่งผลให้แอปเก่าเกิดปัญหา ผมขอยกมาหลัก ๆ ดังนี้

Scoped Storage

คืออะไร?

Scoped Storage ออกแบบมาเพื่อป้องกันไม่ให้แอปพลิเคชันเข้าถึงระบบไฟล์บนอุปกรณ์ได้อย่างไม่จำกัด โดยก่อนหน้านี้แอปสามารถเข้าถีงไฟล์ต่าง ๆ บนอุปกรณ์ผ่าน File API มาตรฐาน และจำเป็นต้องให้ผู้ใช้งานกดมอบสิทธิ์การเข้าถึงพื้นที่จัดเก็บ หรือ Storage Permission

ข้อดี : เพิ่มความปลอดภัยและความเป็นส่วนตัวของผู้ใช้งานมากขึ้น

ผลกระทบ : หากอัปเดตไปใช้ API 29 โดยไม่มีการเปลี่ยนแปลงใด ๆ และพยายามเข้าถึงไฟล์โดยใช้ File API มาตรฐาน จะพบว่ามันจะไม่ทำงาน จำเป็นต้องเรียกใช้ Storage Access Framework (SAF) แทนเพื่อที่จะเปิดไฟล์หรือโฟลเดอร์ แต่ถ้าจะเปิดไฟล์มีเดีย จำพวกรูปภาพ เสียง หรือวิดีโอ ต้องเรียกผ่าน MediaStore และทำงานผ่าน URI ไม่สามารถใช้ File Path ได้ (เช่นเดียวกับการลบหรือเขียนทับ ที่จำเป็นต้องขอสิทธิ์การแก้ไขไฟล์ ด้วยสิทธิ์ WRITE_EXTERNAL_STORAGE แต่สิทธิ์นี้จะใช้ไม่ได้แล้ว)

แล้ว Android เวอร์ชันเก่ากว่าใช้ได้ไหม?

อย่างที่ทราบกัน Scoped Storage มีการบังคับให้ทุกแอปต้องใช้ ใน Android 11 แต่ในเวอร์ชันเก่ากว่า อย่าง API 29 สามารถเลือกไม่ใช้งานได้ โดยตัว Scoped Storage จะเป็นค่าเริ่มต้น ถ้าไม่ต้องการใช้ และกลับไปใช้แบบเดิมสามารถเข้าไปเซ็ตค่า android:requestLegacyExternalStorage=”true” ใน AndroidManifest.xml

ส่วนเวอร์ชันที่ต่ำกว่า API 29 จะไม่สามารถใช้ Scoped Storage ได้

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

การเข้าถึงไฟล์สื่อข้อมูล

ในการเรียกใช้ข้อมูลกับ MediaStore จะต้องเรียกผ่าน ContentResolver และหลังจากนั้นระบบจะทำการแสกนไฟล์จาก External Storage โดยอัตโนมัติ และเพิ่มลงในใน MediaStore แต่ละประเภทดังนี้

  • ไฟล์รูปภาพ : จำพวกภาพถ่าย และภาพหน้าจอ ที่เก็บไว้ใน DCIM/ และ Pictures/ จะเพิ่มลงใน MediaStore.Images
  • ไฟล์วิดีโอ : ที่ถูกเก็บอยู่ใน DCIM/, Movies/ และ Pictures/ จะเพิ่มลงใน MediaStore.Video
  • ไฟล์เสียง : ที่ถูกเก็บอยู่ใน Alarms/, Audiobooks/, Music/, Notifications/, Podcasts/ และ Ringtones/ รวมไปถึง ลิสท์เพลงใน Music/ หรือ Movies/ จะเพิ่มลงใน MediaStore.Audio
  • ไฟล์ดาวโหลด : ที่ถูกเก็บอยู่นใน Download/ จะเพิ่มลงใน MediaStore.Download โดยที่สามารถใช้ได้กับ Android 10 ขึ้นไปเท่านั้น แต่จะไม่มีผลใน Android 9 หรือต่ำกว่า

จะใช้ MediaStore ต้องขอสิทธิ์อะไรไหม?

สิทธิ์การเข้าถึง MediaStore มีอยู่ 2 ส่วนคือ

สิทธิ์ READ_EXTERNAL_STORAGE ครอบคลุมถึง

  • MediaStore.Images
  • MediaStore.Video
  • MediaStore.Audio

สิทธิ์ MANAGE_EXTERNAL_STORAGE ครอบคลุมถึง

  • MediaStore.Download
  • MediaStore.Files

มีอะไรใหม่อีกไหม?

เพื่อให้ง่ายต่อการเข้าถึงไฟล์สื่อข้อมูล ในขณะที่ยังรักษาความเป็นส่วนตัวของผู้ใช้อยู่ Android 11 จึงเพี่มความสามารถเข้ามา คือ

การดำเนินงานแบบเป็นชุด ๆ

ใน Android 11 ได้เพิ่มวิธีการต่าง ๆ เข้าไปใน MediaStore API โดยที่วิธีการเหล่านี้มีประโยชน์สำหรับแอปที่ต้องการความคล่องตัวในการปรับเปลี่ยนไฟล์สื่อข้อมูลที่มีความเฉพาะเจาะจง เช่น การแก้ไขสถานที่ในภาพถ่าย โดยวิธีดังต่อไปนี้

createWriteRequest()

เรียกร้องให้ผู้ใช้งานอนุญาตให้แอปมีสิทธิ์ในการเขียนไฟล์สื่อข้อมูลภายในกลุ่มที่มีการระบุเอาไว้

createFavoriteRequest()

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

createTrashRequest()

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

createDeleteRequest()

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

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

การเข้าถึงไฟล์โดยการใช้ Raw Paths

ใน Android 11 แอปที่มีสิทธิ์ READ_EXTERNAL_STORAGE สามารถอ่านไฟล์สื่อข้อมูล ภายในอุปกรณ์โดยใช้ File Pathโดยตรงและ Native Library

ความสามารถใหม่นี้ช่วยให้แอปทำงานได้ราบรื่นขึ้นเมื่อใช้กับ Third-Party Library

ข้อควรระวัง : ประสิทธิภาพของแอปจะลดลงเล็กน้อย เมื่อมีการบันทึกไฟล์สื่อข้อมูลโดยใช้ Direct Paths และ Native Library ซึ่งถ้าแอปสามารถปรับได้ ควรใช้ MediaStore API แทน

การเข้าถึงไฟล์ทั้งหมด

หากต้องการจะเข้าถึงไฟล์ทั้งหมด ต้องทำดังต่อไปนี้

  • ประกาศสิทธิ์ MANAGE_EXTERNAL_STORAGE
  • นำทางผู้ใช้งานให้ไปที่หน้าตั้งค่าระบบ ส่วนสิทธิ์ของแอป จากนั้นกด Allow access to manage all files

สิทธิ์จะได้มีอะไรบ้าง?

  • อ่านและเขียนไฟล์ในส่วนของ Share Storage
  • สามารถใช้งาน MediaStore.Files

มีข้อจำกัด?

ถึงแอปจะได้สิทธิ์ไฟล์ทั้งหมด แต่แอปจะไม่สามารถเข้าถึงไดเรกทอรีของแอปอื่น และไดเรกทอรีย่อยภายใน Android/Data/ ได้

ข้อจำกัดในการเข้าถึงไฟล์และไดเรกทอรี

Storage Access Framework (SAF) จะมีผลเฉพาะแอปที่ Target กับ Android 11 เท่านั้น

การเข้าถึงไดเรกทอรี

Android 11 จะไม่อนุญาตให้ใช้ ACTION_OPEN_DOCUMENT_TREE เพื่อ Intent ร้องขอการเข้าถึงไดเรกทอรีนี้ดังต่อไปนี้ได้

  • ไดเรกทอรีดาวโหลด
  • ไดเรกทอรีของ SDCard ที่ได้รับการยืนยันว่าเชื่อถือได้

เข้าถึงไฟล์

Android 11 จะไม่อนุญาตให้ใช้ ACTION_OPEN_DOCUMENT_TREE หรือ ACTION_OPEN_DOCUMENT เพื่อ Intent ร้องขอให้ผู้ใช้งานเลือกไฟล์จากไดเรกทอรีนี้ดังต่อไปนี้ได้

  • ไดเรกทอรี Android/Data/ และไดเรกทอรีย่อยภายใน
  • ไดเรกทอรี Android/Obb/ และไดเรกทอรีย่อยภายใน

ถ้าหากเรียกใช้งาน WRITE_EXTERNAL_STORAGE และ WRITE_MEDIA_STORAGE เพื่อเข้าถึงข้อมูลสิ่งที่จะแสดงขึ้นคือความว่างเปล่า

One-time permission

คืออะไร?

ใน Android 11 เมื่อแอปร้องขอสิทธิ์การเข้าถึง ตำแหน่งที่ตั้ง, ไมโครโฟน หรือ กล้อง จะขึ้น Permission Dialog โดยหนึ่งในตัวเลือกทั้งหมดนั้นจะมีตัวเลือกนึงเพิ่มขึ้นก็คือ “Only this time” หรือ “เฉพาะเวลานี้” โดยเมื่อเลือกตัวเลือกนี้ App จะสามารถเข้าถึง Permission ที่ขอนั้นได้ และเมื่อแอปอยู่ในสถานะที่นอกเหนือจากเงื่อนไขที่ระบบกำหนด สิทธิ์นั้นจะหายไป แอปจำเป็นต้องขอ Permission นั้นใหม่อีกครับ

เงื่อนไขมีอะไรบ้าง?

  • Activity ของแอป ผู้ใช้งานต้องสามารถมองเห็นได้
  • แอปต้องสามารถมองเห็นได้เมื่อผู้ใช้ให้สิทธิ์และต้องใช้งานบน Foreground service ตั้งแต่ตอนนั้น ตราบใดที่ Foreground service ยังทำงานอยู่ แอปจะยังได้รับสิทธิ์อยู่ แม้กระทั้งผู้ใช้ปิดแอปไปอยู่เบื้องหลังแล้วก็ตาม

โดยเมื่ออยู่ในสถานะที่นอกเหนือจากเงื่อนไขทั้งหมดข้างต้น สิทธิ์ที่ได้รับจะไม่สามารถใช้งานได้

แตกต่างกับ While using the app อย่างไร?

จุดเหมือน : ทั้งสองตัวเลือก Only this app และ While using the app จะไม่ได้รับสิทธิ์ในการทำงานเบื้องหลังเมื่ออยู่นอกเงื่อนไขที่กำหนดไว้

จุดแตกต่าง : หากเป็น While using the app เมื่ออยู่นอกเงื่อนไขที่กำหนดไว้ แอปจะไม่ได้รับสิทธิ์ในการถึง Permission นั้น ๆ ได้ จนกว่า แอปจะถูกเรียกใช้งานอีกครั้ง สิทธิ์นั้นจะกลับมาใช้ได้อีกครั้ง แตกต่างจาก Only this time ที่สิทธิ์นั้นจะไม่สามารถกลับมาได้อีก จนกว่าผู้ใช้งานจะอนุญาตอีกครั้ง

ถ้าอยาก Allow all the time ต้องทำอย่างไร?

ใน Android 11 ได้มีการนำตัวเลือกนี้ออกไปจาก Permission Dialog

หากต้องการที่จะเปิด จำเป็นต้องให้ผู้ใช้งานเข้าไปที่หน้าตั้งค่าสิทธิ์ของแอปในตั้งค่า แล้ว เลือกสิทธิ์ที่ต้องการให้แอปเข้าถึง จากนั้นเลือกเป็น Allow

ตัวอย่างหน้าตั้งค่าสิทธิ์การเข้าถึงกล้อง ของ Chrome

Permission Dialog Visibility

อย่างที่เราทราบกันว่าเมื่อเราขอสิทธิ์การเข้าถึงต่าง ๆ จะมีการแสดง Permission Dialog ให้ผู้ใช้กดตัวเลือกในการมอบสิทธิ์ให้กับแอป

โดย Android 11 ไม่เห็นด้วยกับการร้องขอสิทธิ์ซ้ำ ๆ ในกลุ่มของสิทธิ์ที่มีเฉพาะเจาะจง หากผู้ใช้มีการกด Deny 2 ครั้งในช่วงที่มีแอปมีการติดตั้งอยู่บนอุปกรณ์ นั้นจะมีความหมายว่า “Don’t ask again” สำหรับสิทธิ์ที่มีความสอดคล้องกัน

มีเงื่อนไขอย่างไร?

ระบบจะมีการจำกัดความหมายของพฤติกรรมการกด Deny ไว้ว่า

  • ถ้าผู้ใช้งานกดปุ่มย้อนกลับเพื่อปิด Permission Dialog การกระทำนั้นจะ ไม่ถูกนับเป็น “Deny”
  • ถ้าผู้ใช้งานถูกให้เข้า ตั้งค่าระบบ จากแอปด้วยคำสั่ง requestPermissions() และเมื่อมีการกดปุ่มย้อนกลับ การกระทำนั้นจะ ถูกนับเป็น “Deny”

สรุป

จากการมาของ Android 11 และการประกาศบังคับใช้ Scoped Storage จึงส่งผลต่อแอปที่เข้าถึงไฟล์อย่างหลีกเลี่ยงไม่ได้ อีกทั้งยังต้องทำแอปให้รองรับกับ Android API เวอร์ชันต่ำกว่า 29 ที่ไม่สามารถรองรับ Scoped Storage ให้สามารถใช้ได้ด้วย อย่างไรก็หวังว่าบทความจะมีประโยชน์ไม่มากก็น้อย หากผิดพลาดตกหล่นประการใด ผมขออภัยไว้ ณ ตรงนี้ด้วย ไว้เจอกันบทความถัดไปครับ OwO)/

--

--