สรุปเนื้อหาสำคัญจากงาน Android Flash Talk ณ เชียงใหม่
พอดีช่วงนี้เจ้าของบล็อกมาใช้ชีวิตอยู่ที่เชียงใหม่พักนึง แล้วเกิดเหงาขึ้นมาก็เลยจัดงานเล็กๆที่ชื่อว่า Android Flash Talk ขึ้นมาเพื่อพบปะพูดคุยกับ Android Developer ที่เชียงใหม่ ถึงแม้ว่าจะเป็น Flash Talk ก็จริง แต่ในวันนั้นก็ปาไปหลายชั่วโมงอยู่เหมือนกัน คุยอะไรกันเยอะมาก เลยอยากจะหยิบเนื้อหาที่พูดคุยกันในงานมาเล่าสู่กันฟังครับ
Android Development Checklists
เพราะอยากให้เป็นงานที่ Android Dev คนไหนๆก็เข้ามาฟังได้ ไม่ถึงขั้น Advance มากนัก ก็เลยพูดถึง Checklists ที่ควรเช็คทุกครั้งเวลาพัฒนาแอพนี่แหละ โดยมีดังนี้
ชื่อตัวแปรไม่สื่อหรือป่าว?
อันนี้เป็นพื้นฐานของการเขียนโค้ดมากกว่า ว่าเวลาเขียนโค้ดแล้วต้องตั้งชื่ออะไรก็ตามไม่ว่าจะเป็นตัวแปร, เมธอด หรือคลาสเนี่ย มันต้องสื่ออยู่เสมอว่าสิ่งนั้นทำหน้าที่อะไร
override fun createStatementWithBalance(balance: String) {
...
}
override fun createStatementWithBalanceAndCurrentCharges(balance: String, currentCharges: String) {
...
}
อาจจะดูยืดยาวและต้องพิมพ์เยอะ แต่การตั้งชื่อยาวๆที่สื่อความหมายจะช่วยให้คนอื่นในทีมหรือแม้แต่ตัวเราเอง สามารถมาไล่โค้ดดูทีหลังแล้วเข้าใจได้ง่าย แถมไม่ต้องเสียเวลาเขียน Comment เพื่ออธิบายโค้ดอีก
fun isBalanceLessThan50Baht(statement: Statement): Boolean {
return isDataAvailable(statement) && Float.compare(statement.getRemaining().getData().getBalance(), 50.0f) < 0
}
ถ้าดูจากตัวอย่างโค้ดข้างบนนี้ก็จะเห็นว่าเจ้าของบล็อกแทบจะไม่ต้องใช้เวลาคิดอะไรเลย เพราะเห็นชื่อ Method ก็เข้าใจได้ทันทีว่าข้างในเปนโค้ดเช็คว่าค่า Balance ต่ำกว่า 50 หรือป่าว
Name Convention เหมือนกันทั้งโปรเจคหรือป่าว?
อันนี้เป็นคำถามต่อจากข้อก่อนหน้า ซึ่งการตั้งชื่อในโค้ดนั้นสำคัญมากและควรจะมีรูปแบบชื่อที่คล้ายๆกันหรือตายตัวเพื่อให้อ่านได้ไวขึ้น ไม่ใช่ว่าตั้งชื่อสะเปะสะปะแล้วแต่จะนึกออก คลาสนึงก็ตั้งชื่ออีกแบบ ส่วนอีกคลาสก็ตั้งชื่ออีกแบบ
ลองหาข้อมูลเรื่อง Naming Convention ดูครับ จะพบว่ามีหลายๆที่พูดถึงเรื่องนี้กันแบบจริงจังเลยนะ
ทำเป็น String Resource หรือยัง?
อันนี้เจอหลุดบ่อยมาก เวลาที่ต้องเขียนโค้ดที่ต้องแสดงข้อความให้ผู้ใช้เห็น เพราะนักพัฒนาจะชอบใส่ข้อความเป็น String ลงไปตรงๆก่อน (Hard Code String) กลายเป็นว่าโปรเจคนั้นก็จะมีข้อความที่อยู่ใน strings.xml และ Hard Code ปนๆกันไป ถ้าเกิดต้องทำภาษาอื่นๆเพิ่มทีหลังก็มักจะลืมแก้ในส่วนที่อยู่ในโค้ด เพราะว่าหาไม่เจอ
โดยเบื้องต้นเจ้าของบล็อกแนะนำให้สร้างข้อความที่ต้องการใช้ไว้ใน strings.xml
ตั้งแต่แรกเลยทุกครั้ง ห้ามใส่ไว้ในโค้ดก่อนเด็ดขาด (เพราะเดี๋ยวก็ลืมอีก)
หรือถ้าจะใช้วิธีแก้ไขทีหลังก็แนะนำว่าให้ใช้ Analyze > Run Inspection by Name…
เข้ามาช่วยหาว่ายังมีตรงไหนที่เป็น Hard Code String อยู่บ้าง
ทำเป็น Dimension Resource หรือยัง?
เหมือนกับข้อก่อนหน้าเลย ที่นักพัฒนาชอบใส่ Dimension ไว้ใน Layout XML ไว้ก่อน แทนที่จะใส่ไว้ใน dimens.xml
ตั้งแต่แรก พอถึงขั้นตอนที่ต้องรองรับกับหน้าจอหลายๆขนาดก็ต้องเสียเวลามานั่งหาอีกว่าตรงไหนที่ใส่ Dimension ลงไปตรงๆ
Responsive Design มั้ย?
อาจจะดูว่าเป็นเรื่องที่ควรจบไปได้นานแล้วในยุคนี้ แต่ก็ต้องยอมรับว่ายังมีนักพัฒนาบางส่วนที่เจอกับปัญหานี้อยู่เรื่อยมา
ซึ่งนักพัฒนาแต่ละคนก็จะมีเทคนิคที่แตกต่างกันออกไปในการทำให้รองรับกับหน้าจอทุกขนาด ถ้าเทคนิคที่เจ้าของบล็อกใช้ประจำก็คือใช้ Layout เหมือนกันแต่แยก Dimension ต่างกันออกไปตามอุปกรณ์โดยแบ่งเป็น
values
: มือถือหน้าจอขนาดเล็กvalues-sw360dp
: มือถือหน้าจอขนาดทั่วไปvalues-sw540dp
: แทบเล็ต 7 นิ้วขึ้นไปvalues-sw720dp
: แทบเล็ต 10 นิ้วขึ้นไป
โดยหยิบ Presentation เก่าจากงาน Droidcon มาให้ดูว่าอุปกรณ์แอนดรอยด์ในทุกวันนี้มีขนาดหน้าจอในหน่วย DP เท่าไรบ้าง
แต่ถ้าจะให้ละเอียดกว่านี้ให้แยกสำหรับ sw480dp
ออกมาด้วย เพราะมีอุปกรณ์แอนดรอยด์บางตัวที่มีขนาดหน้าจอต่างจาก sw360dp
เกินไป แต่ถูกรวมอยู่ในกลุ่ม sw360dp
อาจจะเกิดปัญหาแสดงผลเพี้ยนได้
แยกเก็บไฟล์ภาพตาม Density มั้ย?
ทุกวันนี้เจ้าของบล็อกก็ยังเห็นหลายๆแอพที่นักพัฒนาแอบยัดภาพไว้ใน drawable
ทั้งหมด ซึ่งมันจะทำให้มีปัญหาเรื่อง Out of Memory ได้ รวมไปถึงภาพแตก
ซึ่งวิธีที่ดีที่สุดก็คงจะเป็นการใช้ Vector Drawable ที่นอกจากจะแก้ปัญหา Out of Memory และภาพแตกแล้ว ยังลดขนาดไฟล์ภาพที่จะต้องใช้ด้วย แต่ก็ไม่รองรับกับภาพที่มีความซับซ้อนมาก จะเหมาะกับภาพไอคอนธรรมดาๆเท่านั้น ในกรณีที่เป็นภาพจำพวก JPEG หรือ PNG ก็ให้แยกตาม Density ด้วยทุกครั้ง เพื่อให้ได้ภาพที่ใกล้เคียงกับหน้าจอมากที่สุด
และเพื่อให้ชีวิตง่ายก็ควรใช้ Plugin ที่ชื่อว่า Android Drawable Importer ด้วย จะได้ไม่ต้องมานั่งย่อภาพหลายๆขนาด
สำหรับขนาดภาพก็ให้แยกแค่ mdpi
, hdpi
, xhdpi
และ xxhdpi
ก็พอ ส่วน xxxhdpi
เป็นความละเอียดสูงเกินที่ตาคนจะแยกแยกระหว่าง xxhdpi
ได้ แถมทำให้ APK มีขนาดใหญ่ได้ง่ายด้วย ก็เลยตัดออกไป แต่ถ้าต้องการทำภาพให้รองรับแทบเลตอย่างสวยงามก็ให้แยกด้วย sw540dp
อีกทีหนึ่ง
Save/Restore Instance State หรือยัง?
ชื่อว่าหลายๆคนคงรู้จักกันดีอยู่แล้ว (ถ้าไม่รู้จักก็ไปอ่านที่ Best Practices ของการ Save/Restore State ของ Activity และ Fragment)
แต่เรื่องน่าเศร้าก็คือหลายๆคนนั้นรู้จัก เหตุผลหลักๆคือขี้เกียจและรู้สึกเสียเวลา ทั้งๆที่จริงแล้ว Save/Restore Instance State เป็นสิ่งที่ “ขาดไปไม่ได้” ทำทุกครั้งเมื่อเขียนแอพแอนดรอยด์ เพราะถ้าไม่ทำก็หมายความว่าแอพนั้นๆไม่รองรับ Configuration Changes ใดๆ รวมไปถึงตอนที่แอพถูกคืน Memory แล้วกลับมาเปิดใหม่อีกครั้งก็จะเด้งและพังแบบกระจุยกระจาย
และนอกจากนี้ เวลาเขียน Save/Restore Instance State จะทำให้รู้ว่าประกาศ Global Variable พร่ำเพรื่อเกินจำเป็นหรือป่าว แล้วรวบเป็น Model เพื่อให้จำนวนบรรทัดที่จะต้องเขียนใน Save/Restore Instance State ลดลง
ถ้าหมุนหน้าจอล่ะ?
นอกจากจะทดสอบเรื่อง Responsive Layout แล้วยังสามารถใช้ทดสอบ Save/Restore Instance State ได้ด้วยนะ แต่เอาเข้าจริงก็พบว่าหลายๆแอพมักจะเปิดให้รองรับแค่หน้าจอแนวตั้ง (Portrait) เท่านั้น
ถ้าย่อหน้าจอระหว่างแอพกำลังทำงานจะเป็นอะไรมั้ย?
นอกจากจะเปิดแอพขึ้นมาแล้วกดๆๆ ควรทดสอบด้วยว่าแอพสามารถกลับมาทำงานได้ปกติหรือป่าว ถ้าเกิดย่อแอพกลางคันระหว่างที่แอพกำลังทำงานบางอย่างอยู่ เช่น Fetch ข้อมูลจาก Web Service อยู่ แล้วสลับไปแอพอื่นก่อนที่จะ Fetch ข้อมูลเสร็จ ก็ควรจะกลับมาทำงานต่อได้เมื่อ Fetch ข้อมูลเสร็จแล้ว (ลองนึกภาพว่าถ้าเป็น Service จ่ายเงินที่ตัดเงินจากลูกค้าล่ะ?)
ถ้าย่อแอพแล้วเปิดแอพอื่น จน Process ของแอพเราถูกปิดไปล่ะ?
สมมติว่าผู้ใช้ใช้แอพอยู่แล้วกดสลับไปแอพอื่นเช่น เล่น Facebook ต่อ แล้วก็เปิดแอพถ่ายรูปเล่น ตามด้วยคุย Line แล้วสลับไปเล่นเกม ซึ่งในกรณีนี้สามารถทำให้ Process ของแอพถูกปิดลงชั่วคราวเพื่อคืน Memory เพื่อให้แอพอื่นใช้งาน ซึ่งจริงๆแล้วสถานการณ์แบบนี้เกิดขึ้นได้ไม่ยากนัก โดยเฉพาะอุปกรณ์แอนดรอยด์ที่ไม่ใช่ Flagship
คำถามคือ เคยทดสอบแอพแบบนี้กันหรือป่าวล่ะ?
ลอง Force Kill Activity ใน Developer Options แล้วหรือยัง?
การเปิด Force Kill Activity ใน Developer Options ถือว่าเป็นการทดสอบที่ดีอย่างหนึ่ง เพราะว่ามันจะบังคับให้ Activity ที่อยู่ใน Backstack ถูกทำลายทันที ไม่ต้องรอให้ Memory ไม่พอ ถ้าเป็นเครื่องเทสที่ไม่ใช่เครื่องส่วนตัวก็แนะนำให้เปิดทิ้งไว้เลย
Dependency เป็นเวอร์ชันใหม่ล่าสุดแล้วหรือยัง?
หมั่นอัพเดทและคอยเช็คอยู่เสมอว่า Library ต่างๆนานาที่ใช้งานอยู่นั้นมีอะไรใหม่หรือไม่รวมไปถึงแก้บั๊กอะไรบ้าง
ทดสอบบน Lollipop+ และ Pre-Lollipop หรือยัง?
ถ้าแอพถูกกำหนดให้รองรับกับแอนดรอยด์เวอร์ชันต่ำกว่า Android 5.0 Lollipop (API 21) ก็แนะนำให้ทดสอบกับอุปกรณ์แอนดรอยด์ที่มีเวอร์ชันต่ำกว่า API 21 ด้วยทุกครั้ง เพราะเวอร์ชันนี้ถือว่าเป็นรอยต่อใหญ่ที่มีการเปลี่ยนแปลงเกี่ยวกับการทำงานของแอนดรอยด์มากที่สุด อย่ามัวแต่ทดสอบบนเครื่องตัวเองที่เป็นแอนดรอยด์เวอร์ชันใหม่อยู่เสมอ จนไปเจอ Critical Issue ที่เกิดมาจากอุปกรณ์แอนดรอยด์ของผู้ใช้ที่เวอร์ชันต่ำกว่าล่ะ
Runtime Permission เขียนไว้แล้วนะ?
เป็นฟีเจอร์ (จะเรียกว่าฟีเจอร์ดีมั้ยนะ) ที่มาใน Android 6.0 Marshmallow (API 22) ที่บังคับให้นักพัฒนาต้องเขียนโค้ดเพื่อขออนุญาตใช้งาน Permission ณ ตอนนั้นทันที ดังนั้นเวลาประกาศหรือเรียกใช้คำสั่งอะไรที่เกี่ยวกับ Permission ให้นึกถึงอยู่เสมอว่าจะต้องเขียนโค้ดสำหรับ Runtime Permission ด้วยหรือไม่
ถ้าไม่ได้ต่อเนตแล้วต้องยิงข้อมูลจาก Web Server ล่ะ?
เมื่อใดที่ในโค้ดมีการเรียก Web Service ก็ให้ทดสอบด้วยว่าทำงานต่อได้หรือไม่ถ้าไม่ได้ต่อเนต ลองทั้งเปิด Airplane Mode ลองทั้งปิด Cellular Data และ WiFi ด้วย
เพราะส่วนใหญ่นักพัฒนามักจะมองข้ามการทำงานในส่วนนี้ด้วยการเขียนโค้ดให้แสดงข้อความแบบลวกๆบ่อยมาก เช่น “No internet” ซึ่งจริงๆแล้วต้องมีการออกแบบ UI ด้วยว่าจะแสดงผลยังไงให้ผู้ใช้รู้สึกโอเค
Handle Error Response จาก Web Service ครบทั้งหมดหรือยัง?
ถ้า Web Service ของระบบที่ไม่ใหญ่มากนัก ก็จะมี Error Response ที่ไม่หลากหลายมากนัก แต่สำหรับเจ้าของบล็อกที่ต้องเผชิญกับ Error Response ที่มากมายก็พบว่าทุกครั้งที่เขียนโค้ดที่ต้องไปดึงข้อมูลจาก Web Service จะต้องเขียนโค้ดเพื่อจัดการกับ Error Response ให้ครบทั้งหมดด้วย ไม่ใช่ว่าปล่อยให้มันหลุดไปโดยที่ไม่แสดงผลอะไรบนหน้าจอหรือเอา Developer Message มาแสดงโดยตรง
เก็บข้อมูลสำคัญลงใน SQLite หรือ SharedPreference โดยตรงเลยหรือป่าว?
อันนี้เป็นเรื่องของ Security ที่ไม่ควรเก็บข้อมูลสำคัญๆไว้ในเครื่อง ดังนั้นถ้าดีที่สุดก็คือพยายามเลี่ยงการเก็บข้อมูลลงในเครื่อง แต่ถ้าทำไม่ได้อย่างน้อยก็ควรเข้ารหัส (Encrypted) ไว้ซักหน่อยก็ยังดี (และเลิกใช้ MD5 ได้แล้วนะ ขอร้องล่ะ)
ในกรณีของเจ้าของบล็อกจะใช้ Realm แทน SQLite แทบทั้งหมด ซึ่ง Realm ก็รองรับการทำ Encrypted ในตัวอยู่แล้วจึงไม่มีปัญหาอะไร
แต่สำหรับ SharedPreference นี่สิ ถึงจะเรียกใช้งานโคตรง่ายก็เถอะ แต่มันก็ไม่รองรับการเข้ารหัสเลย เมื่อก่อนก็เลยใช้ Library ที่ชื่อว่า Security Library ของ AndroidX แทน
ลอง Build APK แบบเปิด ProGuard ดูหรือยัง?
เวลาจะ Build APK ขึ้น Google Play ก็ควรจะเปิด ProGuard ด้วยทุกครั้งเนอะ? แต่ถ้าผู้ที่หลงเข้ามาอ่านคนใดไม่ทำ จงกลับไปทำซะ เพื่อความปลอดภัยในแอพของคุณเอง
ถึงแม้ว่า ProGuard จะเป็นขั้นตอนสุดท้ายที่ช่วยทำให้แอพของเรายากต่อ Reverse Engineering มากขึ้น รวมไปถึงตัด Resource ที่ไม่จำเป็นออกไปเพื่อให้ได้ขนาดของ APK ที่เล็กลง แต่ปัญหาส่วนใหญ่ที่เจอก็คือ ProGuard ไม่ได้ฉลาดมากพอที่จะทำงานได้อย่างสมบูรณ์แบบ นักพัฒนาต้องไปเขียนคำสั่งบางอย่างเพื่อบอก ProGuard อีกทีว่าอันไหนต้องทำและอันไหนไม่ต้องทำ (เขียนใน proguard-rules.pro
นั่นเอง)
ดังนั้นควรทดสอบแอพหลังจากที่เปิด ProGuard ด้วยทุกครั้งว่าทำงานได้ปกติสุขอยู่เหมือนเดิมหรือไม่ โดยทดสอบทุกหน้าทุก Case ทุก Flow เลย เพราะบ่อยครั้งที่ ProGuard มักจะเซอร์ไพร์สเราด้วยการตัดคลาสบางตัวออกไป แล้วแอพของเราก็ระเบิดจนกลายเป็นโกโก้ครั้นช์ไป
ในแอพใช้ <uses-feature> อะไรบ้าง?
เจ้าของบล็อกเคยเห็นนักพัฒนาที่เจอปัญหาลูกค้าโหลดแอพไม่ได้หรือค้นหาไม่เจอเลยทีเดียว ซึ่งสาเหตุหลักก็คือ <uses-feature>
ที่อยู่ใน Android Manifest นั่นเอง ซึ่ง <uses-feature>
จะถูกใส่ให้โดยอัตโนมัติในตอนที่ Build APK ว่าอุปกรณ์แอนดรอยด์ที่จะลงแอพนี้ได้จะต้องมีฟีเจอร์อะไรบ้าง
ซึ่ง <uses-feature>
นั้นจะขึ้นอยู่กับโค้ด, Permission ที่ประกาศไว้ใน Android Manifest และ Configuration Qualifier ในโปรเจค
แต่ผู้ที่หลงเข้ามาอ่านจะไม่เห็นว่าในแอพว่ามี <uses-feature>
อะไรบ้างใน Android Manifest จนกว่าจะ Build เป็น APK ออกมา จึงแนะนำให้ใช้ APK Analyzer ใน Android Studio เพื่อเปิดดู AndroidManifest.xml
หลังจาก Build เป็น APK แล้ว
ถามอะไรก็ได้
จะเล่าให้ฟังอย่างเดียวก็คงน่าเบื่อเลยเปิดเป็น Session ให้คนอื่นๆภายในงานถามเลย เดี๋ยวตอบให้ จริงๆก็มีหลายๆคำถามนะ แต่จำไม่ได้ เอาเท่าที่จำได้ก็แล้วกัน
ปัญหา Recycler View กระตุกเมื่อแสดงข้อมูลเยอะๆ
ให้ลองดูก่อนว่า View Holder มีการแสดงภาพหรือป่าว ถ้ามีก็ดูต่อว่าจัดการกับภาพถูกต้องหรือป่าว ซึ่งแนะนำให้ใช้ Picasso กับ Glide แทน
แต่ถ้าเอา Recycler View ไปใส่ไว้ใน NestedScrollView จะทำให้ Item ใน Recycler View ไม่ Recycle ดังนั้นถ้าข้อมูลมีจำนวน 1000 ตัว เวลาเอามาใส่ใน Recycler View ก็จะทำการแปะข้อมูลทั้งหมด 1000 ตัว ลงใน View Holder โดยทันที
ดังนั้นถ้าต้องการแสดง Recycler View ที่มี View อื่นๆแสดงผสมอยู่ด้วย แนะนำว่าให้ทำทั้งหน้านั้นให้เป็น Recycler View ไปเลยดีกว่า โดยให้ View ตัวอื่นๆเป็นหนึ่งใน View Holder ของ Recycler View ด้วย ลองดูเพิ่มเติมได้ที่ ว่าด้วยเรื่อง Recycler View กับการใช้งานจริงในแบบฉบับเจ้าของบล็อก ตอนที่ 1
Library ที่ใช้บ่อย
- Retrofit + OkHttp
- Realm
- RxJava
- Glide
- Timber
- GSON
- Dexter
Build APK แยกประเภท
โดยปกติจะนิยม Build APK แยกตาม CPU เพื่อลดขนาดของไฟล์ APK แต่นอกเหนือจากนั้นก็ยังสามารถแยกระหว่าง Version ได้ด้วย ซึ่งนิยมแยกที่ API 21 หรือจะแยกตาม Density ของอุปกรณ์ก็ได้เช่นกัน ซึ่งจะช่วยลดขนาดของไฟล์ APK เพราะขนาดภาพที่หลากหลาย Density ได้
แต่ทั้งนี้ทั้งนั้นการแยก APK จะทำให้เกิดปัญหาถ้าผู้ใช้เอาไฟล์ APK ไปติดตั้งเอง อาจจะไม่รองรับกับอุปกรณ์แอนดรอยด์เครื่องนั้นๆ
ทั้งหมดก็มีประมาณนี้แหละฮะ จริงๆก็ไม่ได้กะจะให้เนื้อหามันนานอะไรขนาดนั้น แต่เอาเข้าจริงก็ปาไป 3 ชั่วโมงกว่าซะงั้น แถมยังไม่รวมช่วง Networking ที่คุยกัน + นั่งเล่นเกมอีกด้วยนะ