
Table of contents

Nonthawit
CEO | Engineer | Designer
VIEW
2,601
CATEGORY
LAST UPDATED
January 18, 2017

อันดับแรกถ้าเราต้องการทดสอบการ save state บน android ให้ไปที่
Setting > Developer options > Don’t Keep Activities
เพื่อบอก activity ว่าเมื่อกดย่อแอปให้ save state ไว้แล้วทำลายตัวเองทิ้งทันที ทำให้เวลากลับเข้าแอปใหม่ onRestoreInstanceState(…) จะได้ถูกเรียกทุกครั้ง

จากรูปข้างบนเราสร้าง ViewGroup ขึ้นมา 1 ตัวที่มี ImageView, EditText และ Switch ไม่ได้ทำอะไรมากมาย และแปะบน activity ไว้ 3 ตัว เบื้องต้นเราทำการย่อแอปและกลับมาใหม่จะสังเกตเห็นว่า “333” ที่อยู่ตัวสุดท้าย จะถูกนำมาใส่ให้ EditText ทุกตัว ซึ่งผิด เราคาดหวังว่ากลับมาต้องได้ตัวเดิม “111” “222” และ “333” ตัวเดิม
กรณีนี้ เป็นกรณีที่นักพัฒนามองข้ามหรือหลงลืมกันไป ซึ่งวันนี้เราจะมาทำให้มันถูกต้องกันครับ 😎
แต่ก่อนอื่นเราต้อง
เราขอแบ่งออกเป็น 2 เรื่องคือ save state สำหรับ Activity/Fragment และ View/ViewGroup

จากภาพข้างบน onSaveInstanceState( ) จะถูกเรียกทุกครั้งเมื่อกดย่อแอป
แต่ onRestoreInstanceState( ) จะถูกเรียก “ก็ต่อเมื่อ” Activity/Fragment ตัวนั้นตาย (ยกเว้นการตายที่เกิดจาก user กดออกแอปเองนะ) เพราะฉะนั้นเราไม่สามารถคาดเดาได้ว่า Activity/Fragment ตัวนั้นจะตายตอนไหน ตัวอย่างเช่น แอปอื่นๆ ram หมด จนต้อง clear แอปที่อยู่ในสถานะ background ทิ้งไป
ดังนั้นเพื่อให้ onRestoreInstanceState( ) ถูกเรียกทุกครั้ง เราต้องไป enable Don’t Keep Activites ที่ Developer Options เสียก่อนเพื่อให้ Activity ตายเสมอเมื่อกดย่อแอป
ในส่วนของ Activity/Fragment ตรงไปตรงมาครับเราไม่ต้องแก้อะไร เพราะหน้าที่ของ Activity/Framgment คือ save state ตัวมันเอง และมันจะไปเรียก onSaveInstanceState( )/onRestoreInstanceState( ) ของ View/ViewGroup แต่ละตัวให้ต่ออีกที เพราะฉะนั้นต้นเหตุมันจึงอยู่ที่ View/ViewGroup ที่เราเอาไปแปะบน Activity/Fragment ในหัวข้อต่อไปที่กำลังจะพูดถึงนั่นเอง

ส่วนของ View/ViewGroup อย่างที่เราบอก Activity/Fragment จะเรียก view แต่ละตัวให้ทำการ save state ของตัวมันเอง เพราะเบื้องหลังแล้ว saveHierarchyState( )/restoreHierarchyState( ) ของ view แต่ละตัวจะถูกเรียก ไล่มาจนถึง onSaveInstanceState( )/onRestoreInstanceState( ) ตามภาพข้างบนนั่นเอง
ถ้าเราเข้าไปดูในแต่ละ method ปัญหาจะอยู่ที่
void dispatchSaveInstanceState( SparseArray )
และ
void dispatchRestoreInstanceState( SparseArray )
เป็น method ที่ไล่ save/restore state ของ ChildView ที่อยู่ใน ViewGroup แต่ละตัว โดยอิงจาก viewId และจะทำการ save state ลงใน SparseArray ตัวกลางที่ส่งเข้ามาผ่าน parameter
ปัญหาเลยเป็นดังภาพ

