# 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`