11 January 2016

Configuration Changes อีกหนึ่งอย่างที่นักพัฒนาแอนดรอยด์ควรรู้จัก

Updated on


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

Configuration Changes คืออะไร?

        อุปกรณ์แอนดรอยด์ทุกตัวล้วนมี Configuration ในการทำงานของตัวเครื่องอยู่แล้ว ไม่ว่าจะเป็นทิศทางของหน้าจอ ขนาดของหน้าจอ ภาษาที่ใช้ในเครื่อง ซึ่งแต่ละเครื่องจะแตกต่างกันออกไปตามสเปคเครื่องหรือว่าตามการตั้งค่าของผู้ใช้งาน นั่นหมายความว่า "มันสามารถเปลี่ยนแปลงค่าได้"

        ซึ่ง Configuration Changes เนี่ย มันคือ Configuration ใดๆก็ตามที่มีการเปลี่ยนแปลงแล้วทำให้ Activity ถูกทำลายทิ้งแล้วสร้างขึ้นมาใหม่ (Recreate) เพราะบาง Configuration มันก็อาจจะส่งผลต่อการแสดงผลของแอปใช่มั้ยล่ะ ซึ่งแอนดรอยด์ก็จะใช้วิธีสร้าง Activity ใหม่ขึ้นมาแทนนั่นเอง

แล้วมันมีอะไรบ้างนะ?

        ถ้าที่รู้ๆกันอยู่แล้วก็คือการหมุนหน้าจอนั่นเอง นั่นก็คือส่วนหนึ่งของ Configuration Changes ซึ่งจริงๆแล้วยังมีอีกหลายๆอย่างที่เป็น Configuration Changes นะ ตามนี้เลย

        • MMC (mmc) ใส่ SIM Card แล้วมีการอัพเดท Mobile Country Code.
        • MNC (mnc)  ใส่ SIM Card แล้วมีการอัพเดท Mobile Network Code
        • Locale (locale) ภาษาที่แสดงผลถูกเปลี่ยนเป็นภาษาอื่น
        • Touchscreen (touchscreen) การทำงานของ Touchscreen มีการเปลี่ยนแปลง (ไม่ได้เกิดขึ้นตามการใช้งานปกติ)
        • Keyboard (keyboard) เปลี่ยนจาก On-screen Keyboard ไปเป็น External Hardware Keyboard เมื่อมีการเอา Keyboard จากข้างนอกมาต่อใช้งาน (หรือเปลี่ยนกลับ)
        • Keyboard Hidden (keyboardHidden) Accessibility Keyboard หรือ Accessibility Navigation มีการเปลี่ยนแปลง
        • Navigation (navigation) Navigation Bar ของเครื่องมีการเปลี่ยนแปลงระหว่าง On-Screen หรือ Hardware Button  (ไม่ได้เกิดขึ้นตามการใช้งานปกติ)
        • Orientation (orientation) ทิศทางหน้าจอมีการเปลี่ยนแปลงจากการหมุนหน้าจอ (เจอได้บ่อยที่สุด)
        • Screen Layout (screenLayout) Layout ที่แสดงอยู่บนหน้าจอมีการเปลี่ยนแปลง อาจจะเพราะหน้าจอคนละตัวกันทำงานขึ้นมา
        • UI Mode (uiMode) มีการเปลี่ยนแปลงรูปแบบการทำงานของเครื่อง อย่างเช่น Car Mode, Dock Mode หรือ Night Mode เป็นต้น
        • Screen Size (screenSize) ขนาดหน้าจอมีการเปลี่ยนแปลง และการหมุนหน้าจอก็ทำให้ขนาดหน้าจอเปลี่ยน (ความกว้างกับความสูงของหน้าจอสลับกัน)
        • Small Screen Size (smallestScreenSize) ขนาดหน้าจอมีการเปลี่ยนแปลง ซึ่งขนาดหน้าจอในที่นี่หมายถึง Physical Screen Size ยกตัวอย่างเช่น ต่อออกจอทีวีเป็นต้น
        • Layout Direction (layoutDirection) ทิศทางของ Layout มีการเปลี่ยนแปลงระหว่าง RTL (Right to Left) หรือ LTR (Left to Right) สามารถปรับได้จากใน Developer Options > Force RTL Layout Direction
        • Font Scale (fontScale) อัตราส่วนของขนาดฟอนต์มีการเปลี่ยนแปลง ซึ่งผู้ใช้สามารถปรับได้ใน Settings > Display > Font Size

        ซึ่งมีไม่กี่อย่างนักหรอกที่นักพัฒนาจะต้องเจอ เพราะหลายๆอย่างนั้นไม่ได้เกิดขึ้นกันบ่อยๆ ดังนั้นที่เจอบ่อยๆก็จะมีแค่ Locale, Screen Size และ Orientation เท่านั้นเอง

