อาจจะเคยได้ยินคำว่า Configuration Changes กันมาบ้าง แต่รู้หรือไม่ว่าจริง ๆ แล้วมันคืออะไร และทำไมถึงสำคัญมากขนาดที่ว่าถ้าไม่จัดการอย่างถูกต้องก็จะทำให้แอปมีปัญหาไปเลยก็เป็นได้
Configuration Changes คืออะไร?
ถ้าแปลตรงตัว คำว่า Configuration Changes ก็หมายถึงค่าที่เคยกำหนดไว้มีการเปลี่ยนแปลง โดย Configuration ที่ว่านี้ก็คือ Device Configuration หรือค่าต่าง ๆ ที่สำคัญต่อระบบแอนดรอยด์
ทั้งนี้ก็เพราะว่าระบบแอนดรอยด์เป็น OS ที่มีความยืดหยุ่นในการทำงานสูง สามารถรองรับการทำงานได้หลากหลายรูปแบบ และในบางครั้งก็อาจจะเปลี่ยนรูปแบบในการทำงานระหว่างใช้งานอยู่ก็ได้
ยกตัวอย่างเช่น เวลาที่ผู้ที่หลงเข้ามาอ่านถืออุปกรณ์แอนดรอยด์ในแนวตั้ง แล้วทำการหมุนเครื่องให้กลายเป็นแนวนอน (โดยไม่ได้มีการล็อคทิศทางหน้าจอ) อุปกรณ์แอนดรอยด์ก็จะเกิด Configuration Changes เพื่อให้ระบบแอนดรอยด์เปลี่ยนรูปแบบการทำงานให้เหมาะสมกับหน้าจอในแนวนอนนั่นเอง (ในที่นี้คือการแสดง UI บนหน้าจอ)
มีอะไรบ้างที่ทำให้เกิด Configuration Changes
จริง ๆ แล้วการเกิด Configuration Changes บนอุปกรณ์แอนดรอยด์ของผู้ใช้งานนั้นเกิดขึ้นได้ง่ายมาก เพราะบนสิ่งที่เรียกว่า Configuration บนระบบแอนดรอยด์นั้นมีหลายอย่าง ไม่ใช่แค่เรื่อง UI แบบที่ยกตัวอย่างไปในตอนแรกเท่านั้น
โดยจะอ้างอิงจาก Activity Attributes [Android Developers]
- Color Mode - มีการเปลี่ยนรูปแบบสีของการแสดงผลของหน้าจอ
- Density - มีการเปลี่ยนค่า Density Scale ของหน้าจอ
- Font Scale - มีการเปลี่ยน Scale ของขนาดตัวอักษรที่แสดงผลบนหน้าจอ
- Keyboard - รูปแบบของคีย์บอร์ดมีการเปลี่ยนแปลง เช่น มีการต่อ External Keyboard เพิ่มเข้ามา
- Keyboard Hidden - รูปแบบของ Accessibility Keyboard หรือ Accessibility Navigation มีการเปลี่ยนแปลง
- Layout Direction - มีการเปลี่ยนทิศทางของตัวหนังสือระหว่างซ้ายไปขวา (LTR) กับขวาไปซ้าย (RTL)
- Locale - มีการเปลี่ยนภาษาในการแสดงผล
- MCC - มีการใส่ SIM Card เข้ามาในเครื่องแล้วอัปเดตข้อมูล Mobile Country Code
- MNC - มีการใส่ SIM Card เข้ามาในเครื่องแล้วอัปเดตข้อมูล Mobile Network Code
- Navigation - มีการเปลี่ยนรูปแบบในการแสดงผลของ Navigation Button หรือ Navigation Bar
- Orientation - มีการเปลี่ยนทิศทางของหน้าจอระหว่างแนวตั้ง (Portrait) กับแนวนอน (Landscape)
- Screen Layout - หน้าจอแสดงผลมีการเปลี่ยนแปลง ซึ่งจะเกิดขึ้นได้ในกรณีที่เปลี่ยนไปแสดงผลบนอีกหน้าจอหนึ่ง
- Screen Size - ขนาดของหน้าจอมีการเปลี่ยนแปลง ซึ่งจะเกิดขึ้นได้ในกรณีที่แอปทำงานในรูปแบบ Multi/Floating Window หรือเปลี่ยนทิศทางในการแสดงผลหน้าจอระหว่างแนวตั้ง (Portrait) กับแนวนอน (Landscape)
- Smallest Screen Size - ด้านที่เล็กที่สุดของหน้าจอมีการเปลี่ยนแปลง ซึ่งจะเกิดขึ้นได้ในกรณีที่เปลี่ยนไปแสดงผลบนอีกหน้าจอหนึ่ง
- Touchscreen - รูปแบบการทำงานของ Touchscreen มีการเปลี่ยนแปลง โดยจะไม่เกิดขึ้นสำหรับการใช้งานทั่วไป
- UI Mode - เป็นรูปแบบในการทำงานของตัวเครื่องระหว่าง Normal Mode, Dark Theme, Car Mode, Desk Mode, TV Mode, Appliance Mode, Watch Mode และ VR Headset Mode
ถึงแม้ว่าจะมีหลาย ๆ อย่างที่ทำให้เกิด Configuration Changes แต่ที่เจอได้บ่อย ๆ ก็จะมีเพียง Density, Font Scale, Locale, Orientation, Screen Layout, Screen Size, Smallest Screen Size และ UI Mode เท่านั้น
สิ่งที่จะเกิดขึ้นเมื่อ Configuration Changes ทำงาน
เมื่อผู้ใช้ทำอะไรบางอย่างที่ทำให้ Configuration Changes เกิดขึ้น ระบบแอนดรอยด์จะทำการส่งค่า Configuration ใหม่ให้กับแอปที่กำลัง Active อยู่ (รวมไปถึง System UI และแอปเคยเปิดไว้ก่อนหน้าแล้ว Active ในภายหลังด้วย)
และสิ่งที่เกิดขึ้นในแต่ละแอปก็คือ Activity ที่ทำงานอยู่ ณ ตอนนั้นจะถูก Recreate (Destroy แล้ว Create ใหม่) เพื่ออัปเดตค่า Configuration ที่อยู่ใน Context ใหม่ทั้งหมด
เช่น ผู้ใช้หมุนหน้าจอจากแนวตั้งไปเป็นแนวนอน ค่าที่เก็บไว้ใน Context จากเดิมเป็น Configuration.ORIENTATION_PORTRAIT
ก็จะกลายเป็น Configuration.ORIENTATION_LANDSCAPE
แทน
โดยในขั้นตอนของการ Recreate เพื่ออัปเดตค่าให้กับ Context ใหม่นั้น ระบบแอนดรอยด์จะเรียกคำสั่ง onSaveInstanceState(outState: Bundle)
เพื่อให้นักพัฒนาสามารถเก็บ UI State ไว้ใน outState
ก่อนที่ Activity จะถูกทำลายทิ้ง (Destroy) จากนั้นก็จะสร้าง Activity ขึ้นมาใหม่ (Create) พร้อมกับ Context ที่มีค่า Configuration ใหม่ล่าสุด และจะคืนค่า UI State กลับมาให้ใน savedInstanceState
ของ onCreate(savedInstanceState: Bundle)
หรือ onRestoreInstanceState(savedInstanceState: Bundle)
และสำหรับตัวแปรอื่น ๆ ที่ประกาศไว้เป็น Global Variable และไม่ได้จัดการอะไรเลย ก็จะถูกเคลียร์ทิ้งทั้งหมด ยกเว้นตัวแปรที่เป็น Static (แต่เจ้าของบล็อกไม่แนะนำให้เก็บค่าสำหรับ UI State เป็นแบบ Static)
Configuration Changes ส่งผลต่อ Android Resource ด้วยนะ
เมื่อเกิด Configuration Changes ที่ทำให้ Configuration มีการเปลี่ยนแปลงไปจากเดิม ก็จะทำให้การเรียกข้อมูลใน Android Resource มีโอกาสเปลี่ยนไปด้วย ตามเงื่อนไขของ Configuration Qualifier (หรือ Resource Qualifier) ที่นักพัฒนากำหนดไว้ในโปรเจค
ยกตัวอย่างเช่น
โปรเจคมีการสร้าง Layout แยกไว้ระหว่างหน้าจอแนวตั้งกับแนวนอน โดยแยกเป็น layout
กับ layout-land
เมื่อหมุนหน้าจอจากแนวตั้งไปเป็นแนวนอน Configuration Changes ก็จะไปอัปเดตข้อมูลใน Context ให้เป็น Configuration.ORIENTATION_LANDSCAPE
จากนั้นเมื่อ Activity ถูกสร้างขึ้นมาใหม่ก็จะใช้ข้อมูลที่อยู่ใน layout-land
แทน และในขณะเดียวกันถ้าหมุนหน้าจอกลับไปเป็นแนวตั้งอีกครั้ง ก็จะกลับไปใช้ข้อมูลใน layout
เหมือนเดิม
นักพัฒนาควรรับมือกับ Configuration Changes อย่างไร
จากการทำงานของ Configuration Changes ที่ส่งผลกระทบต่อการทำงานของแอปอย่างแน่นอน ดังนั้นนักพัฒนาจึงต้องหาทางรับมือกับ Configuration Changes ให้เหมาะสม โดยมีอยู่ 2 วิธีด้วยกัน
วิธีที่ 1 - จัดการกับ UI State ให้รองรับ Configuration Changes
เป็นวิธีที่เจ้าของบล็อกแนะนำมากที่สุด เพราะเป็นวิธีที่ไม่เข้าไปยุ่งกับการทำงานของ Configuration Changes เลยซักนิด แล้วจัดการกับ UI State ให้ถูกต้องแทน
ดังนั้นนักพัฒนาจะต้องเก็บค่าต่าง ๆ ที่เกี่ยวข้องกับ UI State ใน Activity, Fragment และ View ให้ถูกต้อง สามารถดูวิธีของแต่ละอันจากบทความเหล่านี้ได้เลย
- มา Save และ Restore UI State ใน Activity ให้ถูกต้องกันเถอะ
- Save และ Restore UI State ใน Fragment ควรทำอย่างไรกันนะ?
วิธีที่ 2 - Override Configuration แล้วจัดการด้วยตัวเอง
ในกรณีที่ไม่อยากให้ Configuration ใด ๆ เกิด Configuration Changes ก็ให้กำหนดไว้ใน android:configChanges
ใน Android Manifest แบบนี้
<?xml version="1.0" encoding="utf-8"?>
<manifest ... >
<application ... >
<activity
...
android:configChanges="screenSize|orientation" >
<!-- ... -->
</activity>
</application>
</manifest>
การกำหนด android:configChanges
ไว้ใน Activity ใด ๆ จะเป็นการบอกให้รู้ว่าเวลาที่เกิด Configuration Changes ที่ตรงกับ Configuration ที่กำหนดไว้ ระบบแอนดรอยด์ไม่ต้องอัปเดต Context ใหม่ แต่ให้เรียก onConfigurationChanged(newConfig: Configuration)
แทน เพื่อให้นักพัฒนาจัดการกับ Configuration ใหม่เอง
ซึ่งวิธีนี้จะช่วยให้ Activity ไม่ถูก Recreate ใหม่โดยอัตโนมัติจากระบบแอนดรอยด์ แต่ถึงกระนั้นนักพัฒนาก็ควรจัดการกับค่า Configuration ที่เปลี่ยนแปลงไปให้เหมาะสมกับแอปของตัวเองด้วย
// MainActivity.kt
class MainActivity : AppCompatActivity() {
/* ... */
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// Any configuration has changed
}
}
โดยค่าที่ส่งเข้ามาใน Method ดังกล่าวจะไม่ได้บอกว่าค่าไหนมีการเปลี่ยนแปลง ซึ่งต้องเขียนโค้ดในส่วนนี้เอง แต่ถ้ากำหนดค่าไว้ใน android:configChanges
แค่ Configuration เดียว ก็ไม่ต้องเช็คอะไร
// MainActivity.kt
class MainActivity : AppCompatActivity() {
/* ... */
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
// When screen orientation is landscape
} else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
// When screen orientation is portrait
}
}
}
แต่อย่าลืมนะว่าถ้า Configuration Changes ที่เกิดขึ้นเป็น Configuration ตัวอื่นที่ไม่ได้กำหนดไว้ใน android:configChanges
ก็จะทำให้ Activity ถูก Recreate เพื่ออัปเดตค่า Configuration ใน Context อยู่ดี
อย่าใช้ android:configChanges แบบผิด ๆ
เพราะยังมีนักพัฒนาอีกมากมายที่เข้าใจผิดเกี่ยวกับการทำงานและจุดประสงค์ของ android:configChanges
จึงทำให้นำไปใช้งานอย่างผิดวิธีและเกิดปัญหากับแอปในภายหลัง
คิดว่าเมื่อใช้ android:configChanges แล้ว ไม่ต้องทำ UI State ให้รองรับ Configuration Changes ก็ได้
การกำหนด Configuration แค่บางตัวใน android:configChanges
ก็หมายความว่าอาจจะเกิด Configuration Changes จากเงื่อนไขอื่นได้อยู่ดี
และการกำหนดค่าใน android:configChanges
แต่ไม่ได้จัดการให้เหมาะสม ก็สามารถทำให้แอปทำงานไม่ถูกต้องได้เช่นกัน
คิดว่า onConfigurationChanged มีไว้ดัก Event ของ Configuration ที่ต้องการ
เช่น อยากรู้ว่าหน้าจอมีการเปลี่ยนทิศทางระหว่างแนวตั้งกับแนวนอน จึงกำหนด orientation
ไว้ใน android:configChanges
เพื่อให้ onConfigurationChanges(newConfig: Configuration)
ทำงานตอนที่หมุนหน้าจอ
จริงๆแล้ว Method ดังกล่าวมีไว้ให้นักพัฒนาจัดการกับ Configuration ที่มีการอัปเดตใหม่ให้ถูกต้องต่างหาก ดังนั้นการนำไปใช้เพื่อดัก Event ที่ต้องการ แต่ไม่จัดการกับ Configutation นั้น ๆ ก็อาจจะทำให้แอปทำงานไม่ถูกต้องก็ได้
การทำงานของ onConfigurationChanged บน Android 7.0 / 7.1 Nougat
โดยปกติแล้ว ถ้านักพัฒนาไม่ได้กำหนดค่า android:configChanges
ให้กับ Activity ใดๆใน AndroidManifest ก็จะทำให้คำสั่ง onConfigurationChanges(newConfig: Configuration)
ไม่ถูกเรียก
แต่สำหรับบน Android 7.0 / 7.1 Nougat (API 24-25) จะพบว่า onConfigurationChanges(newConfig: Configuration)
จะถูกเรียกทุกครั้งที่มี Configuration Changes เกิดขึ้น ถึงแม้ว่าจะไม่ได้กำหนดค่าใดๆใน android:configChanges
ก็ตาม ซึ่งทีมพัฒนาแอนดรอยด์ได้แก้ปัญหานี้ไปแล้วใน Android 7.1.1 Nougat (อ้างอิงจาก https://issuetracker.google.com/issues/37120330)
นั่นหมายความว่าถ้านักพัฒนามีการเพิ่มคำสั่งไว้ใน onConfigurationChanges(newConfig: Configuration)
ก็ควรเช็คให้เรียบร้อยว่าคำสั่งดังกล่าวได้รับผลกระทบใด ๆ บน Android 7.0 / 7.1 Nougat จากปัญหาดังกล่าวหรือไม่
สรุป
Configuration Changes นั้นเป็นหนึ่งในการทำงานของระบบแอนดรอยด์ที่นักพัฒนาควรจะรู้และเข้าใจ เพื่อจัดการกับ UI State ให้เหมาะสมกับการทำงานของ Configuration Changes เพื่อลดปัญหาที่อาจจะเกิดขึ้นจากการใช้งานทั่ว ๆ ไปของผู้ใช้
และอย่าลืมว่าการทำงานของ Configuration Changes นั้นจะส่งผลต่อ Android Resource ด้วย จึงทำให้นักพัฒนาสามารถใช้ประโยชน์จาก Configuration Qualifier (Resource Qualifier) เพื่อทำให้แอปสามารถรองรับการใช้งานได้หลากหลายรูปแบบโดยอ้างอิงจาก Configuration ณ ตอนนั้นได้