"เขียนแอปมาซะดิบดี เทสทุกอย่างก็ผ่านหมดแล้ว แต่พอปล่อยให้ใช้งานจริง กลับเจอปัญหาว่าแอปพังซะงั้น"
เชื่อว่านักพัฒนาส่วนใหญ่คงคุ้นเคยกับสถานการณ์แบบนี้กันเป็นอย่างดี เจ้าของบล็อกจึงขอหยิบประเด็นนี้มาเล่าเพื่อให้นักพัฒนาหน้าใหม่เข้าใจถึงปัญหาที่อาจจะเกิดขึ้นได้และหาวิธีแก้ไข จะได้ไม่ต้องเสียเวลาโดยใช่เหตุ
และเพื่อให้เข้าใจถึงต้นเหตุของปัญหามากขึ้น มาทำความรู้จักกับสิ่งที่เรียกว่า Android Fragmentation กันเถอะ
เพราะแต่ละเครื่องไม่ได้เหมือนกันเสมอไป
ด้วยความที่แอนดรอยด์เป็น Open-source Mobile Platform ทำให้แต่ละแบรนด์สามารถนำไปพัฒนาเป็นอุปกรณ์แอนดรอยด์ในรูปแบบของตัวเองได้ตามใจชอบ ส่งผลให้นักพัฒนาจะต้องทำแอปให้รองรับกับอุปกรณ์แอนดรอยด์ให้มากที่สุดเท่าที่ทำได้
ความหลากหลายของอุปกรณ์แอนดรอยด์ที่มีผลต่อการพัฒนาแอปจะเรียกกันว่า Android Fragmentation
Android Fragmentation เป็นเรื่องพื้นฐานสำหรับนักพัฒนาแอนดรอยด์
การพัฒนาแอปบนแอนดรอยด์เพื่อรองรับแค่อุปกรณ์แอนดรอยด์รุ่นเดียวนั้นไม่มีอยู่จริง ถึงแม้ว่าวันนี้จะบอกว่าแอปรองรับแค่อุปกรณ์แอนดรอยด์รุ่นเดียวเท่านั้น แต่เมื่อวันเวลาผ่านไปหลายปีก็จะพบว่าต้องทำแอปให้รองรับกับอุปกรณ์แอนดรอยด์รุ่นใหม่ ๆ อยู่ดี เพราะรุ่นเดิมที่เคยใช้อยู่ไม่สามารถหาซื้อได้อีกต่อไปแล้ว
เพราะการแก้ Legacy Code ให้รองรับ Android Fragmentation ในบางครั้งอาจจะหมายถึงการลบโค้ดทั้งหมดแล้วเขียนใหม่ทั้งหมดก็เป็นได้
ดังนั้นในการพัฒนาแอปแบบระยะยาว (Long Term) ให้รองรับกับ Android Fragmentation ตั้งแต่แรกจะช่วยลด Technical Debt จากการคอยดูแลและปรับปรุงแอปในอนาคตได้
ปัจจัยที่ส่งผลต่อ Android Fragmentation
Android Fragmentation เกิดมาจากหลาย ๆ ปัจจัยรวมกัน ซึ่งแอปของนักพัฒนาแต่ละคนก็จะได้รับผลกระทบจากปัจจัยที่แตกต่างกันออกไป ขึ้นอยู่กับการทำงานของแอปนั้น ๆ ว่ามีฟีเจอร์อะไรที่เกี่ยวข้องกับปัจจัยต่าง ๆ ของ Android Fragmentation
โดยปัจจัยสำคัญที่นักพัฒนาควรรู้ มีดังนี้
Screen Size & Resolution
ด้วยขนาดหน้าจอที่เยอะมากมายของอุปกรณ์แอนดรอยด์ที่มีอยู่ในทุกวันนี้ จึงเป็นสาเหตุหลักที่ทำให้เกิดปัญหาว่า UI ของแอปแสดงผลไม่ถูกต้องตามที่ตั้งใจไว้
ดังนั้นการสร้าง Responsive UI จะช่วยแก้ปัญหาที่เกิดจากขนาดหน้าจอได้ โดยบนแอนดรอยด์ก็จะมี Guideline ให้ทำตามอยู่แล้ว เช่น
- ใช้ Dynamic Dimension Value อย่าง Wrap Content, Match Parent หรือ Match Constraint สำหรับ View ที่มี Dynamic Content
- ใช้ Dimension Unit อย่าง DP และ SP เพื่อรองรับกับความละเอียดหน้าจอหลาย ๆ แบบได้ง่ายขึ้น
- ใช้ Layout Preview เพื่อดู Device Preview หลาย ๆ แบบในระหว่างการสร้าง UI
- ฯลฯ
Android Version
ในแต่ละปีแอนดรอยด์จะมีการปล่อยอัปเดตเวอร์ชันใหม่ แต่ก็ไม่ได้หมายความว่าอุปกรณ์แอนดรอยด์ทุกเครื่องจะได้อัปเดตเป็นเวอร์ชันใหม่เสมอไป ทำให้การพัฒนาแอปจะต้องคำนึงถึงเวอร์ชันเก่า ๆ ด้วย
โดยแต่ละเวอร์ชันก็จะมี Behavior Changes หรือ API Changes แตกต่างกันออกไป ทำให้คำสั่งบางอย่างทำงานได้แค่ในบางเวอร์ชัน หรือบางคำสั่งก็จะมีให้ใช้งานได้เฉพาะในเวอร์ชันใหม่ ๆ เท่านั้น
ในกรณีที่แอปรองรับแอนดรอยด์เวอร์ชันเก่าและมีการเรียกใช้งานคำสั่งที่อยู่ในเวอร์ชันใหม่กว่า โดยปกติแล้ว Android Studio จะมี Lint ที่คอยเช็คโค้ดเหล่านี้ให้อยู่แล้ว แต่นักพัฒนาก็ควรจะเรียนรู้คำสั่งและความสามารถในเวอร์ชันเก่าที่แอปจะต้องรองรับด้วย เพื่อแยกโค้ดสำหรับแต่ละเวอร์ชันให้เหมาะสม
Device Configuration
เพื่อให้ตอบโจทย์การใช้งานที่หลากหลายรูปแบบ ทำให้ระบบแอนดรอยด์มีสิ่งที่เรียกว่า Configuration เพื่อเอาไว้กำหนดรูปแบบในการใช้งานอุปกรณ์แอนดรอยด์ เช่น
- Locale : ภาษาที่ใช้งาน
- Orientation : หน้าจอแสดงผลแบบแนวตั้งหรือแนวนอน
- Night Mode : แสดง UI แบบ Light Mode หรือ Dark Mode
- ฯลฯ
ซึ่งค่าเหล่านี้สามารถส่งผลต่อการแสดงผลของแอปได้ ดังนั้นจึงควรทดสอบด้วย Configuration ที่แตกต่างกันออกไป และอย่าลืมใช้ความสามารถ Configuration Qualifier ของ Android Resource เพื่อแยกรูปแบบในการแสดงผลของ Configuration ตามที่ต้องการด้วย
Hardware Feature
นอกเหนือจากเรื่องของความละเอียดหน้าจอแล้ว อุปกรณ์แอนดรอยด์ก็มีฮาร์ดแวร์ข้างในหลากหลายที่สามารถส่งผลต่อการทำงานของโค้ดได้ ไม่ว่าจะเป็น
- Camera Module
- Cellular Capability
- Sensor Module
- SD Card Support
- ฯลฯ
สำหรับแอปที่มีการเรียกใช้งาน Hardware Feature ควรมีโค้ดเพื่อเช็คก่อนว่าอุปกรณ์แอนดรอยด์นั้น ๆ มีให้เรียกใช้งานหรือไม่ รวมไปโค้ดสำหรับการตั้งค่าเพื่อใช้งาน Hardware Feature เหล่านั้นด้วย เช่น แอปที่เรียกใช้งานกล้องก็จะต้องมีโค้ดเช็คความสามารถของกล้องภายในเครื่องเพื่อตั้งค่าตามเครื่องนั้น ๆ แทนที่จะกำหนดค่าไว้ตายตัว (กล้องหน้า/หลัง, ความละเอียดในการถ่ายภาพ/แสดงผล เป็นต้น) เพราะไม่ใช่ทุกเครื่องที่จะมี Hardware Feature ที่เหมือนกันเสมอไป
RAM
อุปกรณ์แอนดรอยด์ในแต่ละเครื่องจะมี RAM หรือ Memory ที่แตกต่างกันออกไป โดยจะสัมพันธ์กับสเปคอย่างอื่นด้วย เช่น ความละเอียดหน้าจอ จึงทำให้อุปกรณ์แอนดรอยด์แต่ละรุ่นมี Heap Size และ Max Heap Size ต่างกัน จึงทำให้การทำงานบางอย่างที่ต้องใช้ Memory เป็นจำนวนเยอะจะต้องคำนึงถึงอุปกรณ์แอนดรอยด์ที่มี Memory ต่ำด้วย เพื่อไม่ให้เกิดปัญหา Out of Memory จากการทำงานของแอป
สำหรับ Heap Size และ Max Heap Size ของอุปกรณ์แอนดรอยด์แต่ละเครื่องจะไม่ได้มีค่าเท่ากับ RAM ของเครื่องนั้น ๆ โดยตรง แต่จะถูกกำหนดผ่าน Firmware อีกทีเพื่อไม่ให้แอปแต่ละตัวใช้งาน Memory สูงเกินจำเป็น เช่น เครื่องที่มี RAM 8GB ก็อาจจะมี Heap Size 256MB และ Max Heap Size 512MB เป็นต้น
โดยปกติแล้วปัญหา Out of Memory ที่เกิดขึ้นจากแอปส่วนใหญ่มักจะเกิดจากนักพัฒนาเขียนโค้ดจัดการข้อมูลภาพไม่ถูกต้องทำให้ใช้ Memory เยอะเกินจำเป็น ดังนั้นปัญหาจึงแก้ได้ด้วยการทำตาม Guideline จาก Android Developers หรือใช้ 3rd Party Library เข้ามาใช้งานแทนการเขียนเอง
Device Firmware
Firmware ของแอนดรอยด์นั้นจะขึ้นอยู่กับการปรับแต่งจากแต่ละแบรนด์ โดย Pure Android คือชื่อเรียก Firmware ที่ผ่านการปรับแต่งน้อยมากจนแทบจะใกล้เคียงกับ Firmware ดั้งเดิมที่ทาง Google ปล่อยออกมา
บางแบรนด์จะมีการปรับแต่ง Firmware อีกที่เพื่อทำเป็น Firmware สำหรับอุปกรณ์แอนดรอยด์ของตัวเอง เช่น One UI ของ Samsung, ColorOS ของ OPPO, MIUI ของ Xiaomi เป็นต้น จึงทำให้การทำบางอย่างแตกต่างไปจาก Pure Android เล็กน้อย
จึงทำให้การเรียกใช้งานบางอย่างภายในเครื่องให้ผลลัพธ์ที่แตกต่างกันออกไปในแต่ละ Firmware ได้เช่นกัน เช่น
- System App หรือ Pre-installed App ที่ต่างกัน ซึ่งอาจจะส่งผลกับแอปที่ต้องการข้อมูลจากแอปอื่น ๆ ได้
- Battery Optimization ที่ Firmware แต่ละเจ้าทำเพิ่มเพื่อพยายามทำให้แบตเตอรีใช้งานได้ยาวนานมากที่สุด แต่ในขณะเดียวกันก็ส่งผลกระทบต่อการทำงานของ 3rd Party App ด้วยเช่นกัน
- Notification Badge ที่ขึ้นอยู่กับ Launcher ที่ใช้ โดย Launcher ของ Pure Android จะไม่แสดงตัวเลขจำนวน Notification ในขณะที่ Launcher ตัวอื่นมี ทำให้ต้องใช้โค้ดแยกสำหรับแต่ละ Launcher
- Notification UI ที่แต่ละแบรนด์ก็พยายามออกแบบให้สอดคล้องกับ UI ในส่วนอื่น ๆ ของ Firmware ตัวเอง
- การแสดงผลของ Toast บน Samsung ที่จะแสดง Toast อยู่เหนือ On-screen Keyboard ในขณะที่แบรนด์อื่น ๆ จะแสดงผลทับอยู่บน On-screen Keyboard
- SDK พิเศษสำหรับ Samsung หรือ Sony เพื่อใช้งานฟีเจอร์เฉพาะทางของแบรนด์นั้น ๆ
- ฯลฯ
บางอย่างนักพัฒนาสามารถแก้ปัญหาด้วยโค้ดได้ แต่ส่วนใหญ่มักจะทำไม่ได้เฉพาะการทำงานของ Firmware นั้นอยู่เหนือกว่าการทำงานของแอป จึงทำให้บ่อยครั้งแอปไม่สามารถควบคุมการทำงานที่ถูกบังคับมาจาก Firmware ได้เลย
CPU ABI
การทำงานที่ต้องใช้ Android NDK ไม่ว่าจะเป็นโค้ดที่เขียนเองหรือ 3rd Party Library ก็จะมีเรื่องของ CPU ABI ของอุปกรณ์แอนดรอยด์แต่ละเครื่องเข้ามาเกี่ยวข้องด้วยเสมอ โดยบนแอนดรอยด์จะรองรับ CPU ABI อยู่ 2 แบบคือ ARM และ x86 ทั้งแบบ 32-bit และ 64-bit
สำหรับการทำงานระดับ Low Level ของแอนดรอยด์จะต้องใช้ Android NDK ที่เขียนด้วย C++ ได้ แต่ในขณะเดียวกันการทำงานระดับ Low Level ก็จะไม่ได้ขึ้นอยู่กับระบบแอนดรอยด์ จึงทำให้มีเรื่องของ CPU ABI เข้ามาเกี่ยวข้องด้วย ไม่เช่นนั้นจะใช้งานบนแอนดรอยด์ไม่ได้
โดยในปัจจุบันอุปกรณ์แอนดรอยด์ใช้เป็น ARM แบบ 64-bit แทบทั้งหมด มีแค่เพียง Android Emulator เท่านั้นที่มี x86 ให้ใช้งานอยู่
แต่แอปพังก็ไม่ได้เกิดจาก Android Fragmentation เสมอไป
Android Fragmentation เป็นแค่หนึ่งใน Checklist สำหรับการพัฒนาแอปบนแอนดรอยด์เพื่อเลี่ยงปัญหาที่อาจจะเกิดขึ้นกับแอป แต่ทั้งนี้ก็ขึ้นอยู่กับโค้ดของนักพัฒนาด้วยเช่นกัน
ดังนั้นเพื่อมอบประสบการณ์ที่ดีให้กับผู้ใช้ และลดปัญหาจากแอปพังให้มากที่สุดเท่าทำได้ ก็อย่ามองข้ามเรื่อง Android Fragmentation กันนะ