จะเห็นว่า state ของ ChildView ทุกตัวจะถูกนำไปใส่ที่ SparseArray ตัวกลาง ทำให้ state ถูกนำไปทับ viewId เดิมตลอด ทำให้เวลากดย่อแอป และกลับมาใหม่มันจึงดึง state ล่าสุดมาใส่แทน ค่าใน EditText จึงเป็น “333” หมดน้ำตาจะไหล 😭
ซึ่งนี่ถือเป็นเคสที่ร้ายแรงพอสมควรเลยนะ ถ้าเกิดข้อมูลตัวสุดท้ายของ EditText เป็นข้อมูลอย่าง sensitive อย่างเช่น password หรือ รหัสบางอย่าง นี่ตายแน่ๆคนอื่นเห็นหมดกันพอดี
ไอเดียการแก้ไขเรื่องนี้คือ เราต้องทำให้การเก็บ state ของแต่ละ ViewGroup ให้แยกอิสระต่อกันโดยต้องสร้าง SparseArray มาอีกชั้นก่อนจะยัดใส่ลงใน SparseArray ตัวกลางดังภาพ เพื่อกันการ saved state ไปทับ viewId เดิม

เมื่อเห็นภาพแล้วทั้งหมดแล้ว ถึงเวลาที่เราต้องมาลง code กันละ โดยการสร้าง base class ขึ้นมาหนึ่งตัว
override 2 method
เพื่อบอก ViewGroup ตัวนี้ว่าไม่ต้องไปไล่ save/restore state ChildView นะ เพราะเราจะทำการ handle เอง
ต่อไป override
void onRestoreInstanceState(Parcelable state)
และ สร้าง method เพิ่มอีก 2 ตัวคือ
Parcelable onSaveInstanceChildState( ChildSavedState ss )
และ
void onRestoreInstanceChildState( ChildSavedState ss)
Parcelable onSaveInstanceChildState( ChildSavedState ss )
จะเห็นว่าใน method นี้เราสร้าง SparseArray ไว้อีกชั้นเพื่อกันการ save state ไปซ้ำ viewId เดิม และ
void onRestoreInstanceChildState( ChildSavedState ss)
ก็ในลักษณะเดียวกัน
สุดท้ายสร้าง ChildSavedState เป็น inner class เพื่อเก็บ state ต่างๆของ ChildView นั่นเอง
แค่นี้ก็ได้ Base class สำหรับ ViewGroup เป็นอันเสร็จนำไปใช้ต่อกันได้
สร้าง class SwitchViewGroup ที่ extend BaseViewGroup ที่เราทำไว้แล้ว
override 2 method
สังเกตใน method onSaveInstanceState( ) เราเรียก
SavedState ss = (SavedState) onSaveInstanceChildState(
new SavedState( superState )
);
เพื่อไปไล่ save state ของ ChildView ให้เสร็จก่อน เพราะฉะนั้น SavedState ss จะมี state ของ ChildView อยู่ เสร็จแล้วเราจึงทำการ save state ของ SwitchViewGroup (ตัวเอง) ต่อ
และสร้าง SaveState inner class ที่ extend ChildSavedState ที่เราทำเป็น inner class ไว้เช่นกัน
แค่นี้การทำ custom ViewGroup ก็เป็นอันเสร็จเรียบร้อย

เป็นตัวอย่าง boilerplate code ของ CustomViewGroup ลองเอาไปปรับใช้กันดูนะครับวันนี้เอาไว้แค่นี้ก่อนเจอกัน blog หน้าเนาะ
อย่าลืม share ให้มนุษย์ Android คนอื่นด้วยหละ 😎
KNOWLEDGE


Nonthawit
CEO | Engineer | Designer
เข้าใจการทำ Selector แบบ Ripple effect


Nonthawit
CEO | Engineer | Designer
20 สิ่ง ที่ได้หลังจากเป็น Android developer ที่ Nextzy 3 เดือน
บทความนี้แชร์ประสบการณ์ 3 เดือนแรกของการทำงานเป็น Android Developer ที่ Nextzy ครอบคลุมทั้งด้าน technical เช่น MVP architecture, Android Lifecycle, ProGuard, Git workflow และการเขียน Unit Test รวมถึงด้าน soft skill อย่างการสื่อสารกับทีม, การแชร์ความรู้, และการเขียนโค้ดให้ readable และยืดหยุ่น นอกจากนี้ยังสะท้อนวัฒนธรรมองค์กรที่เน้นทีมเวิร์ค การ review โค้ด และบรรยากาศการทำงานที่สนุกสนาน ซึ่งล้วนช่วยลด learning curve และพัฒนาทักษะได้เร็วกว่าการเรียนรู้คนเดียว
![[Tip/Trick] วิธีติดต่อกับ WebView ผ่าน JavascriptInterface มันเท่มาก](https://image.nextzy.tech/1_Aleix_TFC_7yz_Qh_Q_Sx_GV_Rqxw_a29e28219a.png)

Nonthawit
CEO | Engineer | Designer
[Tip/Trick] วิธีติดต่อกับ WebView ผ่าน JavascriptInterface มันเท่มาก