Configuration Changes มันทำให้เกิดอะไรขึ้น?

        เรื่องก็เรื่องก็คือ Activity มัน Recreate นั่นเอง ซึ่งก็ต้องยอมเข้าใจน่ะแหละ เช่น ผู้ใช้เปลี่ยนภาษาในเครื่องระหว่างที่เปิดแอปของเจ้าของบล็อกไว้ ภาษาในแอปก็ต้องเปลี่ยนตามไปด้วย (ถึงแม้จะไม่ได้ทำรองรับไว้ก็ตาม) ดังนั้นการที่ Activity มัน Recreate จึงเป็นเรื่องปกติที่ควรรู้ไว้

        แต่การ Recreate ก็ไม่ได้เป็นการ Destroy แล้ว Create ขึ้นมาใหม่แบบเสียเปล่านะ แต่ว่าในขณะที่ Destroy Activity มันก็จะ Save Intstance State ไว้ให้ พอ Create ขึ้นมาใหม่ก็จะ Restore Instance State กลับมาให้เหมือนเดิม

        เฮ้ย! นี่มันเรื่อง Save/Restore Instance State นี่หว่า

        ใช่ครับ Configuration Changes เป็นหนึ่งในสาเหตุที่ทำให้ต้องมีการ Save/Restore Instance State นั่นเอง ถ้าผู้ที่หลงเข้ามาอ่านคนใดยังไม่รู้จักเรื่อง Save/Restore Instance State ให้ไปอ่านก่อนที่ [Android Code] มา Save/Restore กับ Instance State บน Activity ให้ถูกต้องกันเถอะ

        นักพัฒนาส่วนใหญ่เข้าใจว่าถ้าแอปไม่ได้ทำมาให้หมุนหน้าจอได้ ก็ไม่ต้องจัดการกับ Instance State ซึ่งนั่นเป็นความเข้าใจที่ "ผิด" นะ เพราะการหมุนหน้าจอเป็นแค่หนึ่งใน Event ที่เกิดขึ้นของ Configuration Changes เท่านั้นเอง




        ดังนั้นเพื่อรับมือกับ Configuration Changes ที่ไม่พึงประสงค์เหล่านี้ แอปพลิเคชันของผู้ที่หลงเข้ามาอ่านควรทำในส่วน Save/Restore Instance State ด้วยนะครับ

ควรรับมือกับมันอย่างไร?

        จากการทำงานของ Configuration Changes จึงต้องหาวิธีรับมือกับมันเสียหน่อย เพราะ Activity มัน Recreate ดังนั้นเพื่อให้แอปของผู้ที่หลงเข้ามาอ่านยังคงทำงานได้ปกติสุข ทางแอนดรอยด์เค้าออกแบบมาให้มีวิธีรับมืออยู่ 2 ทางด้วยกัน ซึ่งขอให้เลือกตามความเหมาะสมนะครับ

การรับมือแบบแรก : ไม่ต้องทำอะไร

        เพราะว่าเมื่อเกิด Configuration Changes เมื่อไร เดี๋ยวระบบแอนดรอยด์ก็จะจัดการให้เองเกือบทั้งหมด ที่ต้องทำก็คือ Save/Restore Instance State ให้พร้อมไว้ก็พอ

        [Android Code] มา Save/Restore กับ Instance State บน Activity ให้ถูกต้องกันเถอะ
        [Android Code] Save/Restore กับ Instance State ใน Fragment ควรทำอย่างไรกันนะ?

