19 October 2013

เมื่อต้องเจอคำสั่งใน Android API ที่เรียกใช้งานต่างกันในแต่ละเวอร์ชัน

Created on Saturday, October 19, 2013

เป็นเรื่องธรรมดาสำหรับนักพัฒนาแอนดรอยด์ที่เขียนโค้ดในบางครั้งแล้วพบว่าการทำงานบางอย่างของแอนดรอยด์จะต้องเขียนโค้ดแยกกันตามเวอร์ชันด้วย

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

เจ้าของบล็อกต้องการเขียนโค้ดเพื่อเช็คขนาดของหน้าจอ แล้วพบว่าคำสั่งสำหรับเช็คขนาดหน้าจอมี 2 แบบ

// Android 4.2 Jelly Bean (API 17) or higher
val windowManager = ...
val display = windowManager.defaultDisplay
val metrics = DisplayMetrics()
display.getRealMetrics(metrics)
Pair(metrics.widthPixels, metrics.heightPixels)

// Lower than Android 4.2 Jelly Bean (API 17)
val windowManager = ...
try {
    val rawHeight: Method = Display::class.java.getMethod("getRawHeight")
    val rawWidth: Method = Display::class.java.getMethod("getRawWidth")
    Pair(rawWidth.invoke(display) as Int, rawHeight.invoke(display) as Int)
} catch (e: Exception) {
    val display = windowManager.defaultDisplay
    Pair(display.width, display.height)
}

จะเห็นว่าคำสั่งระหว่าง 2 เวอร์ชันนั้นมีโค้ดที่แตกต่างกันโดยสิ้นเชิง นั่นก็เพราะว่าใน API 17 ได้มีการเพิ่มคำสั่งใหม่ๆเข้ามา รวมไปถึงการเช็คขนาดหน้าจอด้วยเช่นกัน

และนักพัฒนาก็จะต้อง

แยกโค้ดตามเวอร์ชันนั่นเอง

ด้วยการใช้ If-else เพื่อเช็คว่าแอปกำลังทำงานอยู่บนอุปกรณ์แอนดรอยด์เวอร์ชันอะไร เพื่อให้คำสั่งทำงานตรงกับเวอร์ชันนั้นๆ

fun getScreenResolution(windowManager: WindowManager): Pair<Int, Int>? {
    val display = windowManager.defaultDisplay
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        val metrics = DisplayMetrics()
        display.getRealMetrics(metrics)
        Pair(metrics.widthPixels, metrics.heightPixels)
    } else {
        try {
            val rawHeight: Method = Display::class.java.getMethod("getRawHeight")
            val rawWidth: Method = Display::class.java.getMethod("getRawWidth")
            Pair(rawWidth.invoke(display) as Int, rawHeight.invoke(display) as Int)
        } catch (e: Exception) {
            Pair(display.width, display.height)
        }
    }
}

ซึ่งโค้ดในลักษณะนี้สามารถพบเจอได้บ่อยมาก เพราะมีคำสั่งอีกมากมายที่ถูกเปลี่ยนแปลงในแอนดรอยด์แต่ละเวอร์ชัน

API Reference จึงเป็นสิ่งสำคัญสำหรับนักพัฒนา

การศึกษาคำสั่งต่างๆใน API Reference ในเว็ป Android Developers จึงเป็นสิ่งสำคัญมากๆสำหรับนักพัฒนาแอนดรอยด์


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

แต่ไม่ใช่ทุกคำสั่งที่จะทำงานได้ในทุกเวอร์ชัน

ก็ต้องมีบ้างที่ฟีเจอร์ใหม่ๆมาพร้อมกับแอนดรอยด์เวอร์ชันใหม่ๆ ดังนั้นเวอร์ชันเก่าๆจึงไม่ต้องพูดถึง


เมื่อเจอกับกรณีที่ต้องเรียกใช้คำสั่งที่มีแค่ใน API เวอร์ชันใหม่ๆ ก็ควรใส่ Annotion ที่ชื่อ @RequireApi ไว้ที่คำสั่งนั้นๆด้วย เพื่อให้ Android Studio (และนักพัฒนา) รู้ว่า Method ดังกล่าวมีการเรียกใช้คำสั่งของ API เวอร์ชันใหม่ๆ

@RequiresApi(Build.VERSION_CODES.O)
fun initNotificationChannel(context: Context) {
    ...
}

เมื่อมีการเรียกใช้งานคำสั่งนี้ที่ไหนก็ตาม Android Studio จะทำการเช็คทันทีว่าโค้ดในจุดนั้นมีโอกาสที่จะทำงานบนแอนดรอยด์เวอร์ชันที่ต่ำกว่านั้นหรือไม่ แล้วจะแจ้งเตือนให้นักพัฒนารู้ว่าต้องเขียนโค้ดเพื่อป้องกันกรณีนี้ด้วย 

ซึ่งจริงๆแล้ว Android API ที่ใช้งานกันอยู่ทุกวันนี้ก็มีการใส่ @RequireApi ไว้เพื่อให้นักพัฒนารู้ว่าคำสั่งนั้นๆใช้ได้เฉพาะบนแอนดรอยด์เวอร์ชันไหน

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