推送原理解析 極光推送使用詳解
推送技術產生場景:
--服務器端主動性: 客戶端與服務器交互都是客戶端主動的, 服務器一般不能主動與客戶端進行數據交互, 因為服務器端無法得知客戶端的 IP 地址 及 狀態;
--數據實時性: 如果服務器端有緊急數據要傳遞給客戶端, 就必須主動向客戶端發送數據;
--基本原理: 使客戶端實時獲取服務器端消息, Pull 方式, 小周期輪詢, 費電費流量; 另一個就是 Push 方式, 服務器端向客戶端主動推送數據, 可以省電省流量;
一. 推送原理
1. Android 推送原理簡介
(1) SMS 方式推送
SMS 推送:
--SMS: Short Message Service 縮寫, 即短信服務;
--實現方式: 服務器端向手機端發送短信, 手機監聽短信廣播, 然后將攔截的短信信息進行顯示;
--優點: 省電, 省流量, 在沒有網絡的偏遠地點也能接收到推送消息;
--缺點: 費錢, 一毛錢一條;
(2) 輪詢 方式推送
輪詢推送:
--實現方式: 周期性主動獲取網絡中的數據;
--缺點: 費電, 費流量;
(3) 長鏈接 方式推送
長鏈接推送: 主流方法;
--實現方式: 手機端與服務器端建立一條長時間的數據流鏈接, 手機客戶端一直等待服務器端的數據;
--優點: 有一條長鏈接, 有數據的時候才發送數據, 沒有時不消耗流量, 比較省流量;
--缺點: 由於要保存一條長鏈接, 比較費電; 在網絡不穩定的情況下, 推送容易失敗;
2. Android 推送解決方案簡介
(1) C2DM 推送 (Google)
C2DM 推送簡介: 全稱 Cloudto Device Messaging, Google 提供的 推送解決方案;
--運行方式: 提供一個輕量級機制, 允許服務器通知應用程序, 主動與客戶端進行數據交互, 處理消息排隊, 並向運行於目標設備的應用程序分發消息;
--優點: Google 提供的原生框架, 無需在應用中添加第三方代碼 和 部署服務器端;
--缺點: 1.該推送依賴 Google 服務器, 需要綁定 Google 帳號, 目前在中國 Google 被屏蔽, 無法使用; 2. 許多手機廠商去掉了軟件中的該模塊;
--運行機制圖:

(2) MQTT 推送 (IBM)
MQTT 推送簡介: MQTT 是輕量級的消息發布"訂閱協議,
--優點: 省電, 省流量, 輕量級, 有 C++ 版的服務器端組件 RSMB;
--缺點: RSMB 不開源, 部署成本高, 比較復雜;
--IBM相關MQTT官網: http://www-01.ibm.com/support/docview.wss?rs=171&uid=swg24006006 ;
RSMB 服務器端: 全稱 Really Small Message Broker, IBM 提供;
--工作方式: 在服務器端, 接收消息, 並將 消息 傳輸給指定移動設備;
--地址: http://www.alphaworks.ibm.com/tech/rsmb ;
MQTT 推送示例:
--客戶端: https://github.com/tokudu/AndroidPushNotificationsDemo ;
--服務器端 PHP: https://github.com/tokudu/PhpMQTTClient ;
(3) 基於 XMPP 的 AndroidPN 推送 (開源)
XMPP 推送簡介:
--XMPP 簡介: 全稱 Extensible Messaging and Presence Protocol (可擴展通訊和表示協議), 基於可擴展標記語言(XML)的協議,它用於即時消息(IM)以及在線探測, 該協議允許因特網用戶向因特網上的其他任何人發送即時消息;
--AndroidPN: 基於 XMPP 協議開發的 Java 開源 Android 推送通知實現, 包含了完整的客戶端 和 服務器端;
--項目主頁: http://sourceforge.net/projects/androidpn/ ;
--原理圖:

AndroidPN 缺陷: 如果使用該框架進行推送, 需要在了解 XMPP 和 anroidPN 基礎上進行大量的二次開發;
--連接中斷: 連接時間過長, 連接會中斷, 收不到消息;
--穩定性差: 該框架不是很穩定;
--糾錯機制: 消息推送出去后, 不檢查是否推送到客戶端, 推送出去就不管了;
3. 推送 4s 評價標准
4S 標准:Safe(安全),Stable(穩定),Save(省電省流量省成本),Slim(體積小);
(1) Safe (安全)
推送安全標准:
--透明傳輸: 只負責點對點的傳輸的質量, 不關心中間的傳輸介質 與 傳輸業務邏輯 協議等;
--加密方案: 信息需要加密, 另外推送的 ID 系統需要獨立與后台已有的 ID 系統;
(2) Stable (穩定)
服務器穩定: 長鏈接方案對服務器開銷要求很高, 服務器端開發難度很大;
--在線峰值: 同時在線連接數到達100萬的穩定性;
--並發時延: 高並發時的消息平均延遲, 一分鍾處理 100萬 條數據;
--服務器穩定: 穩定性時延占總時間的 99.9%, 有備份和負載均衡的機制;
客戶端穩定: 中國網絡狀況復雜, 手機長時間聯網比較難, 穩定性比較難, 開發時要考慮每個省的每個運營商, 每款手機的機型;
--聯網時間: 每日聯網時間 23.5 小時以上;
--消息到達率: 消息收到后 9 小時內客戶端的消息到達率;
(3) Save (節省)
節省評判:
--電量節省: 注意 CPU 休眠率, 服務短待機時間百分比評判;
--流量節省: 處理協議 和 冗余數據包, 使用空載待機月流量評判;
--成本節省: 單服務器同時承載連接數, 同時承載連接數越多, 成本越低, 個推單服務器連接 300 萬(業內頂尖水平);
(4) Slim (體積小)
集成 SDK 大小: 客戶端推送的 SDK 的大小盡量小, 一般要小於 300K;
二. 極光推送概述
1. 功能概述
極光推送基本功能:主動即時的向用戶發起交互, 可以發送聊天消息;
--作用: 通過向精准的目標用戶推送有價值的消息, 可以提供用戶忠誠度, 提高留存率;
(1) 推送方式
推送方式簡介:
--通知: 推送文本內容, 展示在通知欄中;
--自定義消息: 推送自定義消息, 給用戶自行處理;
--富媒體: 推送 HTML 頁面內容;
(2) 推送目標
推送目標簡介:
--廣播推送: 向所有用戶發送廣播消息;
--標簽推送: 根據用戶設置的標簽分組, 向某一組推送消息;
--別名推送: 客戶端綁定用戶別名, 向單個用戶推送信息;
(3) 用戶分群
用戶分群簡介: 可以根據 JPush 提供的多條件組合, 對用戶進行群組划分, 實現實時篩選推送;
(4) 推送歷史
推送歷史簡介: 通過 WEB 或者 API 發出的推送, 都可以在推送歷史記錄中查詢到, 並可以實時顯示推送結果;
2. 推送框架
推送框架:
--推送數據源: 自己開發的服務器端 或者 使用 極光推送官網的 WEB 后台;
--JPush API: 部署在服務器端, 開發者的服務器端發起推送時, 將數據傳到 JPush API 中, 然后再向下傳遞;
--建立長鏈接: 集成 JPush 的 SDK 客戶端啟動后會建立一個到 JPush Cloud 的長鏈接, 提供 App 永遠在線的能力;
--原理圖:

3. 極光推送原理
參考文章(極光推送官方博客): http://blog.jpush.cn/jpush_wireless_push_principle/ ;
(1) 客戶端原理
IP地址 分配原理:
--IP 地址有限: IPv4 的 IP 地址數量有限, 運營商要動態地為 手機分配 IP 地址, 這些 IP 地址都是運營商的內網 IP;
--網絡地址轉換 (NAT): 全稱 Network Address Translation, 網關維護一個外網 IP 地址, 與 內網 IP 地址對應;
--外網 IP 不固定: 由於運營商持有的外網 IP 數量有限, 需要動態的分配給接入運營商的用戶, 因此在手機一段時間沒有數據傳輸時會將該手機分配的外網 IP 地址收回, 分配給其它用戶;
--解決方案: Android 手機端想要保持長鏈接, 首先外網 IP 地址不能變, 不能讓運營商收回 這個 IP 地址;
Android 手機端實現方案:
--心跳: 為了長時間保持外網 IP, 需要客戶端定期發送心跳給運營商, 以便刷新 NAT 列表;
--Timer 定時方法: 該類計划循環執行定時任務, 但是使用該類會使 CPU 保持喚醒狀態, 比較費電;
--AlarmManager 定時方法: 該類封裝了 Android 手機的 RTC 硬件時鍾模塊, 可以在 CPU 休眠時正常運行, 定時任務執行時再喚醒 CPU, 這樣做到了電量節省;
(2) 服務器原理
C10K 問題: 單台服務器解決 同時保持一萬長鏈接的性能問題;
4. Android SDK 簡介
Android SDK 本質: JPush SDK 集成到 Android APP 中后,作為一個 Service 在 Android 端長期運行, 始終與 服務器端 保持者長鏈接, 相當與永遠在線;
(1) 多平台支持
多平台支持:
--手機芯片類型: 一般的手機是 ARM 芯片, 但是有些手機是 MIPS 芯片 或者 x86 芯片;
--so 庫支持: 每個 CPU 芯片類型對應的 so 庫, 都需要特殊編譯, 無法跨平台調用, 如 ARM 平台的 so 庫在 x86 平台就無法運行;
(2) 電量與流量說明
流量消耗: JPush 的協議是自定義的, 協議的數據量經過了精簡, 流量消耗非常少;
電量消耗: JPush 心跳保持連接時可以在 CPU 不喚醒的情況下執行, 減少了不必要的代碼運行, 電量非常節省;
(3) 相關庫說明
JPush 依賴庫:
--.so 依賴庫內容: JPush 需要一個 so 動態庫, 該庫是 C 語言編寫, 在 Linux 下進行交叉編譯而來, 可以在 ARM 芯片的手機上運行;
--jar 依賴庫內容: 對 Java 代碼的封裝;
三. 極光推送簡單 Demo
Demo 概述: JPush 官方提供了一個簡單 Demo, 向我們演示了 JPush 基本的推送流程, 基本步驟分為下面幾塊, 即
--Web 配置操作部分: 包括 注冊開發者帳號, 創建應用;
--下載手機示例: 下載系統自動生成的 Android 應用示例;
--發送推送指令: 在 Web 端發送推送指令, 手機端接收該指令;
--官方地址: 關於該模塊詳細信息可參考官方文檔, http://docs.jpush.io/guideline/android_3m/ ;
1. Web 配置操作
(1) 帳號創建
該步驟就不再贅述, 普通的帳號注冊即可;
(2) 創建應用
創建應用步驟:
--創建應用界面: https://www.jpush.cn/common/apps ;
--創建應用: 點擊 創建應用 按鈕即可;

--配置應用信息: 輸入應用名稱, 上傳一個圖標, 然后填寫一個包名, JPush 系統會在后台根據你輸入的包名生成的推送的 Android 應用 Demo, 該 Demo 包含了該配置的信息;

查看應用設置界面:

2. 下載導入 Android 應用源碼
(1) 下載 Android 應用源碼
下載源碼: 在應用設置的 Android 模塊, 有 "下載 Android Example" 按鈕, 點擊該按鈕即可下載 Android 應用;

(2) 導入 Android 應用源碼
將下載后的源碼解壓, 不用任何修改, 即可導入到應用中運行;

3. 發送推送
(1) 不初始化推送無效
直接在后台推送: 我們在 Android 手機界面不做任何操作, 直接在網頁后台推送消息;
--發送通知: 在應用的 "推送" 模塊, 點擊發送通知按鈕;

--輸入推送內容: 輸入任意推送內容 "Jpush Demo Send By octopus 1.", 點擊頁面最下方的 "立即發送" 按鈕;


