รู้จัก Firebase Cloud Messaging (FCM) ตั้งแต่ Zero จนเป็น Hero
จากที่ผมได้มีโอกาสไปแชร์เนื้อหา Firebase Cloud Messaging ในงาน Google I/O 2016 Extended Bangkok ที่ผ่านมา ก็เลยอยากมาสรุปให้ฟังสำหรับคนที่ไม่ทัน และคนที่ไม่ได้ไปร่วมงานครับ
Firebase Cloud Messaging (FCM) คือ บริการส่งข้อความแจ้งเตือนแบบ ข้ามแพลตฟอร์ม (cross-platform messaging) ทั้ง Android, iOS และ Web แบบฟรีๆ ชื่อเดิมก็คือ Google Cloud Messaging (GCM) นั่นเอง
ในการพัฒนาเรื่อง FCM ผมจะขอแยกออกเป็น 3 parts ดังนี้
- การ Set up Firebase และ FCM SDK
- การพัฒนาในส่วนของ Android app
- การพัฒนาในส่วนของ Web server
เมื่อพร้อมแล้ว…ก็มาเริ่มกันเลย เปิด Android Studio สร้าง Project ใหม่มารอไว้ก่อน แล้วไปสู่ส่วนแรกกัน
Part 1 การ Set up Firebase และ FCM SDK
เรื่องการ Set up Firebase ให้ไปดูที่บทความนี้
เมื่อ Set up แล้วก็ไปเพิ่ม FCM SDKใน build.gradle ของ app-level แล้วกด Sync ก็เป็นอันจบส่วนที่ 1 ละ
dependencies {
compile 'com.google.firebase:firebase-messaging:11.8.0'
}
Part 2 การพัฒนาในส่วนของ Android app
เริ่มต้นส่วนที่ 2 ด้วยการสร้างคลาส MyFirebaseInstanceIDService.java และ MyFirebaseMessagingService.java ไว้ที่ app-level โดยจะขออธิบายการทำงานของแต่ละคลาสกันก่อน
MyFirebaseInstanceIDService.java
คลาสนี้เอาไว้รับค่า token โดย method onTokenRefresh จะถูกเรียกเมื่อค่า Token มีการเปลี่ยนแปลง ซึ่งเราจะสามารถ handle ค่า token เองได้ เช่นการส่งไปเก็บหรืออัพเดทใน web server เรา
MyFirebaseMessagingService.java
คลาสนี้ใช้สำหรับรับ Notification แบบ foreground โดย method onMessageReceived จะถูกเรียกเพื่อรับ remoteMessage object จาก FCM ซึ่งผมทำตัวอย่าง ให้มีการรับทั้งแบบ notification payload และ data payload(จะลงรายละเอียดเรื่อง payload ใน Part 3) รวมถึงทำการ custom ตัว notification เพื่อให้เห็นความแตกต่างในการรับ notification แบบ background
เมื่อสร้าง 2 คลาสด้านบนเรียบร้อย ก็ให้ไปประกาศตัว service ภายใน <application> ที่ไฟล์ AndroidManifest.xml กันตามนี้
<service
android:name=".MyFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
</intent-filter>
</service><service
android:name=".MyFirebaseInstanceIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT"/>
</intent-filter>
</service>
ในกรณีที่แอปเราอยู่ในสถานะ background เราสามารถกำหนดค่าเริ่มต้นให้กับ notification icon, notification color และ notification channel id ได้โดยกำหนดค่า meta-data ใน <application> ที่ไฟล์ AndroidManifest.xml ได้ดังนี้
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="default_channel_id" /><meta-data android:name="com.google.firebase.messaging.default_notification_icon" android:resource="@drawable/ic_notification" /><meta-data android:name="com.google.firebase.messaging.default_notification_color" android:resource="@color/colorAccent" />
ต่อไปเราสร้าง layout กันที่ activity_main.xml กัน โดยผมจะให้มันมีหน้าตาแบบนี้ (เอาที่สบายใจเลยละกัน)
คราวนี้เรามาใช้งานกันดู เปิด MainActivty.java ของทุกคนขึ้นมาฮ๊าว์ฟฟฟ
จะสังเกตว่า source code ของ layout ด้านบน ผมได้ประกาศ attribute ที่ชื่อว่า android:onClick ไว้ครบทั้ง 3 ปุ่มแล้ว ดังนั้นก็มาใส่ code กันเลย
public void showToken(View view) {
// แสดง token มาให้ดูหน่อยเสะ
mTextView.setText(FirebaseInstanceId.getInstance().getToken());
Log.i("token", FirebaseInstanceId.getInstance().getToken());
}public void subscribe(View view) {
// สับตะไคร้หัวข้อ news
FirebaseMessaging.getInstance().subscribeToTopic("news");
mTextView.setText(R.string.subscribed);
}public void unsubscribe(View view) {
// ยกเลิกสับตะไคร้หัวข้อ news
FirebaseMessaging.getInstance().unsubscribeFromTopic("news");
mTextView.setText(R.string.unsubscribed);
}
เบื่องต้นเราก็พัฒนาเสร็จละ ต่อไปเราไปทดสอบยิง Notification จาก Firebase Console กัน โดยเมื่อเข้ามาที่ Console เราจะเจอเมนูทางซ้ายมือชื่อว่า Notifications คลิกเบาๆจะเจอ เจ๊คนนึงผิวคล้ำใส่แว่น มาตอนรับ โดยเจ๊จะเชิญชวนให้กดยิง message แรกซะ เอ้า รอรัยกดเลย
จากนั้นเราจะเข้าสู่หน้าที่ให้ระบุรายละเอียดที่เราจะส่งละ
เบื้องต้น จะส่งแบบ Simple มากๆ แค่ใส่ Message text และเลือก User segment แล้วส่งเลย เพื่อดูผลลัพธ์กันก่อน ว่า notification เข้าไหม หากพร้อมแล้วก็กดปุ่ม SEND MESSAGE สีฟ้าๆได้เลย
แทม ทาดาแดม แทมแถ่ม แถ่ม แทม แท้ม…
สำเร็จแล้ว คราวนี้เรามาทำความรู้จักกับเมนู notification ใน Firebase Console เพิ่มขึ้นอีกหน่อย
- Message text : ข้อความที่แสดงใน notification
- Message label (optional) : ไม่ต้องกรอก เพราะมันไม่มีวันแสดงให้เราเห็น 555
- Delivery date : วันเวลาในการส่ง มีทั้งแบบ ส่งทันที และ ตั้งเวลา โดยการตั้งเวลาจะตั้งได้ไกลสุดคือ 1 เดือนนับจากวันที่ตั้ง
- Target : เป้าหมายที่เราจะส่งไป แบ่งออกเป็น 3 แบบ
แบบแรก User segment : เป็นการเลือกแอพที่จะส่ง โดยเลือกจาก package ของแอพ และยังสามารถ fillter ได้อีกทั้ง กลุ่มผู้ใช้, ภาษา และเวอร์ชัน แถมยังเพิ่ม Target หลายๆแอพในการส่งครั้งเดียวได้อีก อะไรจะดีงามพระรามเก้า ขนาดนี้
แบบถัดมาคือ Topic การทำงานคือจะส่งให้ User ที่ได้ subscribe หัวข้อนั้นๆไว้ ถ้าคุณใช้มันถูกทางมันจะทรงอานุภาพมาก (เดี๋ยวอธิบายในส่วนที่ 3 เรื่อง Web server จะเห็นภาพเลย)
แบบสุดท้ายคือ Single device การทำงานคือให้เอา Token มาใส่แบบยิงปืนนัดเดียว ได้นกตัวเดียว
- Advanced options : ตัวเลือกขั้นสูง (แปลตรงไปตรงมา)
จะมี field ให้คุณระบุเพิ่มหลายตัวดังนี้
- Title: ระบุ title เองได้ละ ปกติจะยึดจากชื่อแอพเป็น title
- Custom data: เป็นการส่งค่าแบบ key/value ซึ่งขารับจะต้องรับ data payload
- Priority: ความสำคัญของข้อความ Normal กับ High (อธิบายเพิ่มใน part 3)
- Sound: ส่งเสียงมะ
- Expires: ข้อความจะถูกเก็บไว้ได้ไม่เกิน 1 เดือนหลังจากเรากดส่ง (ข้อความที่เราส่งจะยังไม่ถูกส่งไปยังเครื่องปลายทาง ถ้าไม่มีสัญญาณ หรือ ไม่ได้เปิดเครื่องไว้)
เมื่อรู้จักการส่งขั้นสูงแล้ว คราวนี้ก็ลองกดส่งอีก 2 ครั้ง
จะเห็นว่าเราสามารถ custom title ได้ละ และที่ส่ง 2 ครั้งเพราะต้องการให้เห็นความต่างของการรับ notification แบบ background และ foreground ว่ามันต่างกัน นั่นสิแล้วมันต่างกันยังไงบ้าง
- background : ถ้าแอพไม่ได้ถูกเปิดอยู่ เวลาส่ง notification มา จะไม่สามารถ custom การแสดง notification ใน system tray ได้ อารมณ์เหมือนเข้า notification center ของ iOS และเมื่อกดมันก็จะพาไปเปิดหน้า launcher ของแอพ โดยในหน้า launcher จะสามารถ handle ตัว data payload ได้(ถ้าส่งมา)
- foreground : ถ้าแอพเปิดอยู่ เวลาส่ง notification มา ข้อมูลทั้งหมดจะวิ่งเข้าไปที่คลาส MyFirebaseMessagingService.java ซึ่งเราจะสามารถ handle message ได้ทั้ง notification payload และ data payload ทั้งยังสามารถ custom ตัว notification ที่จะแสดงใน system tray และ event เมื่อกด notification ได้
Part 3 การพัฒนาในส่วนของ Web server
เริ่มต้นด้วยการไปที่ Firebase Console กันอีกละ จากนั้นเข้าไปที่ Project ที่เราสร้างไว้ เลือก Project settings แล้วเลือก Tab ที่ชื่อว่า CLOUD MESSAGING ก็จะเจอ Server key ให้ Copy มาซะ
เรื่อง Protocol ในการส่ง Message จะมีด้วยกัน 2 แบบคือ HTTP และ XMPP และในตัวอย่างนี้ผมจะใช้ HTTP โดยการส่งจะประกอบไปด้วย 3 ส่วน คือ
- URL ปลายทางที่จะใช้คือ https://fcm.googleapis.com/fcm/send
- HTTP HEADER
- Content-Type:application/json
- Authorization:key=YOUR_SERVER_KEY
YOUR_SERVER_KEY ก็คือสิ่งที่เรา copy มาจากด้านบนนั่นเอง - Messages (JSON)
คราวนี้เรามาโฟกัสกันที่ Messages (JSON) กันหน่อย เพราะมันสำคัญมากเลย ใน Message นั้นจะประกอบไปด้วย 3 ส่วน (อีกละ)
- Targets คือ เป้าหมายที่จะส่ง messages ไป ตัวอย่าง
1.1. to: เป็นการระบุ token 1 ตัว หรือ topic 1 เรื่อง
1.2. registration_ids: เป็นการระบุ token หลายๆตัว ที่บรรจุอยู่ใน array
1.3. condition: เป็นการระบุ topic ที่มากกว่า 1 เรื่อง ซึ่งสามารถใช้ operator ด้วยได้ เช่น ‘Website’ in topics && (‘Android’ in topics || ‘iOS’ in topics) - Options คือ ตัวเลือกเพิ่มเติม ตัวอย่าง
2.1. priority: ความสำคัญของข้อความ มีค่า Normal กับ High เช่น ถ้า android คุณกำลังหลับไหลใน doze mode หรือแอพใน iOS ของคุณไม่ได้ถูกเปิด และไม่ได้อยู่ใน background การระบุค่า priority เป็น high จะปลุกมันขึ้นมาได้ - Payload คือ ชุดของข้อความที่จะส่ง จะประกอบไปด้วย 2 แบบคือ
3.1. Notification: ตัวนี้เกิดมาใหม่ เพื่อให้รองรับการทำงานข้าม platform ได้ โดยทาง FCM จะกำหนดตัวแปรมาให้เราใช้ ซึ่งจะแตกต่างกันเล็กน้อยสำหรับแต่ละ Platform เช่น
Android จะมี title, body, sound, icon, click_action…
iOS จะมี title, body, sound, badge, click_action
3.2. Data: ตัวนี้มีมาตั้งแต่สมัย GCM เป็นชุดข้อความที่ให้เราสามารถกำหนด key/value เองได้
ตัวอย่างหน้าตา messages ก่อนส่งก็จะเป็นแบบนี้
สามารถดูรายละเอียดเพิ่มเติมแบบจัดเต็มที่ https://firebase.google.com/docs/cloud-messaging/http-server-ref
เอาหละเรามาสร้าง class FCM ด้วย PHP กันดีกว่า ซึ่งตัวอย่าง target ผมจะใช้ to เพื่อส่ง topic ชื่อ news นะครับ
สร้าง class เสร็จแล้ว ก็เขียน request ดูตามนี้
เอ้า ยิง!!!…ตู้ม
เมื่อเราทำได้ถึงจุดนี้ อาจมีคำถามว่า ถ้าส่งข้อความด้วย notification payload และเราไม่ได้เปิดแอพอยู่ (background) อยากให้กด notification แล้วกระโดดไปยังหน้าที่ต้องการ ไม่ใช่หน้า launcher จะทำได้ไหม…จุดนี้ทีม Firebase เค้าเตรียมมาให้ละครับ โดยเราจะต้องทำเพิ่ม 2 ส่วนด้วยกันคือ
- ระบุค่าตัวแปร click_action ใน notification payload
เช่น click_action => OPEN_DETAIL_ACTIVITY - ระบุ intent-fillter ใน activity ที่คุณต้องการให้เปิดหลังคลิก notification
<intent-filter>
<action android:name="OPEN_DETAIL_ACTIVITY" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
ที่เหลือก็อาจจะส่งค่าแนบมาใน data payload เพื่อรับค่าและ handle งานบางอย่างที่ activity นั้นๆต่อไป
จบละ…แต่ช้าก่อน Android Developer คงไม่ใช่ทุกคนที่เขียน PHP ได้ช่ายมะ งั้นเอานี่ไปเลย ผมเพิ่ม code ให้คุณใน Android Project ให้ยิงผ่านแอพกันไปเลย
Source code ทั้งหมดของ Android ผมเอาขึ้น GitHub ให้ละนะครับ โหลดไปดูได้
กรณีศึกษาเรื่อง บร๊ะลานุภาพของ topic
Before: เมื่อก่อนผมเคยยิง notification จาก server รอบละ 1 ล้าน devices ซึ่งผมใช้วิธี query ตัว token มาจากฐานข้อมูลทั้งหมดก่อน แล้วแยก Thread ออกไป 10 Threads เพื่อให้กระจายยิงไปแบบเท่าๆกัน คือ Thread ละ 100,000 devices ในแต่ละ request เราสามารถบรรจุ token ได้ 1,000 tokens ซึ่งใช้เวลาทั้งหมดประมาณ 5–10 นาที แต่บางครั้ง Thread บางตัวมี error หรือ timeout เราก็จะต้องมา handle ต่อว่าจะ retry หรือ เพิ่ม Thread ต่อไป ซึ่งมันดูไม่ scalable แบบอัตโนมัติ
After : เมื่อผมมาทำความเข้าใจเรื่อง topic แล้วก็พบว่า Firebase จะ handle สิ่งที่ผมทำใน before ให้หมด โดยผ่านแค่ request เดียว เช่น ถ้าผมต้องการจะส่ง Message ให้ทุกคนที่เคย login แล้วเท่านั้น ผมก็จะไป subscribe กลุ่มผู้ใช้หลังจาก login ใน topic ชื่อ logined จากนั้นตอนส่งแค่เพียงเราเลือกส่ง topic ชื่อ logined ตัว Firebase ก็จะจัดการส่งให้หมดละ ทำให้ชีวิตเราดีงามพระราม 8.9 ขึ้นอีก
เคล็ดขัดยอก…เอ้ย!…เคล็ดไม่ลับ…เอ้ย!…เคล็ดลับ…เอ้ย! ถูกแล้ว
ท้ายที่สุดผมขอเสนอ “สิ่งที่คุณอาจไม่รู้มาก่อนใน Firebase Cloud Messaging” เพราะคุณอาจไม่เคยพบเจอ หรือยังไม่มีใครบอกคุณ ดังต่อไปนี้ นี้ นี้…
- สำหรับ Android ถ้าต้องการให้ notification วิ่งเข้า MyFirebaseMessagingService.java ตลอดเพื่อที่จะ custom ทุกอย่างเอง เวลาส่ง message ให้ส่งเฉพาะ data payload
- registration_ids จะต้องมี token บรรจุอยู่อย่างน้อย 1 token แต่ไม่เกิน 1,000 tokens ต่อการยิง 1 ครั้ง ไม่เช่นนั้นยิงไปก็สูญเปล่า
- Topics ใน Notification Console จะโผล่มาช้าหน่อย หลัง subscribe ประมาณ 3–6 ช.ม อย่าได้ตกใจเดี๋ยวมันก็มา แต่หากเขียนโปรแกรมยิงเองจาก Web server ก็ยิงได้ทันทีไม่ต้องรอ
- Condition สำหรับ topics จะรองรับแค่ 2 operators ต่อ 1 expression เช่น ‘Website’ in topics && (‘Android’ in topics || ‘iOS’ in topics)
- ขนาดของ Notification payload ต้องไม่เกิน 2KB
- ขนาดของ Data payload ต้องไม่เกิน 4KB
- Token มีโอกาสเปลี่ยนแปลงได้ ดังเงื่อนไขต่อไปนี้
- แอพใช้คำสั่งลบ Instance ID
- restore แอพนั้นๆไปที่ device ใหม่
- uninstall แล้ว reinstall แอพ
- ผู้ใช้ clear app data
ช่วงสุดท้ายของรายการ
ผมได้แชร์ Slide ของหัวข้อ Firebase Cloud Messaging ที่ใช้ในงาน Google I/O 2016 Extended Bangkok ไว้ให้ด้วยครับ
สำหรับบทความนี้ใช้เวลาไป 3 วันกว่าจะเขียนเสร็จ เนื้อหาลงลึกในหลายประเด็น สำหรับ FCM ก็หวังว่าบทความนี้จะเป็นประโยชน์กับ Android Developer ที่ต้องการจะพัฒนาระบบ Push Notification ขึ้นมาใช้งาน ตั้งแต่ต้นน้ำยันปลายน้ำ ได้ลองดู แล้วพบกันใหม่บทความหน้าครับ…ราตรีสวัสดิ์ พี่น้องชาวไทย