# Android 推播APP教學 > [name=UltraLCC] > [time=Thu, Sep 20, 2018 8:08 PM] ##### 這個教學會教你如何從無到有建立一個Android Studio專案,並完成推播功能 > [TOC] > > [color=#5cb73a] ## 會用到的東西 * Android Studio * Kotlin 語言 * 一個Google account * 一台開發用Android手機 ## 1. 建立AndroidStudio專案 選擇**Start a new Android Studio project** [![](https://i.imgur.com/6yhBXXG.png)](https://i.imgur.com/6yhBXXG.png) 輸入專案的名稱,並勾選Include Kotlin support 來使用Kotlin進行開發 [![](https://i.imgur.com/tFuhgEZ.png)](https://i.imgur.com/tFuhgEZ.png) 建立一個Empty Activity 的專案 [![](https://i.imgur.com/kSjj7kl.png)](https://i.imgur.com/kSjj7kl.png) 完成![ps.如果遇到問題可以看看這裡](#1-有時候新建專案或開啟專案時會出現下面的錯誤,這是代表有遺漏的套件) [![](https://i.imgur.com/yBSpATo.png)](https://i.imgur.com/yBSpATo.png) ## 2. 建立Firebase連接 ### 一: 連接到Firebase 點擊 **Tools**->**Firebase** 呼叫Firebase功能欄 [![](https://i.imgur.com/cItWZbR.png)](https://i.imgur.com/cItWZbR.png) 選擇 **Cloud Messaging** 並點擊 **Set up Firebase Cloud Messaging** [![](https://i.imgur.com/lIyVXxQ.png)](https://i.imgur.com/lIyVXxQ.png) 點擊**Connect to Firebase** [![](https://i.imgur.com/4hxyuPF.png)](https://i.imgur.com/4hxyuPF.png) 登入 Google 帳戶 [![](https://i.imgur.com/8I29II1.png)](https://i.imgur.com/8I29II1.png) 這個網頁不用管他 直接回到Android Studio [![](https://i.imgur.com/fP6xp2N.png)](https://i.imgur.com/fP6xp2N.png) 如果還沒創建Firebase的專案就選擇上面的創建一個新專案,如果已經有專案就選擇下面的專案加入 點選**Connect to Firebase** [![](https://i.imgur.com/LL23zpU.png)](https://i.imgur.com/LL23zpU.png) 可能會出現連接失敗,這是他的Bug 再按一次 **Connect to Firebase** 接著點選 **Sync** 就會直接連接了 [![](https://i.imgur.com/NPFrygY.png)](https://i.imgur.com/NPFrygY.png) ### 二: 將FCM套件加入專案 點擊**Add FCM to your app** 之後點擊 **Accept changes** 即可 [![](https://i.imgur.com/LLkxMOr.png)](https://i.imgur.com/LLkxMOr.png) ### 三: 創建FCM所需要的class [![](https://i.imgur.com/PvmBcsV.png)](https://i.imgur.com/PvmBcsV.png) 我們需要創建"**MyFirebaseInstanceIdService**"和"**MyFirebaseMessagingService**"兩個Class 並分別繼承"**FirebaseInstanceIdService**"與"**FirebaseMessagingService**" 我們創立一個service的package 並創建這兩個class [![](https://i.imgur.com/B5n5QgL.png)](https://i.imgur.com/B5n5QgL.png) 在這兩個文件中輸入以下程式碼 #### *MyFirebaseInstanceIdService.kt* ```kotlin=3 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* ```kotlin=3 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* ```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> ``` [![](https://i.imgur.com/YdSQRhW.png) ](https://i.imgur.com/YdSQRhW.png) ### 四: 設定Firebase 若連接Firebase時選的是新建專案,不需要設定Firebase 當選擇的是加入專案時需要至專案裡點擊剛剛加入的Application [![](https://i.imgur.com/oWnVgnH.jpg)](https://i.imgur.com/oWnVgnH.jpg) [![](https://i.imgur.com/0zFksJF.jpg)](https://i.imgur.com/0zFksJF.jpg) 接著直接跳到第四步 [![](https://i.imgur.com/9f631fs.png)](https://i.imgur.com/9f631fs.png) 現在執行你的app [![](https://i.imgur.com/siMMwDz.png)](https://i.imgur.com/siMMwDz.png) 稍等一下網頁上出現成功就代表設定成功了 ## 3. 嘗試使用Firebase推播 到Firebase網頁 從左邊欄位選擇 **拓展** -> **Cloud Messaging** 點擊傳送第一則訊息 [![](https://i.imgur.com/DRsp7LB.png) ](https://i.imgur.com/DRsp7LB.png) 輸入訊息文字並選擇目標為Android,接著按下傳送訊息就可以了 (重要!! 在按下傳送前必須確保App是在背景運行,若App在前景運行,則不會顯示通知,並且請在實體手機上測試) [![](https://i.imgur.com/e9xdwww.png)](https://i.imgur.com/e9xdwww.png) 成功!! [![](https://i.imgur.com/Nscrvxb.jpg)](https://i.imgur.com/Nscrvxb.jpg) ## 4. 設定Token的接收,設定所屬Topic **MyFirebaseInstanceIdService** 內的 **onTokenRefresh()** 函數會在Token生成時被呼叫 在這個函數底下我們先新增兩行程式碼 ```kotlin= override fun onTokenRefresh() { // Get updated InstanceID token ... ... FirebaseMessaging.getInstance().subscribeToTopic("all"); FirebaseMessaging.getInstance().subscribeToTopic("android"); } ``` 這兩行程式碼在onTokenRefresh()時會向Firebase註冊兩個Topic用來對群體發送推播 若想要對單一裝置做推播則需要取得裝置的Token ```kotlin= override fun onTokenRefresh() { // Get updated InstanceID token ... ... ... OKHttpService().sendRegistrationToServer(refreshedToken);///這個函數要自己實現 } ``` 我們可以建立一個http連線將我們的Token回傳給我們的server 我們可以利用OKHttp套件來進行http請求 ## 5. OKHttp 這邊會教你使用OKHttp對server進行post請求 ### 一. 安裝套件 在**build.gradle(Project)** 裡加入版本訊息 ```groovy= buildscript { ... ext.anko_version='0.10.6' ... ... } ``` 在**buile.gradle(Module)** 裡加入這兩個套件的訊息 ```groovy= dependencies { ... ... ////3rd part packages---------- implementation "org.jetbrains.anko:anko:$anko_version" implementation 'com.squareup.okhttp3:okhttp:3.11.0' } ``` 接著按下**sync**讓專案下載所需的套件 [![](https://i.imgur.com/uJn3kAc.jpg)](https://i.imgur.com/uJn3kAc.jpg) ### 二. 建立Post的方法 在service資料夾下創建名為**OKHttpService**的Class,鍵入下面的程式碼 ```kotlin=3 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即可~ ```kotlin=11 class OKHttpService { ... ... ... fun sendRegistrationToServer(refreshedToken : String?){ val param = "{\"token\":\"$refreshedToken\"}" doAsync { Log.d("ResponseOK",post("http://10.88.0.216:8888", param)) } } } ``` ### 四. 設定網路權限 #### *AndroidManifest.xml* ```xml=4 <uses-permission android:name="android.permission.INTERNET"/> ``` [![](https://i.imgur.com/ghW9Frm.png)](https://i.imgur.com/ghW9Frm.png) 這樣就完成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()** 發送通知 ```kotlin= if (remoteMessage.data.isNotEmpty()) { Log.d("GETMESSAGE", "Message data payload: " + remoteMessage.data) sendNotification(remoteMessage) } ``` 接著我們必須要實作**sendNotification()** ### 一.建立channel Android 8 以上的手機需要建立Channel才能成功發送通知 ```kotlin= 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) } } } ``` ### 二.建立推播物件,並設定發送 ```kotlin= 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()** 外面 ```kotlin= 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* ```xml=5 <uses-permission android:name="android.permission.WAKE_LOCK" /> ``` 加入控制程式碼 ```kotlin= 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: ```json= { "to" : "/topics/android", or "to" : "Token", "data" : { "message" : "First Notification", "title": "My Title" } } ``` ### ==APIKEY==的取得: 點擊專案裡的專案設定 [![](https://i.imgur.com/CtrEhHW.jpg)](https://i.imgur.com/CtrEhHW.jpg) 伺服器金鑰即為==APIKEY== [![](https://i.imgur.com/a1yhAd1.jpg)](https://i.imgur.com/a1yhAd1.jpg) ## 8. 完成 完成拉 讚! ## 9. 一些Debug秘訣和Android Studio小提示 #### 1. 有時候新建專案或開啟專案時會出現下面的錯誤,這是代表有遺漏的套件 點擊右下角的 "Install Build Tools 27.0.3 and sync project"來安裝即可 (有時需要連續安裝多個套件) [![](https://i.imgur.com/NJSnIHU.png)](https://i.imgur.com/NJSnIHU.png) ## 10.一些有用的連結 [Legacy HTTP Protocol 的官方文件](https://firebase.google.com/docs/cloud-messaging/http-server-ref) ###### tags: `Android`