一、前言
推送是一個很友好的功能,各種app基本都有主動推送消息的功能,小編最近在項目中也使用了主動推送的功能,借用了第三方——友盟。
下面小編就向大家介紹一下友盟相關的推送API.
二、友盟
在介紹api之前,先向大家介紹一下友盟。
友盟是一個第三方的公司,主要提供了App 移動統計、Web 網站統計、Push 消息推送、Share 社會化分享、AppTrack 移動廣告監測、Finplus 金融行業數據解決方案。
本次小編使用了友盟的Push 消息推送。
三、Push 消息推送
基本說明
本文所描述的API接口均基於HTTP REST協議,若無特殊說明接口均使用UTF-8編碼,消息體參數以及返回結果均採用JSON格式。
注意:使用API前需要在Web後臺的App應用信息頁面獲取 Appkey 和 App Master Secret,同時在Web後臺添加服務器IP地址做IP白名單安全驗證或關閉IP白名單。如下圖:
名稱解釋
這裏需要了解一下推送先關的名詞:
-
Appkey:應用唯一標識。友盟消息推送服務提供的appkey和友盟統計分析平臺使用的同一套appkey。
-
App Master Secret:服務器祕鑰,用於服務器端調用API請求時對發送內容做簽名驗證。
-
Device Token:友盟消息推送服務對設備的唯一標識。Android的device_token是44位字符串,iOS的device_token是64位。
-
Alias:開發者自有賬號,開發者可以在SDK中調用setAlias(alias, alias_type)接口將alias+alias_type與device_token做綁定,之後開發者就可以根據自有業務邏輯篩選出alias進行消息推送。
-
通知-Android(notification):消息送達到用戶設備後,由友盟SDK接管處理並在通知欄上1顯示通知內容。
-
消息-Android(message):消息送達到用戶設備後,消息內容透傳給應用自身進行解析處理。
-
通知-iOS:和APNs定義一致。
-
靜默推送-iOS:和APNs定義一致。
-
測試模式:在廣播、組播等大規模發送消息的情況下,爲了防止開發者誤將測試消息大面積發給線上用戶,特增加了測試模式。 測試模式下,只會將消息發送給測試設備。測試設備需要到Web後臺上手工添加。
-
測試模式-Android:Android的測試設備是正式設備的一個子集
-
測試模式-iOS: iOS的測試模式對應APNs的開發環境(sandbox), 正式模式對應APNs的生產環境(prod),測試設備和正式設備完全隔離。
-
簽名: 爲了保證調用API的請求是合法者發送且參數沒有被篡改,需要在調用API時對發送的所有內容進行簽名。簽名附加在調用地址後面,簽名的計算方式參見附錄K。
-
推送類型:單播(unicast)、列播(listcast)、自定義播(customizedcast且不帶file_id)統稱爲單播類消息,Web後臺不會展示此類消息詳細信息,僅展示前一天的彙總數據;廣播(broadcast)、文件播(filecast)、組播(groupcast)、自定義播(customizedcast且file_id不爲空)統稱爲任務類消息,任務類消息支持API查詢、撤銷操作,Web後臺會展示任務類消息詳細信息。
-
單播(unicast):向指定的設備發送消息。
-
列播(listcast):向指定的一批設備發送消息。
-
廣播(broadcast):向安裝該App的所有設備發送消息。
-
組播(groupcast)::向滿足特定條件的設備集合發送消息,例如: “特定版本”、”特定地域”等。
-
文件播(filecast):開發者將批量的device_token或者alias存放到文件,通過文件ID進行消息發送。
-
自定義播(customizedcast):開發者通過自有的alias進行推送,可以針對單個或者一批alias進行推送,也可以將alias存放到文件進行發送。
發送限制
爲了提供更加穩定高效的推送環境,生產環境下有以下發送限制(測試環境無任何限制):
-
廣播(broadcast)默認每天可推送10次
-
組播(groupcast)默認每分鐘可推送5次
-
文件播(filecast)默認每小時可推送300次
-
自定義播(customizedcast, 且file_id不爲空)默認每小時可推送300次
-
單播類消息暫無推送限制
入參
android和ios的入參是不一樣的:
Android:
{
"appkey":"xx", // 必填,應用唯一標識
"timestamp":"xx", // 必填,時間戳,10位或者13位均可,時間戳有效期爲10分鐘
"type":"xx", // 必填,消息發送類型,其值可以爲:
// unicast-單播
// listcast-列播,要求不超過500個device_token
// filecast-文件播,多個device_token可通過文件形式批量發送
// broadcast-廣播
// groupcast-組播,按照filter篩選用戶羣, 請參照filter參數
// customizedcast,通過alias進行推送,包括以下兩種case:
// - alias: 對單個或者多個alias進行推送
// - file_id: 將alias存放到文件後,根據file_id來推送
"device_tokens":"xx", // 當type=unicast時, 必填, 表示指定的單個設備
// 當type=listcast時, 必填, 要求不超過500個, 以英文逗號分隔
"alias_type": "xx", // 當type=customizedcast時, 必填
// alias的類型, alias_type可由開發者自定義, 開發者在SDK中
// 調用setAlias(alias, alias_type)時所設置的alias_type
"alias":"xx", // 當type=customizedcast時, 選填(此參數和file_id二選一)
// 開發者填寫自己的alias, 要求不超過500個alias, 多個alias以英文逗號間隔
// 在SDK中調用setAlias(alias, alias_type)時所設置的alias
"file_id":"xx", // 當type=filecast時,必填,file內容爲多條device_token,以回車符分割
// 當type=customizedcast時,選填(此參數和alias二選一)
// file內容爲多條alias,以回車符分隔。注意同一個文件內的alias所對應
// 的alias_type必須和接口參數alias_type一致。
// 使用文件播需要先調用文件上傳接口獲取file_id,參照"文件上傳"
"filter":{}, // 當type=groupcast時,必填,用戶篩選條件,如用戶標籤、渠道等,參考附錄G。
"payload": { // 必填,JSON格式,具體消息內容(Android最大爲1840B)
"display_type":"xx", // 必填,消息類型: notification(通知)、message(消息)
"body": { // 必填,消息體。
// 當display_type=message時,body的內容只需填寫custom字段。
// 當display_type=notification時,body包含如下參數:
// 通知展現內容:
"ticker":"xx", // 必填,通知欄提示文字
"title":"xx", // 必填,通知標題
"text":"xx", // 必填,通知文字描述
// 自定義通知圖標:
"icon":"xx", // 可選,狀態欄圖標ID,R.drawable.[smallIcon],
// 如果沒有,默認使用應用圖標。
// 圖片要求爲24*24dp的圖標,或24*24px放在drawable-mdpi下。
// 注意四周各留1個dp的空白像素
"largeIcon":"xx", // 可選,通知欄拉開後左側圖標ID,R.drawable.[largeIcon],
// 圖片要求爲64*64dp的圖標,
// 可設計一張64*64px放在drawable-mdpi下,
// 注意圖片四周留空,不至於顯示太擁擠
"img": "xx", // 可選,通知欄大圖標的URL鏈接。該字段的優先級大於largeIcon。
// 該字段要求以http或者https開頭。
// 自定義通知聲音:
"sound": "xx", // 可選,通知聲音,R.raw.[sound]。
// 如果該字段爲空,採用SDK默認的聲音,即res/raw/下的
// umeng_push_notification_default_sound聲音文件。如果
// SDK默認聲音文件不存在,則使用系統默認Notification提示音。
// 自定義通知樣式:
"builder_id": xx, // 可選,默認爲0,用於標識該通知採用的樣式。使用該參數時,
// 開發者必須在SDK裏面實現自定義通知欄樣式。
// 通知到達設備後的提醒方式,注意,"true/false"爲字符串
"play_vibrate":"true/false", // 可選,收到通知是否震動,默認爲"true"
"play_lights":"true/false", // 可選,收到通知是否閃燈,默認爲"true"
"play_sound":"true/false", // 可選,收到通知是否發出聲音,默認爲"true"
// 點擊"通知"的後續行爲,默認爲打開app。
"after_open": "xx", // 可選,默認爲"go_app",值可以爲:
// "go_app": 打開應用
// "go_url": 跳轉到URL
// "go_activity": 打開特定的activity
// "go_custom": 用戶自定義內容。
"url": "xx", // 當after_open=go_url時,必填。
// 通知欄點擊後跳轉的URL,要求以http或者https開頭
"activity":"xx", // 當after_open=go_activity時,必填。
// 通知欄點擊後打開的Activity
"custom":"xx"/{} // 當display_type=message時, 必填
// 當display_type=notification且
// after_open=go_custom時,必填
// 用戶自定義內容,可以爲字符串或者JSON格式。
},
extra:{ // 可選,JSON格式,用戶自定義key-value。只對"通知"
// (display_type=notification)生效。
// 可以配合通知到達後,打開App/URL/Activity使用。
"key1": "value1",
"key2": "value2",
...
}
},
"policy":{ // 可選,發送策略
"start_time":"xx", // 可選,定時發送時,若不填寫表示立即發送。
// 定時發送時間不能小於當前時間
// 格式: "yyyy-MM-dd HH:mm:ss"。
// 注意,start_time只對任務類消息生效。
"expire_time":"xx", // 可選,消息過期時間,其值不可小於發送時間或者
// start_time(如果填寫了的話),
// 如果不填寫此參數,默認爲3天后過期。格式同start_time
"max_send_num": xx, // 可選,發送限速,每秒發送的最大條數。最小值1000
// 開發者發送的消息如果有請求自己服務器的資源,可以考慮此參數。
"out_biz_no": "xx" // 可選,開發者對消息的唯一標識,服務器會根據這個標識避免重複發送。
// 有些情況下(例如網絡異常)開發者可能會重複調用API導致
// 消息多次下發到客戶端。如果需要處理這種情況,可以考慮此參數。
// 注意, out_biz_no只對任務類消息生效。
},
"production_mode":"true/false", // 可選,正式/測試模式。默認爲true
// 測試模式只會將消息發給測試設備。測試設備需要到web上添加。
// Android: 測試設備屬於正式設備的一個子集。
"description": "xx", // 可選,發送消息描述,建議填寫。
//系統彈窗,只有display_type=notification生效
"mipush": "true/false", // 可選,默認爲false。當爲true時,表示MIUI、EMUI、Flyme系統設備離線轉爲系統下發
"mi_activity": "xx", // 可選,mipush值爲true時生效,表示走系統通道時打開指定頁面acitivity的完整包路徑。
}
ios:
{
"appkey":"xx", // 必填,應用唯一標識
"timestamp":"xx", // 必填,時間戳,10位或者13位均可,時間戳有效期爲10分鐘
"type":"xx", // 必填,消息發送類型,其值可以爲:
// unicast-單播
// listcast-列播,要求不超過500個device_token
// filecast-文件播,多個device_token可通過文件形式批量發送
// broadcast-廣播
// groupcast-組播,按照filter篩選用戶羣, 請參照filter參數
// customizedcast,通過alias進行推送,包括以下兩種case:
// - alias: 對單個或者多個alias進行推送
// - file_id: 將alias存放到文件後,根據file_id來推送
"device_tokens":"xx", // 當type=unicast時, 必填, 表示指定的單個設備
// 當type=listcast時, 必填, 要求不超過500個, 以英文逗號分隔
"alias_type": "xx", // 當type=customizedcast時, 必填
// alias的類型, alias_type可由開發者自定義, 開發者在SDK中
// 調用setAlias(alias, alias_type)時所設置的alias_type
"alias":"xx", // 當type=customizedcast時, 選填(此參數和file_id二選一)
// 開發者填寫自己的alias, 要求不超過500個alias, 多個alias以英文逗號間隔
// 在SDK中調用setAlias(alias, alias_type)時所設置的alias
"file_id":"xx", // 當type=filecast時,必填,file內容爲多條device_token,以回車符分割
// 當type=customizedcast時,選填(此參數和alias二選一)
// file內容爲多條alias,以回車符分隔。注意同一個文件內的alias所對應
// 的alias_type必須和接口參數alias_type一致。
// 使用文件播需要先調用文件上傳接口獲取file_id,參照"2.4文件上傳接口"
"filter":{}, // 當type=groupcast時,必填,用戶篩選條件,如用戶標籤、渠道等,參考附錄G。
"payload": // 必填,JSON格式,具體消息內容(iOS最大爲2012B)
{
"aps": // 必填,嚴格按照APNs定義來填寫
{
"alert":""/{ // 當content-available=1時(靜默推送),可選; 否則必填。
// 可爲JSON類型和字符串類型
"title":"title",
"subtitle":"subtitle",
"body":"body"
}
"badge": xx, // 可選
"sound": "xx", // 可選
"content-available":1 // 可選,代表靜默推送
"category": "xx", // 可選,注意: ios8才支持該字段。
},
"key1":"value1", // 可選,用戶自定義內容, "d","p"爲友盟保留字段,
// key不可以是"d","p"
"key2":"value2",
...
},
"policy": // 可選,發送策略
{
"start_time":"xx", // 可選,定時發送時間,若不填寫表示立即發送。
// 定時發送時間不能小於當前時間
// 格式: "yyyy-MM-dd HH:mm:ss"。
// 注意,start_time只對任務生效。
"expire_time":"xx", // 可選,消息過期時間,其值不可小於發送時間或者
// start_time(如果填寫了的話),
// 如果不填寫此參數,默認爲3天后過期。格式同start_time
"out_biz_no": "xx" // 可選,開發者對消息的唯一標識,服務器會根據這個標識避免重複發送。
// 有些情況下(例如網絡異常)開發者可能會重複調用API導致
// 消息多次下發到客戶端。如果需要處理這種情況,可以考慮此參數。
// 注意,out_biz_no只對任務生效。
"apns_collapse_id": "xx" // 可選,多條帶有相同apns_collapse_id的消息,iOS設備僅展示
// 最新的一條,字段長度不得超過64bytes
},
"production_mode":"true/false" // 可選,正式/測試模式。默認爲true
// 測試模式只會將消息發給測試設備。測試設備需要到web上添加。
"description": "xx" // 可選,發送消息描述,建議填寫。
}
出參
{
"ret":"SUCCESS/FAIL",
"data": {
// 當"ret"爲"SUCCESS"時,包含如下參數:
// 單播類消息(type爲unicast、listcast、customizedcast且不帶file_id)返回:
"msg_id":"xx"
// 任務類消息(type爲broadcast、groupcast、filecast、customizedcast且file_id不爲空)返回:
"task_id":"xx"
// 當"ret"爲"FAIL"時,包含如下參數:
"error_code":"xx", // 錯誤碼,詳見附錄I
"error_msg":"xx" // 錯誤信息
}
}
單播Ucast推送
pushUnicastByUserInfo:
/**
* 根據用戶信息推送單播類消息-王雷-2018年12月21日14:34:523
*
* @param unicastPushNotificationBO 單播推送BO
* @return 推送結果
*/
@Override
public BusiSystemResponse<UnicastPushNotificationBO> pushUnicastByUserInfo(
UnicastPushNotificationBO unicastPushNotificationBO) {
BusiSystemResponse<UnicastPushNotificationBO> ret = new BusiSystemResponse<>();
if (unicastPushNotificationBO == null) {
DushuLogger.info("參數校驗失敗,接收到空對象unicastPushNotificationBO[null]");
ret.setStatus(CommonResponseCodeEnum.PARAM_ERROR.getCode());
ret.setData(null);
return ret;
}
if (StringUtils.isBlank(unicastPushNotificationBO.getUserId())
|| StringUtils.isBlank(unicastPushNotificationBO.getTicker())
|| StringUtils.isBlank(unicastPushNotificationBO.getTitle())
|| StringUtils.isBlank(unicastPushNotificationBO.getText())) {
DushuLogger.info("參數校驗失敗,unicastPushNotificationBO[{}]", unicastPushNotificationBO.toString());
ret.setStatus(CommonResponseCodeEnum.REQUIRED_PARAMETER_MISSING.getCode());
ret.setData(null);
return ret;
}
List<String> userIdList = new ArrayList<>();
userIdList.add(unicastPushNotificationBO.getUserId());
try {
// 根據userid獲取devicetoken
String listDeviceToken = this.listDeviceToken(userIdList);
if (StringUtils.isBlank(listDeviceToken)) {
ret.setStatus(CommonResponseCodeEnum.DATA_NOT_EXIST.getCode());
ret.setData(null);
return ret;
}
unicastPushNotificationBO.setDeviceToken(listDeviceToken);
} catch (Exception e) {
DushuLogger.error("根據用戶id獲取token信息失敗", e);
ret.setStatus(CommonResponseCodeEnum.ERROR.getCode());
ret.setData(null);
return ret;
}
try {
// 根據設備token推送單播類消息
String deviceToken = unicastPushNotificationBO.getDeviceToken();
String ticker = unicastPushNotificationBO.getTicker();
String title = unicastPushNotificationBO.getTitle();
String text = unicastPushNotificationBO.getText();
String deviceType = StringUtil.getDeviceTypeNameByDeviceToken(deviceToken);
Map<String, String> map = unicastPushNotificationBO.getJumpMap();
if (StringUtils.equalsIgnoreCase(PushConstant.ANDROID_DEVICE_TYPE, deviceType)) {
RpcManager.getDushuThreadPoolExecutor().execute(new ThreadTask("sendAndroidUnicastPushMessageAsync") {
@Override
public void execute() {
pushUnicastAndroid(unicastPushNotificationBO);
}
});
}
if (StringUtils.equalsIgnoreCase(PushConstant.IOS_DEVICE_TYPE, deviceType)) {
RpcManager.getDushuThreadPoolExecutor().execute(new ThreadTask("sendIOSUnicastPushMessageAsync") {
@Override
public void execute() {
pushUnicastIos(unicastPushNotificationBO);
}
});
}
ret.setStatus(CommonResponseCodeEnum.SUCCESS.getCode());
} catch (Exception e) {
DushuLogger.error("根據用戶信息推送單播類消息失敗", e);
ret.setStatus(CommonResponseCodeEnum.ERROR.getCode());
ret.setData(null);
return ret;
}
return ret;
}
pushUnicastAndroid:
/**
* 推送安卓單播消息 - 王雷 -2019-3-20 09:29:43
* @param unicastPushNotificationBO
*/
private void pushUnicastAndroid(UnicastPushNotificationBO unicastPushNotificationBO) {
String deviceToken = unicastPushNotificationBO.getDeviceToken();
String ticker = unicastPushNotificationBO.getTicker();
String title = unicastPushNotificationBO.getTitle();
String text = unicastPushNotificationBO.getText();
Map<String, String> map = unicastPushNotificationBO.getJumpMap();
try {
boolean productionMode = unicastPushNotificationBO.isProductionMode();
AndroidUnicast unicast = new AndroidUnicast(PushConstant.ANDROID_APP_KEY,
PushConstant.ANDROID_APP_MASTER_SECRET);
unicast.setDeviceToken(deviceToken);
unicast.setTicker(ticker);
unicast.setTitle(title);
unicast.setText(text);
if (!CollectionUtil.isNullOrEmpty(map)) {
unicast.setExtraField("extra", JsonUtil.toJSON(map));
unicast.goCustomAfterOpen(JsonUtil.toJSON(map));
} else {
unicast.goAppAfterOpen();
}
unicast.setMiPush(true);
unicast.setDisplayType(AndroidNotification.DisplayType.NOTIFICATION);
if (productionMode) {
unicast.setProductionMode();
} else {
unicast.setTestMode();
}
String postUrl = SignUtils.mergeSignUrl(PushConstant.SEND,
SignUtils.sign(unicast, PushConstant.SEND));
sendUnicastPush(postUrl, unicast.getPostBody());
} catch (Exception e) {
DushuLogger.info("推送安卓單播消息異常!unicast:{}", unicastPushNotificationBO.toString());
}
}
pushUnicastIos:
/**
* 推送IOS單播消息 - 王雷 -2019年3月20日09:30:00
* @param unicastPushNotificationBO
*/
private void pushUnicastIos(UnicastPushNotificationBO unicastPushNotificationBO) {
String deviceToken = unicastPushNotificationBO.getDeviceToken();
String ticker = unicastPushNotificationBO.getTicker();
String title = unicastPushNotificationBO.getTitle();
String text = unicastPushNotificationBO.getText();
Map<String, String> map = unicastPushNotificationBO.getJumpMap();
try {
boolean productionMode = unicastPushNotificationBO.isProductionMode();
IOSUnicast unicast = new IOSUnicast(PushConstant.IOS_APP_KEY,
PushConstant.IOS_APP_MASTER_SECRET);
unicast.setDeviceToken(deviceToken);
Map<String, String> mapAlert = new HashMap<>();
mapAlert.put("title", title);
// mapAlert.put("subtitle", ticker);
mapAlert.put("body", text);
unicast.setAlert(JsonUtil.toJSON(mapAlert));
unicast.setBadge(0);
unicast.setSound("default");
if (!CollectionUtil.isNullOrEmpty(map)) {
for (String key : map.keySet()) {
unicast.setCustomizedField(key, map.get(key));
}
}
if (productionMode) {
unicast.setProductionMode();
} else {
unicast.setTestMode();
}
String postUrl = SignUtils.mergeSignUrl(PushConstant.SEND,
SignUtils.sign(unicast, PushConstant.SEND));
sendUnicastPush(postUrl, unicast.getPostBody());
} catch (Exception e) {
DushuLogger.error("推送IOS單播消息異常!unicast:{}", unicastPushNotificationBO.toString());
}
}
這裏用到了第三方的API,已經封裝好了,詳情見github地址:
具體的例子就列一個吧,其他的也大同小異。
四、小結
使用友盟,感覺推送的成功率也是分手機的,有的手機推送的成功率比較高,比如華爲,有點久稍微差一點,比如vivo。還有就是要支持推送消息,點擊後,跳轉網頁,可以使用自定義的模式。