10 February 2020

[Android Dev Tips] มากำหนด Authority ของ Content Provider ให้เหมาะสมกับแอปของคุณกันเถอะ


        Content Provider ถือเป็นหนึ่งใน Component พื้นฐานที่ใช้สำหรับควบคุมการเข้าถึงข้อมูลภายในเครื่องจากแอปต่างๆได้ ซึ่งในบทความนี้จะมาพูดถึงเรื่อง Authority ของ Content Provider กันล่ะ

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


        การทำเช่นนี้จะช่วยให้แอปปลายทางไม่จำเป็นต้องสนใจเลยว่า Data Storage ที่จะให้บันทึกไฟล์นั้นเป็นที่ไหน สนใจแค่ Content Provider ที่ส่งมาให้เท่านั้นก็พอ

        และการสร้าง Content Provider ขึ้นมาซักตัวหนึ่ง ก็จะต้องประกาศไว้ใน Android Manifest เหมือนกับ Component ตัวอื่นๆอย่าง Activity, Service และ Broadcast Receiver ด้วยทุกครั้ง

<!-- AndroidManifest.xml -->

<manifest ...>
    <application ...>
        <provider
            android:name="..."
            android:authorities="..."
            ...>
            ...
        </provider>
    </application>
</manifest>

        จะเห็นว่าตัวอย่างโค้ดข้างบนนี้จะมีการกำหนด Attribute ที่จำเป็นสำหรับ Content Provider อยู่ด้วย ซึ่งหนึ่งในนั้นก็คือ Authority นั่นเอง

Authority คืออะไร?

        Authority เป็นชื่อเฉพาะตัวของ Content Provider ที่นักพัฒนาจะต้องกำหนดเองทุกครั้งเพื่อให้ระบบแอนดรอยด์รู้จักกับ Content Provider ตัวนั้นๆ

        โดย Content Provider นั้นสามารถกำหนด Authority ได้มากกว่า 1 ตัว (แต่ส่วนใหญ่มักจะกำหนดแค่ตัวเดียว) และถ้าอิงตาม Guideline ของแอนดรอยด์ จะแนะนำให้ตั้งชื่อตาม Java-style naming convention แบบเดียวกับที่กำหนด Package Name ของแอปนั่นเอง

          ยกตัวอย่างเช่น

com.akexorcist.sleepingforless.imagepicker.provider

          การกำหนด Authority ของ Content Provider โดยอิงจาก Package Name แล้วใส่คำต่อท้ายเข้าไป เป็นวิธีที่ง่ายที่สุดและนักพัฒนาส่วนใหญ่ก็มักจะทำแบบนี้กัน

<!-- AndroidManifest.xml -->

<manifest ...>
    <application ...>
        <provider
            android:name="..."
            android:authorities="com.akexorcist.sleepingforless.imagepicker.provider"
            ...>
            ...
        </provider>
    </application>
</manifest>

         ในกรณที่มีหลาย Authority ก็ให้คั่นระหว่างชื่อด้วยเครื่องหมาย Semicolon

<!-- AndroidManifest.xml -->

<manifest ...>
    <application ...>
        <provider
            android:name="..."
            android:authorities="com.akexorcist.sleepingforless.imagepicker.provider;com.akexorcist.sleepingforless.filepicker.provider"
            ...>
            ...
        </provider>
    </application>
</manifest>

ชื่อที่กำหนดใน Authority จะต้องเป็น Unique เท่านั้น

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

        ดังนั้นถ้าผู้ใช้ติดตั้งแอป 2 ตัวที่มี Authority เหมือนกัน แอปที่ติดตั้งหลังสุดจะไม่สามารถติดตั้งลงในเครื่องได้นั่นเอง หรือที่เรียกกันว่า Conflicting Provider

จะเกิดอะไรขึ้นเมื่อเจอปัญหา Conflicting Provider?

ฝั่งนักพัฒนา

       นักพัฒนาอย่างเราๆก็จะรู้ได้ทันทีในตอนที่ติดตั้งแอปเพื่อทดสอบผ่าน Android Studio จะเจอข้อความที่บอกว่าไม่สามารถติดตั้งแอปได้เพราะ Conflicting Provider


        ถ้าติดตั้งไฟล์ APK ผ่าน ADB ก็จะได้ข้อความในลักษณะเดียวกัน