--對話框選擇: Web 界面會彈出對話框, 提示是否發送, 選 "發送吧" 即可;

--查看推送結果: 點擊之后又彈出對話框, 點擊 "去看看" 可以查看推送結果;

--推送結果分析: 點擊上面的 "去看看" 可以查看推送結果, 點擊 "推送歷史" 按鈕, 也可一查看推送結果; 由於我們沒有在 客戶端進行初始化, 因此推送沒有成功;

(2) 初始化后推送
初始化之后推送:
--手機端初始化: 點擊手機端的 "initPush" 按鈕, 進行初始化;

--發送消息: 發送 "Jpush Demo Send By octopus 2." 消息, 此時連上一次推送的消息也送達, 網絡不同會延遲一定時間;

--查看發送結果: 此時顯示的推送, 兩次都成功了;

(3) 停止 和 恢復 推送功能
停止恢復推送功能: 當點擊停止推送 "stopPush" 按鈕時, 推送手機端就不會再接收推送內容, 當點擊恢復推送時, 會將期間積攢的所有推送內容一次性推送到手機中;
四. Android 應用集成 JPush
1. 依賴庫拷貝
(1) SDK 簡介
SDK 下載: 最新的 SDK 壓縮包 Jpush-Android-sdk-1.7.3.zip ;
--JPush SDK 下載頁面: http://docs.jpush.io/resources/ ;
--Android SDK 下載地址: https://www.jpush.cn/downloads/sdk/android/ ;
詳細文件說明: 將 Jpush-Android-sdk-1.7.3.zip 解壓, 解壓后的目錄是 Jpush-Android-sdk;
--查看文檔目錄: 使用 tree -L 3 命令, 查看深度為 3 的路徑結構;
octopus@octopus:~/JPush/Jpush-Android-sdk$ tree -L 3
.
├── AndroidManifest.xml
├── ChangeLog.txt
├── doc
│ └── Jpush-sdk-集成指南.pdf
├── example
│ ├── AndroidManifest.xml
│ ├── libs
│ │ ├── armeabi
│ │ ├── armeabi-v7a
│ │ └── jpush-sdk-release1.7.3.jar
│ ├── proguard-project.txt
│ ├── project.properties
│ ├── res
│ │ ├── drawable-hdpi
│ │ ├── drawable-ldpi
│ │ ├── drawable-mdpi
│ │ ├── drawable-xhdpi
│ │ ├── layout
│ │ ├── values
│ │ └── values-zh
│ └── src
│ └── com
└── libs
├── armeabi
│ └── libjpush173.so
├── armeabi-v7a
│ └── libjpush173.so
└── jpush-sdk-release1.7.3.jar
--AndroidManifest.xml (配置文件): 這是 Android 應用的主要配置文件示例;
--ChangeLog.txt (升級說明): SDK 版本升級的說明;
--doc (文檔): doc 目錄下 有個 "Jpush-sdk-集成指南.pdf" 文檔, 這是集成 JPush 文檔;
--example (代碼示例): example 明顯是個 Android 示例 demo;
--libs (依賴庫): Android 應用中相關庫, 放在這個目錄中, libjpush173.so 是依賴的 C 底層庫, jpush-sdk-release1.7.3.jar 是依賴的 Java 庫;
(2) 拷貝 依賴庫 到 Android 應用中
執行過程:
--創建應用: 創建一個空應用, 注意應用最小版本應大於 2.1, 否則不能支持極光推送, 應用的包名為 cn.org.octopus.jpush.demo 即可, 不用在后台另外創建一個
--拷貝依賴庫: 在工程根目錄下創建一個 libs 目錄, 拷貝jpush-sdk-release1.7.3.jar到 libs 目錄中, 將armeabi/libjpush173.so和armeabi-v7a/libjpush173.so拷貝到 libs 目錄, 注意要連文件夾一起拷貝, 拷貝完后如下圖;

