Try   HackMD

Android 推播APP教學

UltraLCC
Thu, Sep 20, 2018 8:08 PM

這個教學會教你如何從無到有建立一個Android Studio專案,並完成推播功能

會用到的東西

  • Android Studio
  • Kotlin 語言
  • 一個Google account
  • 一台開發用Android手機

1. 建立AndroidStudio專案

選擇Start a new Android Studio project

輸入專案的名稱,並勾選Include Kotlin support 來使用Kotlin進行開發

建立一個Empty Activity 的專案

完成!ps.如果遇到問題可以看看這裡

2. 建立Firebase連接

一: 連接到Firebase

點擊 Tools->Firebase 呼叫Firebase功能欄

選擇 Cloud Messaging 並點擊 Set up Firebase Cloud Messaging

點擊Connect to Firebase

登入 Google 帳戶

這個網頁不用管他 直接回到Android Studio

如果還沒創建Firebase的專案就選擇上面的創建一個新專案,如果已經有專案就選擇下面的專案加入 點選Connect to Firebase

可能會出現連接失敗,這是他的Bug 再按一次 Connect to Firebase 接著點選 Sync 就會直接連接了

二: 將FCM套件加入專案

點擊Add FCM to your app 之後點擊 Accept changes 即可

三: 創建FCM所需要的class

我們需要創建"MyFirebaseInstanceIdService"和"MyFirebaseMessagingService"兩個Class
並分別繼承"FirebaseInstanceIdService"與"FirebaseMessagingService"

我們創立一個service的package 並創建這兩個class

在這兩個文件中輸入以下程式碼

MyFirebaseInstanceIdService.kt

