อาจจะเคยได้ยินคำว่า 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 ให้ถูกต้อง สามารถดูวิธีของแต่ละอันจากบทความเหล่านี้ได้เลย

วิธีที่ 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 ณ ตอนนั้นได้

แหล่งอ้างอิงข้อมูล