2. 配置 AndroidManifest.xml 文件
(1) 權限配置
JPush 定義權限: JPush 定義了一個權限, 允許應用接收 JPush 代碼發送的廣播消息, You Package.permission.JPUSH_MESSAGE, 注意要使用你的包名替換其中的 Your Package;
用戶權限:
可選用戶權限 :
(2) 配置 JPush 服務
JPush 服務:
--推送服務:
android:name="cn.jpush.android.service.PushService"
android:enabled="true"
android:exported="false" >
--下載服務:
android:name="cn.jpush.android.service.DownloadService"
android:enabled="true"
android:exported="false" >
(3) 配置 JPush 廣播接收者
JPush 廣播接收者配置:
--推送接收者:
android:name="cn.jpush.android.service.PushReceiver"
android:enabled="true" >
-- 時鍾相關接收者 :
(4) 配置 渠道 和 推送標識
渠道 和 AppKey 配置:
(5) 最終的配置文件
配置好的文件:
package="cn.org.octopus.jpush.demo"
android:versionCode="1"
android:versionName="1.0" >
android:minSdkVersion="17"
android:targetSdkVersion="19" />
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme">
android:name=".MainActivity"
android:label="@string/app_name" >
android:name="cn.jpush.android.service.PushService"
android:enabled="true"
android:exported="false" >
android:name="cn.jpush.android.service.DownloadService"
android:enabled="true"
android:exported="false" >
android:name="cn.jpush.android.service.PushReceiver"
android:enabled="true" >
3. 推送測試
后台推送:
--控制台地址: https://www.jpush.cn/common/apps/ ;
--發起推送: 進入控制台, 點擊對應的應用, 進入推送頁面;

--查看結果:

--推送統計:

五. JPush 相關 API
1. 初始化 停止 恢復 推送
推送控制方法:
--初始化推送: JPushInterface.init(Context context), 初始化之后才能接收推送消息;
--恢復推送: JPushInterface.resumePush(Context context), 停止推送后, 調用該方法, 即可恢復推送;
--停止推送: JPushInterface.stopPush(Context context), 調用該方法之后, 手機便收不到推送信息了;
代碼示例 :
public void onClick(View view) {
int id = view.getId();
switch (id) {
case R.id.init_jpush:
//初始化 JPush, 初始化之后才可以進行推送, 否則推送失敗
JPushInterface.init(this);
//設置調試模式, 可以在 LogCat 中查看 JPush 日志
JPushInterface.setDebugMode(true);
break;
case R.id.start_jpush:
//恢復推送
JPushInterface.resumePush(getApplicationContext());
break;
case R.id.stop_jpush:
//停止推送
JPushInterface.stopPush(getApplicationContext());
break;
default:
break;
}
}
界面效果及說明:
--界面效果:

--按鈕說明: 點擊 "初始化" 按鈕就可以接收推送消息, 點擊 "停止推送" 按鈕手機停止接收消息推送, 點擊 "恢復推送" 即開始接收推送消息;
2. 根據 別名 和 標簽 分組推送
參考文檔: http://docs.jpush.cn/pages/viewpage.action?pageId=557241
(1) 概念介紹
別名:
--作用: 別名用於代表安裝了應用的用戶, 每個用戶對應着一個別名;
--用戶與別名對應性 (多對一): 每個用戶只能指定一個別名, 一個別名可以同時指定給多個用戶, 給別名發消息時, 會同時給所有設置該別名的用戶發消息;
標簽:
--作用: 方便開發者根據標簽發送推送消息;
--用戶與標簽對應性(多對多): 一個用戶可以有多個標簽, 一個標簽可以設置給多個用戶;
(2) 設置別名標簽接口方法
設置別名與標簽方法:
--方法接口:
public static void setAliasAndTags(
Context context, //上下文對象
String alias, //別名, 只能設置一個別名
Set tags, //標簽集合, 可設置多個標簽
TagAliasCallback callback) //回調接口, 其中有一個 gotResult 接口方法, 系統回傳入錯誤碼給 responseCode 參數
-- 別名設置說明 : a. 設置 null(沒有地址) 即不設值值; b. 設置 "" (初始化后) 即清空之前的設置; c. 設置會覆蓋之前的設置; d. 長度 40字節 UTF8 編碼;
--標簽設置說明:a. 設置 null(沒有地址) 即不設值值; b. 設置 空集合(沒有數據, 已經初始化) 即清空之前的設置; c. 設置會覆蓋之前的設置; d. 每個標簽長度 40字節 UTF8 編碼, 最多 100 個標簽;
設置別名方法:
--方法接口:
public static void setAlias(
Context context, //上下文對象
String alias, //別名內容
TagAliasCallback callback) //回調接口
-- 參數說明 : 該參數與 setAliasAndTags 方法參數相同;
設置標簽方法:
--方法接口:
public static void setTags(
Context context,
Set tags,
TagAliasCallback callback)
--參數說明:該參數與setAliasAndTags 方法參數相同;
過濾無效標簽: 感覺這純屬 JPush API 定義缺陷, 這個應該對我們隱藏才對;
--方法接口:
public static Set filterValidTags(Set tags)
--作用: 設置標簽時如果 標簽Set集合 中有一個是無效的, 那么整個設置都無效, 應該是設計缺陷, 后期修補 BUG 的權益之際;
回調接口: TagAliasCallback 回調接口;
--方法接口:
public void gotResult(
int responseCode, //錯誤碼
String alias, //別名
Set tags); //標簽集合
代碼示例:
--設置方法代碼:
String alias = set_alias.getText().toString();
String tag = set_tag.getText().toString();
Set set = new HashSet();
set.add(tag);
JPushInterface.setAliasAndTags(
getApplicationContext(),
alias,
set,
this);
Log.i(TAG, "已設置別名 與 標簽");
-- 回調方法 :
@Override
public void gotResult(int arg0, String arg1, Set arg2) {
Log.i(TAG, "錯誤碼 : " + arg0 + " , 別名 : " + arg1);
}
--手機端設置:

-- 后台發送極光推送 :

--設置標簽:

--標簽報錯: 如果設置的標簽沒有手機注冊, 會出現如下報錯;

--推送結果:

--推送通知消息:

3. 根據 RegistrationID 進行單機推送
參考文檔 : http://docs.jpush.cn/pages/viewpage.action?pageId=8814639
(1) 概念介紹
RegistrationID 簡介: 應用第一次注冊到 JPush 后台時, 會生成一個唯一的設備標識 RegistrationID, 每個設備不重復;
(2) 獲取RegistrationID
方法接口:
//SDK 初次注冊成功后,開發者通過在自定義的 Receiver 里監聽 Action - cn.jpush.android.intent.REGISTRATION 來獲取對應的 RegistrationID。注冊成功后,也可以通過此函數獲取
public static String getRegistrationID(Context context)
-- 獲取 RegistrationID 代碼 :
//獲取 RegistrationID
String registrationid = JPushInterface.getRegistrationID(getApplicationContext());
tv_registrationid.setText("RegistrationID : " + registrationid);
--界面示例:

--后台推送設置:

4. 清除通知
清除通知簡介 :
-- 方法接口 :
public static void clearAllNotifications(Context context);
-- 代碼示例 :
case R.id.clear_notification:
//清除所有通知
JPushInterface.clearAllNotifications(getApplicationContext());
break;
-- 效果 : 在后台發送一條消息, 點擊清除按鈕, 所有的推送通知都清除;

5. 推送時間限制
(1) 設置允許推送時間
方法接口:
public static void setPushTime(
Context context, //上下文對象
Set weekDays, //允許接收推送的 一周天數
int startHour, //開始時間
int endHour) //結束時間
-- 代碼示例 :
Set days = new HashSet();
days.add(1);
days.add(2);
days.add(3);
days.add(4);
days.add(5);
JPushInterface.setPushTime(getApplicationContext(), days, 10, 23);
(2) 設置禁止推送時間
方法接口:
public static void setSilenceTime(
Context context, //上下文對象
int startHour, //開始時間-小時
int startMinute, //開始時間-分鍾
int endHour, //結束時間-小時
int endMinute) //結束時間-分鍾
-- 代碼示例 :
JPushInterface.setSilenceTime(getApplicationContext(), 22, 30, 8, 30);