import android.util.Log import com.google.firebase.iid.FirebaseInstanceId import com.google.firebase.iid.FirebaseInstanceIdService class MyFirebaseInstanceIdService: FirebaseInstanceIdService() { override fun onTokenRefresh() { // Get updated InstanceID token val refreshedToken = FirebaseInstanceId.getInstance().token //用這個函數取得已經生成的Token Log.d("FCMTOKEN", "Refreshed token: " + refreshedToken); } }

MyFirebaseMessagingService.kt

import android.util.Log import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage class MyFirebaseMessagingService: FirebaseMessagingService() { override fun onMessageReceived(remoteMessage: RemoteMessage) { ////// Do when received push notification Log.d("GETMESSAGE", "Message data payload: " + remoteMessage.data) } }

AndroidManifest.xml裏置入下面的程式碼,激活上面的class

AndroidManifest.xml

<service android:name=".service.MyFirebaseMessagingService"> <intent-filter> <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/> </intent-filter> </service> <service android:name=".service.MyFirebaseInstanceIdService"> <intent-filter> <action android:name="com.google.firebase.INSTANCE_ID_EVENT"/> </intent-filter> </service>


四: 設定Firebase

若連接Firebase時選的是新建專案,不需要設定Firebase
當選擇的是加入專案時需要至專案裡點擊剛剛加入的Application

接著直接跳到第四步

現在執行你的app

稍等一下網頁上出現成功就代表設定成功了

3. 嘗試使用Firebase推播

到Firebase網頁 從左邊欄位選擇 拓展 -> Cloud Messaging

點擊傳送第一則訊息


輸入訊息文字並選擇目標為Android,接著按下傳送訊息就可以了
(重要!! 在按下傳送前必須確保App是在背景運行,若App在前景運行,則不會顯示通知,並且請在實體手機上測試)

成功!!

4. 設定Token的接收,設定所屬Topic

MyFirebaseInstanceIdService 內的 onTokenRefresh() 函數會在Token生成時被呼叫
在這個函數底下我們先新增兩行程式碼

override fun onTokenRefresh() { // Get updated InstanceID token ... ... FirebaseMessaging.getInstance().subscribeToTopic("all"); FirebaseMessaging.getInstance().subscribeToTopic("android"); }

這兩行程式碼在onTokenRefresh()時會向Firebase註冊兩個Topic用來對群體發送推播
若想要對單一裝置做推播則需要取得裝置的Token

override fun onTokenRefresh() { // Get updated InstanceID token ... ... ... OKHttpService().sendRegistrationToServer(refreshedToken);///這個函數要自己實現 }

我們可以建立一個http連線將我們的Token回傳給我們的server
我們可以利用OKHttp套件來進行http請求

5. OKHttp

這邊會教你使用OKHttp對server進行post請求

一. 安裝套件

build.gradle(Project) 裡加入版本訊息

buildscript { ... ext.anko_version='0.10.6' ... ... }

buile.gradle(Module) 裡加入這兩個套件的訊息

dependencies { ... ... ////3rd part packages---------- implementation "org.jetbrains.anko:anko:$anko_version" implementation 'com.squareup.okhttp3:okhttp:3.11.0' }

接著按下sync讓專案下載所需的套件

二. 建立Post的方法

在service資料夾下創建名為OKHttpService的Class,鍵入下面的程式碼

import okhttp3.MediaType import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody import java.io.IOException class OkHttpService { val JSON = MediaType.parse("application/json; charset=utf-8") var client = OkHttpClient() @Throws(IOException::class) public fun post(url: String, json: String): String { val body = RequestBody.create(JSON, json) val request = Request.Builder() .url(url) .post(body) .build() val response = client.newCall(request).execute() return response.body()!!.string() } }

三. 實作sendRegistrationToServer() 函數

在param裏組成json,並將url改成自己的server即可~

class OKHttpService { ... ... ... fun sendRegistrationToServer(refreshedToken : String?){ val param = "{\"token\":\"$refreshedToken\"}" doAsync { Log.d("ResponseOK",post("http://10.88.0.216:8888", param)) } } }

四. 設定網路權限

AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>

這樣就完成http的發送步驟了!,Token將會在產生時一併進行發送

6. 設定通知並發送通知,設定自動喚醒

大家還記得上面我們在測試推播時,App必須是背景執行才會有通知顯示,這是因為當時App顯示的通知是Firebase預設的通知形式,必須在App關閉時才會收到,並且不會跳出通知。

Firebase 的通知形式:

Firebase 有兩種通知的形式

  1. notification
    • notification形式為Firebase發送通知的預設形式,用網頁發出的通知都是notification形式
    • App在接到notification形式的通知時,如果App為開啟狀態則 onMessageReceived() 會被觸發,在背景執行或App關閉時不會觸發
  2. data
    • App在接到data形式發出的通知時,無論任何狀態都會觸發 onMessageReceived()
    • data形式的推播只能由呼叫Firebase的API來送出,無法使用網頁送出

為了使我們的應用程式在任何時候都會收到,並彈出通知,我們必須使用data形式的通知來實作MyFirebaseMessagingService裏的onMessageReceived() ,產生自定義的通知

onMessageReceived() 裏加入下面的程式碼,使得remoteMessage收到資料時去呼叫sendNotification() 發送通知

if (remoteMessage.data.isNotEmpty()) { Log.d("GETMESSAGE", "Message data payload: " + remoteMessage.data) sendNotification(remoteMessage) }

接著我們必須要實作sendNotification()

一.建立channel

Android 8 以上的手機需要建立Channel才能成功發送通知

class MyFirebaseMessagingService: FirebaseMessagingService() { ... ... ... private fun sendNotification(remoteMessage: RemoteMessage){ //////// Create notification channel if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val name = "HighChannel" val description = "HighPriorityChannel" val importance = NotificationManager.IMPORTANCE_HIGH val channel = NotificationChannel("HighChannel", name, importance) channel.description = description // Register the channel with the system; you can't change the importance // or other notification behaviors after this val notificationManager = getSystemService(NotificationManager::class.java) notificationManager!!.createNotificationChannel(channel) } } }

二.建立推播物件,並設定發送

class MyFirebaseMessagingService: FirebaseMessagingService() { ... ... ... private fun sendNotification(remoteMessage: RemoteMessage){ ... ... //////// Set up notification val notification = remoteMessage.data val clickIntent = intentFor<MainActivity>() val piClick = PendingIntent.getActivity(this, R.string.app_name,clickIntent,PendingIntent.FLAG_UPDATE_CURRENT) val builder = NotificationCompat.Builder(this,"HighChannel") val myNotifyBuilder = builder.setContentIntent(piClick) .setDefaults(Notification.DEFAULT_ALL) .setPriority(NotificationManager.IMPORTANCE_HIGH) .setAutoCancel(true) .setSmallIcon(R.mipmap.ic_launcher) .setWhen(System.currentTimeMillis()) .setContentTitle(notification["title"]!!) .setContentText(notification["message"]!!) .setVisibility(Notification.VISIBILITY_PUBLIC) if (!notification["subText"].isNullOrEmpty()) myNotifyBuilder.setSubText(notification["subText"]) if(!notification["icon"].isNullOrEmpty()) myNotifyBuilder.setLargeIcon(getBitmapFromURL(notification["icon"]!!)) val notify = myNotifyBuilder.build() val notifyMgr = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notifyMgr.notify(R.string.app_name,notify) } }

三.實作上推播用到的 getBitmapFromURL()

*記得這個要寫在 sendNotification() 外面

class MyFirebaseMessagingService: FirebaseMessagingService() { ... ... ///// Build Bitmap from url (use for custom icon) private fun getBitmapFromURL(strURL: String): Bitmap? { try { val url = URL(strURL) val connection = url.openConnection() as HttpURLConnection connection.doInput = true connection.connect() val input = connection.inputStream return BitmapFactory.decodeStream(input) } catch (e: IOException) { e.printStackTrace() return null } } }

四.設定通知到達螢幕自動亮起

取得電源控制權限

AndroidManifest.xml

<uses-permission android:name="android.permission.WAKE_LOCK" />

加入控制程式碼

class MyFirebaseMessagingService: FirebaseMessagingService() { ... ... ... private fun sendNotification(remoteMessage: RemoteMessage){ ... ... ... ///// Wake up the screen when receive push notification val screenOn = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP, "example") screenOn.acquire() screenOn.release() } }

7. Firebase API 使用(Legacy HTTP Protocol)

Firebase Lagacy HTTP Protocol 使用Post方法觸發
應該設定的地方有

  1. 應該加入兩項header
    • Content-Type : application/json
    • Authorization : key=APIKEY
  2. body應該傳送json
    • 必要項:
      • topic : Token 或 topic
      • data : {dataJson}
    • dataJson :
      • 必要-
      • message : 內容
      • title : 標題
      • -可選擇-
      • subtext : 子標題 (String)
      • icon : 縮圖 (Url)

Example:

{ "to" : "/topics/android", or "to" : "Token", "data" : { "message" : "First Notification", "title": "My Title" } }

APIKEY的取得:

點擊專案裡的專案設定

伺服器金鑰即為APIKEY

8. 完成

完成拉 讚!

9. 一些Debug秘訣和Android Studio小提示

1. 有時候新建專案或開啟專案時會出現下面的錯誤,這是代表有遺漏的套件

點擊右下角的 "Install Build Tools 27.0.3 and sync project"來安裝即可
(有時需要連續安裝多個套件)

10.一些有用的連結

Legacy HTTP Protocol 的官方文件

tags: Android