การรับมือแบบที่สอง : จัดการด้วยตัวเอง

        สมมติว่าแอปของผู้ที่หลงเข้ามาอ่านไม่ต้องการให้ Recreate Activity ก็สามารถกำหนดได้ครับ ว่าจะให้ส่ง Event มาบอกใน Activity นั้นๆแทนว่าเกิด Configuration Changes แต่ว่าไม่ต้อง Recreate Activity

        มันคือคำสั่งที่กำหนดลงใน Android Manifest แบบนี้ครับ

<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
    <application ...>
        <activity 
            ...
            android:configChanges="screenSize|orientation" >

            ...

        </activity>
    </application>
</manifest>

        ใช่ครับ มันคือ android:configChanges นั่นเอง!!!

        การใช้คำสั่งนี้ก็เพื่อที่จะบอกกับระบบแอนดรอยด์ว่า "เฮ้ย ถ้าเกิด Configuration Changes ตรงกับที่กำหนดไว้ในนี้เนี่ย เอ็งไม่ต้องมายุ่งกับข้า (Activity) แค่บอกมาก็พอว่ามีการเปลี่ยนแปลงเกิดขึ้น" พูดภาษา Dev ด้วยกันก็คือ Activity จะไม่ถูก Recreate แต่ว่าระบบแอนดรอยด์จะส่ง Event เข้า Method ที่ชื่อว่า onConfigurationChanged เพื่อบอกให้รู้ว่ามี Configuration Changes เกิดขึ้นและเป็นอันที่กำหนดไว้ในคำสั่ง (ซึ่งในกรณีตัวอย่างของเจ้าของบล็อกคือ Screen Size กับ Orientation)

        ผลที่เกิดขึ้นก็คือ ถ้าประกาศ onCongifurationChanged ไว้ใน Activity นั้นๆ มันก็จะถูกเรียกทำงานทันทีพร้อมกับมีการส่งค่า Configuration มาด้วยเพื่อให้ผู้ที่หลงเข้ามาอ่านสามารถไปเช็คเองได้ว่ามีอะไรเปลี่ยนแปลง

public class MainActivity extends Activity {

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        // เมื่อ Orientation กับ Screen Size มีการเปลี่ยนแปลง (หมุนหน้าจอ)
    }
}

        ซึ่งในนี้ก็ควรใช้คำสั่งจัดการกับ Activity ให้เรียบร้อยซะ แต่หลักๆก็คือต้องเช็คก่อนว่า Configuration ที่กำหนดไว้เนี่ย มีค่าเป็นอะไร

        จากตัวอย่างเจ้าของบล็อกกำหนดไว้ว่าถ้า Screen Layout หรือ Orientation เปลี่ยนแปลง (หมุนหน้าจอ) ก็จะให้แจ้งเข้าที่นี่ ดังนั้นเจ้าของบล็อกก็ต้องเช็คว่าตอนนี้ทิศทางของหน้าจอเป็นอย่างไร แล้วใช้คำสั่งจัดการตามต้องการ

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
        // เมื่อหน้าจอเป็นแนวนอน
    } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
        // เมื่อหน้าจอเป็นแนวตั้ง
    }
}

        แต่อย่าลืมว่านอกเหนือจาก Screen Layout และ Orientation แล้ว ก็ยังคงทำให้ Activity ถูก Recreate อยู่ดีนะ เพราะใน Android Manifest ของเจ้าของบล็อกกำหนดไว้แค่สองตัวนี้เท่านั้น

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

