มาเรียนรู้เทคนิคเจ๋งๆจาก UI ของ KAKAO WEBTOON บนแอนดรอยด์กัน
ตั้งชื่อบล็อกตามชื่อ event เลยแหละ จะบอกว่ารีวิวแอพ KAKAO WEBTOON ก็ไม่น่าใช่ เป็นการพูดถึงในมุมนักพัฒนาแอพพลิเคชั่นบนแอนดรอยด์มากกว่า
รายละเอียดของ event นี้จ้า
โดยกิจกรรม จับแบบปุ๊ปปั๊ป ประกาศเมื่อวาน วันนี้ไลฟ์ตอนสองทุ่มจ้า
Lesson Learn สำหรับนักพัฒนาแอปแอนดรอยด์จากแอป KAKAO WEBTOON ที่เพิ่งเปิดให้บริการในไทยไม่นานมานี้ถ้าใครได้ลองใช้งานแอปก็จะพบว่า Animation และ Transition ต่างๆภายในแอปนั้นอลังการงานสร้างสุดๆดังนั้นเพื่อเรียนรู้เทคนิคต่างๆภายในแอปดังกล่าว เราจึงจัดงานนี้ขึ้นมาเพื่อให้นักพัฒนาแอปแอนดรอยด์ได้เรียนรู้เทคนิคต่างๆที่เกี่ยวกับ UI และ Animation ของแอปตัวนี้ด้วยการ Reverse Engineer กัน
FAQ : Reverse Engineer เป็นเรื่องที่ผิดไม่ใช่หรือ?
การ Reverse Engineer ไม่ได้ผิดกฏหมาย แต่การหาช่องโหว่โดยใช้ Reverse Engineer เพื่อเข้าถึงข้อมูลหรือผลประโยชน์เกินกว่าการใช้งานตามที่แอปกำหนดไว้ต่างหากที่ผิด
FAQ : จุดประสงค์ในการ Reverse Engineer ครั้งนี้
เรียนรู้และทำความเข้าใจเกี่ยวกับเทคนิคที่ใช้ในด้าน UI และ Animation เพื่อให้นักพัฒนาแอปแอนดรอยด์สามารถเข้าใจถึงเทคนิคและความสามารถที่ทำได้บนแอนดรอยด์ เพื่อนำไปประยุกต์ใช้งานตามที่ตนเองต้องการ
จะไม่มีการพูดถึง API หรือ Business Logic ที่สำคัญภายในแอปอย่างเด็ดขาด
สร้าง Awareness ให้กับนักพัฒนาแอปแอนดรอยด์ในเรื่องของการ Reverse Engineer เพื่อนำไปประยุกต์ใช้กับโปรเจคของตัวเองได้อย่างเหมาะสม
เมื่อ Reverse Engineer แล้วจะสามารถก๊อปแอปแบบ KAKAO WEBTOON ได้เลยหรือป่าว?
ทำได้ยากมากสำหรับกรณีนี้ เพราะสิ่งที่เราสนใจคือเทคนิคโดยรวมที่ใช้งานมากกว่า และไม่ได้ลงลึกไปถึงรายละเอียดมากนัก (ไม่ใช่เรื่องง่ายอย่างที่เข้าใจกัน) ดังนั้นสิ่งที่เราจะได้เรียนรู้คือหลักการหรือภาพรวมของเทคนิคที่ใช้ แต่ถ้าจะทำตาม ก็ต้องนำไปคิดต่อเองอยู่ดี
KAKAO WEBTOON คืออะไร?
เป็น platform อ่านการ์ตูนออนไลน์ ของทาง Kakao ของประเทศเกาหลี และได้ลิขสิทธิ์แปลไทยอย่างถูกต้อง ให้เราได้อ่านกัน ตัวแอพตอนแรกๆมีให้อ่านฟรีก่อน อ่านแล้วสนุกก็ซื้อเพื่อปลดล็อกตอนต่อไป
ซึ่ง เพิ่งเปิดตัวมาได้ไม่นาน โดยปล่อยให้ลองเล่นใน Android และทาง Website ในวันที่ 6 มิถุนายน 2564 และเพิ่มปล่อยแอพ iOS ในวันที่ 8 มิถุนายน 2564
เมื่อเราลองเล่นดูพบว่า animation สวยงามและดู smooth มาก ทั้งในแอพ Android และ Website
ในคลิปเราเปิดเส้น layout bound ไว้ อาจจะยุ่บยับหน่อยแหละ แต่ให้เห็นภาพว่าเขาแบ่ง UI ไว้กี่ก้อน จัดอะไรยังไง
ปล. ไม่สามารถอัดส่วนที่ติด content แล้วพอกดแล้วแยกเป็น 2 ส่วนซ้ายขวาได้ ไปลองทำเอาเองเนาะ
เพราะมันสวยงามแบบนี้แหละ เราก็ต้องคิดแหละว่าเขาทำยังไง ใช้อะไร ทำไมถึงคลุมได้หลาย platform เดี๋ยวเราจะมารู้กันใน session ในวันนี้เนอะ
แนะนำการ์ตูนใน KAKAO WEBTOON
แน่นอนว่าเห็นคลิป video ของคุณเอก สิ่งที่ตาเราไปเห็นคือ เห้ยยย มีแปลไทยเรื่อง Itaewon Class อ่ะ แบบตอนนั้นเจอใน Daum แล้วตัวเว็บเป็นภาษาเกาหลีหมดเลย พอมีแปลไทยถูกลิขสิทธิ์แบบนี้ อ่านโล้ดดดดดด
ประเทศเกาหลีใต้นั้นเป็นประเทศที่ซื้อ webtoon พวกนี้มาทำเป็นซีรีส์เยอะมากๆๆๆ อย่างเช่นเรื่อง Itaewon Class ที่กล่าวไป ถ้าใน platform นี้ที่เจอก็จะมีเลขาคิม ที่เราไม่ค่อยอินสักเท่าไหร่ แหะๆ และยังมีเรื่องอื่นๆที่สนุก เช่น The Uncanny Counter โดยตัว counter จะมีหน้าที่จับปีศาจต่างๆที่เพ่นพ่านในโลกมนุษย์กลับไปยังปรโลก
อันนี้ก็เพิ่งรู้ว่าสร้างจากเว็บตูน เป็นซีรีส์ใน Netflix ชื่อว่า Navillera ชื่อไทยคือ ดั่งผีเสื้อร่ายระบำ พล็อตเรื่องคือคุณลุงวัย 70 อยากเต้นบัลเลต์ เลยไปเรียนกับพระเอก ที่ทำตามฝันในการเป็นนักบัลเลต์ชื่อดังจ้า เตรียมทิชชู่กันได้เลยจ้า
จริงๆการ์ตูนพวกนี้อ่ะ ต้นฉบับจริงๆน่าจะจบแล้ว แต่ยังทยอยลงแปลไทยให้เราอ่านกันจ้า
ส่วนตัวสนใจอันนี้555
มาดูเขาแกะโค้ดกันเถอะ
ไลฟ์ย้อนหลังอยู่ที่นี่เน้อ
ปล. บทความนี้ไม่เหมาะกับทีมชาวบ้าน เนื่องจาก มันเป็นเนื้อหาที่ Android Developer หรือคนที่เคยเขียน Android น่าจะเก็ทกันดี แหะๆ
จุดประสงค์ในการไลฟ์ในวันนี้คือการ decompile เพื่อให้เราได้ศึกษาการ manage UI และ transition ต่างๆที่อยู่ในแอพ ว่าเขาทำยังไงบ้างนะ
การ decompile สำหรับแอพแอนดรอยด์ เป็นการ Reverse Engineer อย่างนึง แล้วมันผิดไหมนะ?
สมมุติว่าเราอยากสร้างเครื่องคิดเลข แต่เราทำไม่เป็นเลย อยากรู้ว่าทำยังไง เลยลอง Reverse Engineer อันนี้ไม่ผิด เพราะทำเพื่อการศึกษา
แต่ถ้าเราทำเพื่อเอามาแข่งกัน หรือหาประโยชน์อื่นๆ เช่น หาประโยชน์จากช่องโหว่ของแอพ อันนี้ผิดเต็มๆ
สิ่งที่เราเอามาดูได้จะเป็นส่วนของ front-end คนสามารถดูได้อยู่แล้ว พวก UI ต่างๆ จะไม่ใช่ส่วนของ business logic หรือ infrastructure ด้านหลังเนอะ
การ decompile เครื่องรู้อยู่แล้วว่าโค้ดตรงนี้ทำอะไรบ้าง และแสดงให้เห็น เพื่อให้รู้ได้มากขึ้น
ในที่นี้จะใช้โปรแกรมที่ชื่อว่า JADX มา decompile แอพ KAKAO WEBTOON
ปล. เราชอบใช้ในเว็บออนไลน์ แหะๆ
เมื่อ decompile เสร็จ เอามา export มาเปิดใน Android Studio เป็น editor ที่ Android Developer ไล่โค้ดได้สะดวกที่สุด แต่เราไม่สามารถ compile มาบิ้วลงเครื่องไม่ได้น้า
ไฟล์ที่นำมา decompile ก็คือ APK นั่นเอง เหมือนเป็น zip file ตัวนึงที่บรรจุโค้ดและ resource ต่างๆในแอพในนั้น ส่วน AAB ก็เหมือนเป็น APK หลายๆตัว เป็นการแยก zip file ให้แอพของเรา โหลดได้หลายๆ device ทำให้แอพของเราเบาขึ้น
ตัวอย่าง usecase ง่ายๆ เช่น สงสัยว่าแอพนี้ขอ permission ทำไมเยอะแยะ สามารถเข้าไปดูได้ที่ AndroidManifest.xml
ได้
ด้วยความที่ในไลฟ์พาเราไปดูของเยอะพอสมควร เวลาผ่านไปก็ยิ่งดึก ก็จะงงๆหน่อยๆ เราจะพยายามสรุปให้อ่านง่ายที่สุดเนอะ
Splash Screen ของแอพ ได้แต่ใดมา?
หลักๆที่ดูกันในไลฟ์นี้ คือส่วน Splash Screen ที่เป็น video ยาวๆ แล้วเข้าไปในหน้า Main Content เนอะ
เมื่อเปิดเส้น layout bound จะพบว่า
มันถูกแบ่งเป็น 3 ก้อนไว้ก่อนแล้ว ตาม RecyclerView นั่นแหละ และไม่ใช่ Activity แยกกันระหว่างหน้า Splash Screen กับหน้า Main Content และส่ง intent data มาเปิด แต่ทั้งสองหน้านี้ ถูกเปิดขึ้นมาพร้อมกัน (เข้าใจว่าทั้งสองหน้านี้เป็น Fragment ที่อยู่ใน Activity เดียวกัน) โดยในระหว่างโหลดของจะแสดงภาพยาวๆขึ้นมา จากนั้นค่อยไปหน้า Main Content
ถ้าเจอลักษณะแบบนี้ไม่ต้องตกใจ ดูเหมือนย้อนกลับไปในไฟล์เก่า แต่จริงๆถูกถึงเพื่อสร้าง class ใหม่ ที่ code generate มาให้ในรูปที่มี $
ต่อกันแบบนี้ ให้ข้ามไป สาเหตุ มาจากพวก inline ต่างๆใน Kotlin ซึ่งถ้าเอา Java มาเขียนเหมือน Kotlin ก็จะ pain หน่อยแหละ
ถ้าเราเจอค่าที่เป็นประมาณ magic number ก็คือค่าที่ถูกแปลงมา ให้ลองหาจากใน document ได้ด้วยนะ เช่น เขา set อะไรนะ paint.setFlags(1);
เลยไปหาจาก function setFlags
ว่าใส่อะไรได้บ้าง และตรงกับ constant ตัวไหน อันนี้เอาไว้ใช้วาดกรอบเนอะ
อยากรู้ว่า view นี้วาดอะไร ให้ไปดู onDraw()
ทาง host เดากันว่า วาดเจ้าเส้นเฉียงๆที่คล้ายๆสามเหลี่ยมสีดำ และเดาว่าวาดตรงกลางไว้ เพราะมีการ Parallax ของ view แต่ละ item เมื่อเรา scroll ขึ้นหรือลงก็ตาม
ตัว layout ที่ใช้ พบว่า เขาได้สร้าง custom view หลายๆตัวเพื่อนำมาใช้ในแอพ
ลองเอา id ของสัก view นึงไปลองหาดู พบว่าเขาใช้ View Binding ทำให้เราหาเจอว่า view นี้ถูกใช้ที่ไหน ถ้าเดิมทีที่เราใช้ findViewById
จะออกมาเป็น id ประหลาดๆน่าปวดหัวอยู่ไม่น้อยเลย
มีการใช้ ValueAnimator
ในการ animate ควบคู่ไปกับ ViewModel
ในการเก็บ state ว่าถ้าเรา animate เสร็จแล้ว ให้ไปหน้า Main Content ต่อ จะไม่ทำ animation โดยตรง แต่จะเป็นการส่งค่าแล้วให้คนอื่นไปทำ animation ต่อ
สรุป CoverImageView
เป็นคนทำ animation และอาจจะมีการ set delta ข้อมูลระหว่าง frame (ความแตกต่างระหว่าง frame) เพื่อเอาไปจูนได้ animation ที่แน่นอน ใน 1 วินาที
เบื้องหลังของความลื่นในการแสดง animation ในแอพ
เบื้องหลังก็คือ SplashViewState
เป็นตัวจัดการ state ในการ render view เพื่อทำให้ลื่น control ด้วย view state ตัวเดียว ข้อเสียคือมี enum เยอะ โค้ดเลยเยอะไปด้วย เพราะทุกอย่างกองอยู่ที่เดียวกันหมด
SplashViewState
จะถูกใช้ใน SplashFragment
โดย intent จะมีเป็นของตัวเอง มีการส่งเพื่อโหลดข้อมูลใน splash screen
ในโค้ดพบว่ามีการควบคุมลำดับในการ load data เท่าที่ฟังจะมีการแสดง popup ก่อน, แอพมี version update ไหม (ช่วงนี้เจอบ่อย แต่ก็ไม่มีอะไรให้อัพเดต งองง) ไปเรื่อยๆ พอถึง ready ให้แสดงหน้าต่อๆไปงี้ คือจดไม่ทันแหละ
ดังนั้นมันมีการ load data ตั้งแต่หน้าแรกแล้ว ภาพจึงมาทันทีเมื่อ splash screen เสร็จ ซึ่งตอนแสดง splash screen จะโหลดของเก็บไว้ และหยิบ cache ไปใช้ได้
GLImageView
ถูกใช้ใน SplashFragment
extend จาก GLSurface
ของ Android
FlipView
extend มาจาก FrameLayout ซึ่งเป็น custom view เนอะ ถูกใช้ใน GLImageView
มีหน้าที่ในการทำ surface view ด้วย OpenGL แล้ว animation จะดีกว่าปกติ หรือเปล่าน้าาา?
มีการเลื่อนแล้วเล่น animation ในแอพนี้ การแสดงภาพปกติเราจะใช้ ImageView กัน
แล้วภาพเคลื่อนไหวหล่ะ? gif นี่ไม่น่าใช่ หรือจะเป็น webp animate แต่คิดว่าน่าจะเป็น file video มากกว่า พอไปดูเจอว่าน่าจะมาจาก API เลยจบตรงนี้ เดี๋ยวผิดวัตถุประสงค์
เรื่องแสดง video เราคิดเล่นๆในใจว่า lottie หรือเปล่านะ เพราะมันต้องแสดงเหมือนกันทุก OS น้องเบนก็คิดเหมือนกันแหละดูในแชท (แต่ละอันอย่างปั่น) เราเองเจอว่าเขาใช้ lottie แต่ไม่ได้แงะต่อว่าเขาใช้กับอะไรนะ
การแสดงรูป parallax
เดาว่าน่าจะเป็น fragment ซ้อนกัน ใน Activity เดียว จะประกอบด้วย fragment หน้า splash screen กับ main content เนอะ
fade โดยใช้ FragmentTransition
ใน MainContentFragment
flow คร่าวๆ : splash screen แสดง video ยาวๆ เมื่อข้อมูลดหลดมาครบแล้วก็ fade เพื่อไปหน้า Main Content
โดยตัว tab ด้านบนมันจะเป็นแบบวนลูป เพราะเดาจากชื่อ InfinityTabLayout
ซึ่งเป็น custom view
ข้างในตัว pager adapter ที่ใส่ใน ViewPager ชื่อว่า MainContentPagerAdapter
พบว่ามี 6 tab พอมานับดูในแอพจะมีหายไป 1 อัน คือ novel นั่นเอง อะหรือว่า หรือว่า อนาคตเราจะได้อ่านนิยายในแอพนี้กันนะ
และทุกหน้าจะมี sub tab ข้างในอีกทีนึง ถ้าดูจาก UI เลยอะนะ
Tips: การเขียนชื่อให้ตรงกับ function จะทำให้ทีมไล่โค้ดได้ง่ายขึ้นนะ
ปล. อันนี้ไม่ได้จด แต่ดูโค้ดแล้วจำได้เลย ว่าพอเรามีMainActivity
และHomeActivity
ก็จะงงๆหน่อย ว่าตกลงอันไหนทำอะไรกันแน่นะ
สรุป layout ที่ใช้ใน Main Content หลักๆจะมี CoordinatorLayout
เป็น parent และมี RecyclerView
ในการแสดง item ต่างๆ
ถ้ามุดเข้าไปดูใน RecyclerView
ก็จะมีการใช้ adapter ซึ่งก็มีสิ่งที่เรียกว่า BaseAdapter
ที่เราคุ้นเคยนั่นเอง
ถ้าเจออะไรแปลกๆงี้ก็เข้าไปหากันสดๆเลยงี้
ความขยันของทีมเดฟ KAKAO WEBTOON
เมื่อไปดูใน layout พบว่า เขาทำ layout แยก size ตามขนาดหน้าจอ และแยกตาม Android version ด้วย ทาง host เดาว่า น่าจะมี attribute บางตัวที่ใช้ได้เฉพาะ version 22 (Android 5.1) ขึ้นไป จึงแยก version ออกมาด้วย
ตอนอ่านการ์ตูนจะมี menu ให้กดเลือกตอนได้ พอกดแล้วเท่านั้นแหละ ผ่ากลางให้เลยจ้า โอ้โหวววว อึ้งเลย แบ่ง content เป็นซ้ายขวา
ตัวหนังสือชื่อเร่ิอง น่าจะส่ง image มาให้ render เพราะใครมันจะส่ง custom font มาหน่ะ แต่ละเรื่องก็ใช้ font ไม่เหมือนกันป้ะ เวลาเราเลื่อนใช้สีดำทับ
SideBySideView
เป็น custom view ที่มี ImageView เป็น parent ซึ่งเขาเขียนผ่านโค้ดทั้งหมด คือตัว view ที่ซ้อนกัน ภาพที่อยู่คู่กันซ้ายขวานั่นเอง
custom view จะเขียนได้ 2 แบบ คือ ใส่ xml เข้ามา และเขียน view ทั้งหมดด้วย code
AlphaMovieView
เป็น custom view ที่ extend มาจาก GLTextureView
ซึ่งมันก็เป็น custom view อีกทีนึง
โดยตัวนี้จะใช้ในการ render video แล้วทาง host ก็หาสดๆ พบว่ามีความคล้ายกับตัวนี้ของ wasabeef
แอบพูดถึง wasabeef นิดนุง เขาเป็น Android Developer ชาวญี่ปุ่น ปัจจุบันเป็น Android GDE ด้วยแหะ เราจำได้ว่าเราเคยใช้ library เขา อย่างตัวนี้น่าจะใช้กันบ่อย ของเราใช้ทุกโปรเจกเลยนะ / เห็นเขาที่งาน Google I/o 2021 ด้วย ไม่แน่ใจว่าใช่เขาไหม555 ตอนนั้นร่วมกิจกรรมของ WTM ของฝั่ง APEC พอดีอ่ะ
มีการใส่ video source ไว้ที่ openFd()
เหมือนกับเราต่อท่อให้ไฟล์
ดังนั้นทุกอย่างบน canvas นั้นจะ render ผ่าน CPU ทำให้การแสดงรูปและวิดีโอนั้น โหลดได้ลื่น และมีความ seamless
เจ้า video โปร่งใส่จะใช้เป็นไฟล์ webm แล้วใช้ OpenGL ช่วย render video ผ่าน surface ในการลบ area ที่เราไม่ต้องการออกไป
โค้ดจะมีความคล้ายกับใน library แต่ไม่ได้เหมือนกัน หลักการจะคล้ายๆกัน เช่น prepare
opaque ถ้าเป็นภาพทึบ ค่าจะเป็น false
มี Manager class ทำหน้าที่ transition โดยเฉพาะ
และมี event TransitionManager เดาว่าใช้ในการกดไปดูการ์ตูนเรื่องนั้นๆแล้วพื้นหลังถูกขยาย
และในโค้ดมีการ check ว่าอยู่ในเกาหลีไหมด้วยแหะ เดาว่าต้องมีการแสดง UI บางอย่างที่ต่างกันแน่ๆเลย เราลองมุด VPN แล้วดูไม่ได้อ่ะ เขากันไว้อย่างดีเลย แหะๆ
สรุปแบบมึนๆ จากความเข้าใจของเราเอง
flow จากการแงะในไลฟ์ก็จะเป็นประมาณนี้แหละเนอะ
- แอพเขียนด้วย Kotlin แหละเนอะ
- หน้าแรกที่เป็น Splash Screen กับ Main Content นั้น จะอยู่ใน Activity เดียวกัน แต่แยก Fragment และส่งค่าข้ามไปมาผ่าน ViewModel
- หน้า Splash Screen ใช้ custom view ต่างๆ
- หน้า Main Content ใช้ ConstraintLayout และ RecyclerView
- มีตัวเก็บ state ในการ render view
- มีการสร้าง BaseAdapter
- มีการแยก view ในแต่ละหน้าจอ รวมไปถึงแต่ละ Android version ด้วย จะขยันไปหน้าย
- ในส่วน custom view นี่ก็ขยันเหมือนกัน เขียนด้วยโค้ดทั้งหมดเลยจ้า
- วิดีโอพื้นหลังใส่สามารถทำได้ โดยใช้ไฟล์ webm และใช้ OpenGl ในการ render video ผ่าน Surface โดยลบส่วนที่เราไม่ต้องการออกไป
- ทุกอย่างบน canvas ใช้ CPU ในการ render ทำให้การแสดงรูปและวิดีโอลื่นมากๆ เป็น seamless ด้วย
ก็จะประมาณนี้เนอะ จะมึนๆหน่อยเพราะจบตอนสี่ทุ่มพอดี และ host ก็จะไปเล่นเกมส์ต่อหล่ะ
ปล. ทุกคนชื่นชมว่า dev ที่ทำ KAKAO WEBTOON เก่งมากๆ มากแบบขอคาวระ และ cost ในการทำ animation แบบนี้สูงมาก ตอนนี้กำลังลองหาว่าทางเขาแอบเขียนบล็อกอะไรไหมนะ แหะๆ
สรุปทั้งหมดจากคุณเอก
ปล. อ่ะพอไลฟ์จาก OBS เนี่ย ใน YouTube ได้ 1080p เหมือน host แต่ Facebook ยังไงก็ได้ 720p อยู่ดี บางทีถ้าตัวหนังสือเล็กไปก็อ่านไม่ชัด แต่ก็มีวิธีแก้ปัญหาอยู่ สามารถอ่านจากในนี้ได้เลย ลงโพยไว้หมดแล้วว
download แอพอ่านบล็อกใหม่ของเราได้ที่นี่
ติดตามข่าวสารและบทความใหม่ๆได้ที่
ช่องทางใหม่ใน Twiter จ้า
และ YouTube ช่องใหม่จ้า