ฝั่งผู้ใช้ทั่วไป

        เนื่องจาก Content Provider ไม่ใช่เรื่องที่ผู้ใช้จำเป็นต้องรู้จัก ดังนั้นเวลาติดตั้งแอปผ่าน Google Play แล้วเจอปัญหาดังกล่าว สิ่งที่ผู้ใช้จะเห็นก็มีแค่หน้าต่างแจ้งว่าติดตั้งแอปไม่ได้ พร้อมกับวิธีแก้ไขที่ไม่ได้เกี่ยวกับเรื่องนี้เลยซักนิด


        ในกรณีที่ผู้ใช้เอา APK มาติดตั้งเองภายในเครื่องสิ่งที่ผู้ใช้จะเห็นก็คือ หน้าจอที่บอกว่าติดตั้งแอปไม่ได้ โดยไม่ได้มีสาเหตุระบุไว้ว่าทำไมถึงติดตั้งไม่ได้เช่นกัน


อย่าให้แอปต้องตกม้าตายเพียงเพราะเจอปัญหา Conflicting Provider

         เพราะการที่ผู้ใช้ไม่สามารถติดตั้งแอปลงในเครื่องได้ก็สามารถทำให้แอปสูญเสียโอกาสส่วนหนึ่ง และถ้าเป็นปัญหา Conflicting Provider ก็จะวิเคราะห์หาสาเหตุได้ยากมาก เนื่องจากฝั่งผู้ใช้ไม่สามารถเช็คอะไรได้เลย นอกจากนักพัฒนาจะทดลองสั่งติดตั้งไฟล์ APK ผ่าน ADB ดู ดังนั้นทางที่ดีจึงควรให้ความสำคัญในการตั้ง Authority ด้วย

        • อย่าใช้ชื่อ Authority ที่ก๊อปโค้ดมาจากอินเตอร์เน็ตโดยตรง เพราะโอกาสซ้ำเยอะมาก
        • ควรนำหน้าชื่อด้วย Package Name แล้วเพิ่มคำต่อท้าย เพื่อลดโอกาสซ้ำกับแอปอื่นๆ
        • ใช้ Application ID ใน Gradle จะได้ไม่ต้องพิมพ์ Package Name เองทุกครั้ง

ลดปัญหาการกำหนดชื่อ Authority ให้กับ Content Provider ด้วยการ Inject Application ID จาก Gradle ลงใน Android Manifest

         เนื่องจากการตั้งชื่อ Authority จะนิยมใช้ Package Name ของแอปเป็นคำนำหน้า ดังนั้นผู้ที่หลงเข้ามาอ่านจึงใช้ประโยชน์ของการกำหนด Application ID ใน Gradle เพื่อช่วยกำหนดชื่อให้กับ Authority ได้เช่นกัน

<!-- AndroidManifest.xml -->

<manifest ...>
    <application ...>
        <provider
            android:name="..."
            android:authorities="${applicationId}.imagepicker.provider"
            ...>
            ...
        </provider>
    </application>
</manifest>

         ซึ่งการทำแบบนี้นอกจากจะช่วยให้นักพัฒนาสามารถทำ Provider ตัวนี้ไปใช้งานได้ง่าย เพราะรองรับกับการทำ Build Variant ที่แยก Package Name ของแต่ละ Flavor อยู่แล้วด้วย จึงไม่ต้องกังวลว่าจะเจอปัญหา Conflicting Provider เมื่อติดตั้งแอปหลายๆ Variant ไว้ในเครื่องเดียวกัน


        นอกจากนี้ วิธีนี้ยังเหมาะกับการพัฒนาไลบรารีเพื่อให้แอปต่างๆนำไปใช้อีกด้วย เพื่อไม่ให้แอปที่นำไลบรารีไปใช้ต้องเจอปัญหา Conflicing Provider เพียงเพราะว่าใช้ไลบรารีตัวเดียวกัน


        ดังนั้นการตั้งชื่อ Authority จึงมีความสำคัญอย่างมาก ถึงแม้ว่าจะเป็นเรื่องเล็กๆ แต่ก็ไม่ควรมองข้าม เพราะถ้าเกิดปัญหาขึ้นมาแล้วตอนหาสาเหตุจะทำได้ยากมาก แถมเสียโอกาสที่ผู้ใช้จะได้ติดตั้งแอปและใช้งานแอปของเราอีกด้วย ควรตรวจสอบให้ถี่ถ้วนก่อนจะ Publish ขึ้น Google Play นะ