2019年3月17日 星期日

[Android] 完整加入 Firebase 推播雲端通知訊息實作教學 (FCM)


2019年4月 GCM 即將退場,Google 主推 Firebase Cloud Messaging 來為我們的 App 提供推播雲端通知訊息,然而 Android Studio 也內建好 Firebase 能夠快速引導我們把 FCM 納入 Android App 專案裡面。

接下來會用簡單的方式引導概念並手把手完成整個步驟,以及需要的注意事項。
廢話不多說,那就往下開始吧!



Firebase Cloud Messaging

  • 簡稱 FCM,俗稱推播
  • 透過此服務我們可以即時發送訊息給多台手機
  • 例如:新文章通知、有人回覆你的聊天...等

引入專案

首先開啟 Android Studio 專案,建議專案設定 minSdkVersion 16,其他的設定就自由選擇。




點選上方的 Tool -> Firebase ,右邊則展開 Firebase 所有服務,選擇 Cloud Messaging




到了 這一頁直接按 Connect to Firebase,如果你沒登入會引導去 Google 登入頁,
此時登入再切回 Android Studio 即可,

有登入的話會讓你選此 App 要屬於哪個 Firebase 專案。



如果你在 Firebase 後台網頁建立好專案,那麼可以選 Choose an existing Firebase or Google project
都沒有的情況下會選第一個,讓 Android Studio 去 Firebase 後台建立一個專案連接 App。

最後就是按藍色按鈕 Connect to Firebase 了!
Android Studio 進度條會跑一下,耐心等候。





此時把左上角的 Android 切成 Project ,會發現 app 資料夾裡面多了一個 google-service.json
而右邊變成 Connected ,表示正確連上 Firebase 了。





於是到 Firebase 後台 https://console.firebase.google.com
會發現多了一個我們的專案 FCMDemoApp (我們自己取名的),
點進去後在網頁左上角有個設定按鈕,點選專案設定



網頁下面會看到 App 相關資訊,對照 package name 會一樣,
而且也幫你準備好 google-service.json,如果遺失可以從此下載。

app/build.gradle 下的 dependencies 區塊多加一行
 implementation 'com.google.firebase:firebase-messaging:17.4.0'
在最外層 build.gradle 下的 dependencies 區塊多加一行
 classpath 'com.google.gms:google-services:4.2.0'

再來新增一個 Class 繼承 FirebaseMessagingService,並且 Override onMessageReceivedonNewToken,加入 Log.i 以便等一下看是否接收到訊息。

  • onMessageReceived 收到的訊息都在這接收,請注意是在背景執行緒執行,官方建議不要做超過十秒的事情,否則要另開 Job 處理
  • onNewToken 會產一個手機裝置的 token ,傳送訊息識別用
 public class MyFirebaseService extends FirebaseMessagingService {
    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);

        if (remoteMessage.getNotification() != null) {
            Log.i("MyFirebaseService","title "+remoteMessage.getNotification().getTitle());
            Log.i("MyFirebaseService","body "+remoteMessage.getNotification().getBody());
        }
        
    }

    @Override
    public void onNewToken(String s) {
        super.onNewToken(s);
        Log.i("MyFirebaseService","token "+s);
    }
}
並於 AndroidMenifest.xml 的 <application> 標籤裡加入,com.example.fcmdemoapp.MyFirebaseService 改成你的 Service Class 位置
 <service android:name="com.example.fcmdemoapp.MyFirebaseService">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>
在 MainActivity 加入FirebaseInstanceId.getInstance().getInstanceId();,多了 addOnCompleteListener 是為了方便看每次 token 的變化。 Token 通常只有 App 第一次安裝會產一組新的。
 FirebaseInstanceId.getInstance().getInstanceId()
                .addOnCompleteListener(new OnCompleteListener() {
                    @Override
                    public void onComplete(@NonNull Task task) {
                        if (!task.isSuccessful()) {
                            return;
                        }

                        if( task.getResult() == null)
                            return;
                        // Get new Instance ID token
                        String token = task.getResult().getToken();

                        // Log and toast
                        Log.i("MainActivity","token "+token);
                    }
                });

此時 Run 你的 App,Android Studio 的 Logcat info 會印出你註冊的 token,可以先複製記下來。



回到 Firebase console,左側選擇 拓展 > Cloud Messaging ,並且按 Send your first message


 


直接跳到第二步把應用程式 package name 填好。(不填收不到東西)



跳回第一步,在介面上輸入你的通知名稱跟通知文字,最後按下傳送測試訊息。