อย่าใช้ android:configChanges ผิดจุดประสงค์

        เพราะเจ้าของบล็อกเชื่อว่ายังมีนักพัฒนาอีกหลายๆคนที่เข้าใจเกี่ยวกับ android:configChanges ผิดอยู่

         • เชื่อว่ามันเอาไว้ทำให้ไม่ต้อง Save/Restore Instance State เมื่อหมุนหน้าจอ แต่ในความเป็นจริงแล้วยังมีอีกหลายๆกรณีที่ทำให้ต้องจัดการกับ Instance State อยู่ดี

         • เชื่อว่ามันเอาไว้ Detect Event ของ Configuration Changes เท่านั้น อย่างเช่นอยากดัก Event ตอนที่หน้าจอมีการเปลี่ยนทิศทาง จึงเข้าใจว่ากำหนดใน android:configChanges ก็จะสามารถดัก Event ได้ แต่ในความเป็นจริงคำสังนี้มีไว้เพื่อให้ระบบไม่ Recreate Activity เอง แล้วให้นักพัฒนาจัดการกับ Activity เองต่างหาก ดังนั้นการดักเอาค่าทิศทางไปใช้ แต่ไม่จัดการอะไรกับ Activity เลย นั้นไม่ถูกต้อง (และกรณีนี้จะทำให้ Activity มี Layout แนวตั้งกับแนวนอนคนละแบบไม่ได้ด้วย)

         เพราะงั้นลองมาทำความเข้าใจเกี่ยวกับมันใหม่ดูนะครับ

การทำงานของ onConfigurationChanged สำหรับ Android 7.0/7.1 Nougat

        ในบทความนี้เจ้าของบล็อกได้อธิบายไว้ว่า onConfigurationChanged ที่อยู่ใน Activity นั้นจะไม่ทำงาน ถ้าไม่ได้ประกาศ android:configChange ไว้ใน Android Manifest

        แต่ถ้าทำงานอยู่บน Android 7.0/7.1 จะทำให้ onConfigurationChanged ถูกเรียกอยู่เสมอ ซึ่งดูเหมือนว่าจะเป็นความตั้งใจของทีมพัฒนาแอนดรอยด์อยู่แล้ว (อ้างอิงจาก Issue Tracker เรื่องนี้ https://issuetracker.google.com/issues/37120330)

        ทว่าพอเป็น Android 7.1.1 ขึ้นไป ก็จะไม่ถูกเรียกแล้ว (ถ้าไม่ได้ประกาศ android:configChange) ดังนั้นจึงอาจจะทำให้ผู้ที่หลงเข้ามาอ่านสับสนในการทำงานของ onConfigurationChanged ได้ (เป็นหนึ่งใน Fragmentation ของแอนดรอยด์เลยนะเนี่ย)

        ดังนั้นถ้าเป็นไปได้ให้ถือว่า onConfigurationChanged จะไม่ทำงานเมื่อไม่ได้ประกาศ android:configChange ไว้ใน Android Manifest จะดีกว่า จะได้ไม่มีปัญหากับเวอร์ชันอื่นๆที่ไม่ใช่ Android 7.0/7.1 ในภายหลัง

สรุป

        Configuration Changes เป็นหนึ่งใน Event ที่นักพัฒนาควรจะรู้จัก เพราะมันเป็นหนึ่งในสาเหตุหลักที่ทำให้นักพัฒนาต้องจัดการกับ Instant State ซึ่ง Event ที่เกิดขึ้นจะมีหลากหลายเงื่อนไข ซึ่งการหมุนหน้าจอคือหนึ่งในนั้น ดังนั้นควรเช็คให้ดีว่ามันมีอะไรบ้าง แล้วอันไหนที่น่าจะมีโอกาสเกิดขึ้นกับแอพของผู้ที่หลงเข้ามาอ่านได้ ถึงแม้ว่าจะ Inactive อยู่ก็ตาม

         และที่สำคัญ Configuration Changes จะส่งผลกับการทำงานของ Resource นะเออ เพราะเมื่อเกิด Configuration Changes ระบบก็จะดึง Resource ที่ตรงกับ Configuration ณ ตอนนั้น (เช่น layout-port กับ layout-land เป็นต้น)

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

        • Handling Runtime Changes [Android Developer]
        • Activity Attribute [Android Developer]