在新增FCM註冊憑證那個輸入欄位貼上你的 Token (1.),並且按 + 符號 (2.),最後點選測試(3.)。




然後 Logcat 就會印出收到的訊息了。這個測試是針對單一裝置 Token 發送訊息。






也可以走完所有步驟設定,按審查 > 發佈
則對所有裝置含有此 App 發送訊息。


加入 Notification


只有印出 Log 似乎單調了點,通常會加入 Notification UI 提醒,
那麼三步驟即可完成。

首先,在 MainActivity (或是你額外設定的初始化 Activity) 加上 Oreo 以上版本需要 chnannel id。

   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            // Create channel to show notifications.
            String channelId  = "default_notification_channel_id";
            String channelName = "default_notification_channel_name";
            NotificationManager notificationManager =
                    getSystemService(NotificationManager.class);
            notificationManager.createNotificationChannel(new NotificationChannel(channelId,
                    channelName, NotificationManager.IMPORTANCE_LOW));
        }
再來到 MyFirebaseService 加入 sendNotification 方法。 依照自己需求可自由調整樣式。
  private void sendNotification(String messageTitle,String messageBody) {
        Intent intent = new Intent(this, MainActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent,
                PendingIntent.FLAG_ONE_SHOT);

        String channelId = "default_notification_channel_id";
        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder notificationBuilder =
                new NotificationCompat.Builder(this, channelId)
                        .setContentTitle(messageTitle)
                        .setSmallIcon(R.drawable.ic_launcher_foreground)
                        .setContentText(messageBody)
                        .setAutoCancel(true)
                        .setSound(defaultSoundUri)
                        .setContentIntent(pendingIntent);

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        // Since android Oreo notification channel is needed.
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel(channelId,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);
            notificationManager.createNotificationChannel(channel);
        }

        notificationManager.notify(0, notificationBuilder.build());
    }
然後在 MyFirebaseService 的 onMessageReceived 直接添加 sendNotification (因為 Notification 屬於 RemoteView 所以可以直接在非 ui thread 使用)
   @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);

        if (remoteMessage.getNotification() != null) {
            Log.i("MyFirebaseService","title "+remoteMessage.getNotification().getTitle());
            Log.i("MyFirebaseService","body "+remoteMessage.getNotification().getBody());
            sendNotification(remoteMessage.getNotification().getTitle(),
                             remoteMessage.getNotification().getBody());
        }
    }

這樣簡單的推播功能就完成了!
我們可以看到模擬器已經收到發送的 FCM 通知。


需注意的地方


因為前述步驟我們都是送通知名稱通知文字,在 Firebase 的文件提到,
如果我們的 App 放到背景,甚至是關閉,推播的  Notification 會變成系統預設樣式
然而預設系統樣式能夠變化的只有 small icon 跟 文字顏色。
不會 call onMessageReceived 且如果點擊進 App 收資料會從 Activity Intent 接收
(詳細連結: https://firebase.google.com/docs/cloud-messaging/android/receive)

如果希望保持一致,都在 onMessageReceived 接收,並且不會跑去系統,
那麼就必須把資料放在 data (自訂資料) 而不要送通知標題跟通知文字。
但 Firebase 後台不允許這麼做,故要做的話需與自家後台配合。

若要測試則可以到 http://pushtry.com/
MyFirebaseService 會改成如下,getData 後面的 Key 值自訂。
     @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);

        if (remoteMessage.getData()!=null) {
            sendNotification(remoteMessage.getData().get("title"),
                 remoteMessage.getData().get("msg"));
        }
    }


伺服器金鑰可以從 Firebase 後台左邊的專案設定裡找到,以下為發送訊息的 json 格式
   {"to":"你的裝置token","data":{"title":"測試 data 標題","msg":"測試 data 文字"},"priority":"high"}

中途遇到的坑

搭配 Android Studio 引導的 Firebase 教學
使用 'com.google.firebase:firebase-messaging:17.3.2'
classpath 'com.google.gms:google-services:4.1.0'

會出現 Error!!!

錯誤訊息

ERROR: Unable to resolve dependency for ':app@debug/compileClasspath': Could not resolve com.google.android.gms:play-services-stats:15.0.1.Show Details
Affected Modules: app

解法

更新最新版號就好了。算是官方的 library 一個 BUG。

完整程式碼

GitHub 專案連結

想深入了解 Firebase 詳細的功能請參考以下書籍


⇒ 新手入門 Android 實戰開發 APP,轉職工程師、充電學習必選好評課程懶人包


1 則留言: