Markdown版本筆記 | 我的GitHub首頁 | 我的博客 | 我的微信 | 我的郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
目錄
JPush產品簡介
消息形式
推送目標
集成步驟
FAQ
簡潔版自定義消息推送Demo
Activity
Receiver
消息處理類
Bean
配置文件
AndroidManifest.xml
JPush產品簡介
極光推送是一個端到端的推送服務,使得服務器端消息能夠及時地推送到終端用戶手機上,讓開發者積極地保持與用戶的連接,從而提高用戶活躍度、提高應用的留存率。
主要功能:
- 保持與服務器的長連接,以便消息能夠即時推送到達客戶端
- 接收通知與自定義消息,並向開發者App傳遞相關信息
主要特點:
- 客戶端維持連接占用資源少、耗電低
- SDK豐富的接口,可定制通知欄提示樣式
- 服務器大容量、穩定
原理:JPush Android SDK 是作為 Android Service 長期運行在后台的,從而創建並保持長連接,保持永遠在線的能力。
JPush是經過考驗的大規模APP推送平台,每天推送消息數超過5億條。 開發者集成SDK后,可以通過調用API推送消息。同時,JPush提供可視化的web端控制台發送通知,統計分析推送效果。 JPush全面支持 Android, iOS, Winphone 三大手機平台。
消息形式
JPush提供四種消息形式:通知,自定義消息,富媒體和本地通知。
1、通知
或者說 Push Notification,即指在手機的通知欄(狀態欄)上會顯示的一條通知信息。 通知主要用於提示用戶的目的,應用於新聞內容、促銷活動、產品信息、版本更新提醒、訂單狀態提醒等多種場景
開發者參考文檔:Push API v3 notification
2、自定義消息
自定義消息不是通知,所以不會被SDK展示到通知欄上。其內容完全由開發者自己定義。 自定義消息主要用於應用的內部業務邏輯。一條自定義消息推送過來,有可能沒有任何界面顯示。
開發者參考文檔:Push API v3 message
3、富媒體
JPush支持開發者發送圖文並茂的通知,從而更好的傳達信息,帶來更豐富的用戶互動。 JPush提供了5種模板,開發者可以通過填充模板的內容,發送landing page、彈窗、信息流形式的富媒體通知。 開發者還可以直接通過URL發送預先編輯好的頁面。 富媒體當前支持Android平台,為更好的使用富媒體的功能,建議更新當前SDK版本至v2.1.8及以上。 暫時只能通過極光推送的控制台發送,不支持API形式。
Android 開發者參考文檔:Rich Push開發指南
4、本地通知
本地通知API不依賴於網絡,無網條件下依舊可以觸發;本地通知的定時時間是自發送時算起的,不受中間關機等操作的影響。 本地通知與網絡推送的通知是相互獨立的,不受保留最近通知條數上限的限制。 本地通知適用於在特定時間發出的通知,如一些Todo和鬧鍾類的應用,在每周、每月固定時間提醒用戶回到應用查看任務。
Android 開發者參考文檔:Android 本地通知
推送目標
通過使用標簽,別名,Registration ID 和用戶分群,開發者可以向特定的一個或多個用戶推送消息。
1、標簽
為安裝了應用程序的用戶打上標簽,其目的主要是方便開發者根據標簽,來批量下發 Push 消息。 可為每個用戶打多個標簽。 舉例: game, old_page, women
2、別名
每個用戶只能指定一個別名(意思是說,一個別名可以有多個用戶)。 同一個應用程序內,對不同的用戶,建議取不同的別名。這樣,盡可能根據別名來唯一確定用戶。
Android 開發者參考文檔:Android 標簽和別名,使用別名和標簽推送請參考文檔:Push API v3 Audience
3、Registration ID
客戶端初始化 JPush 成功后,JPush 服務端會分配一個 Registration ID,作為此設備的標識(同一個手機不同 APP 的 Registration ID 是不同的)。開發者可以通過指定具體的 Registration ID 來進行對單一設備的推送。
4、用戶分群
用戶分群的篩選條件有:標簽、地理位置、系統版本、注冊時間、活躍用戶和在線用戶。 比如,開發者可以設置這樣的用戶分群:位於北京、上海、廣州和深圳,並且最近7天在線的用戶。 開發者可以通過在控制台設置好用戶分群之后,在控制台推送時指定該分群的名稱或使用API調用該分群的id發送。
用戶分群控制台使用指南:用戶分群
5、統計分析
JPush支持推送數量、用戶打開次數、用戶使用時長、新增用戶、活躍用戶等數據的統計。 Android開發者需要實現了相關的統計API,才可以進行用戶相關的統計。 iOS的開發者不需要實現統計API,可以直接在【控制台】-【統計】頁面查看相關數據。
Android 開發者參考文檔:統計分析API
集成步驟
1、快速開始
2、三 分鍾快速使用
文檔地址
創建應用
使用注冊賬號登陸,進入極光控制台后,點擊“創建應用”按鈕。創建帳號進入極光推送后,首先顯示的是創建應用的界面。填上你的應用程序的名稱,以及 Android包名這二項就可以了。
下載Demo,導入AS,運行
點擊 ”下載Demo“,你將下載到一個 .zip 壓縮文件。解壓后,即看到一個同名目錄。這個目錄下,是一個 Android 項目里的所有文件。
在 Android Studio 中,新建一個項目。 通過 import module 導入 JPush Example
導入 module 后在 Android studio 內運行到指定設備上
Portal上推送通知和消息
在上述步驟安裝 JPush Example 的手機上,你就可以收到推送的通知和消息了。
FAQ
怎么樣保證推送消息的安全?
我們建議開發者不要推送保密的信息,就像QQ建議你不要在聊天時發送保密的信息一樣。
如果開發者的確有保密的信息,需要送達到用戶,則可以考慮這樣做:
先通過 JPush 推送一條消息,這條消息觸發客戶端App去與開發者服務器交互保密信息。
極光推送后台使用什么技術實現的?是 XMPP 協議么?
后台主要使用純 C 語言實現。
使用自定義的二進制協議,以盡可能節約流量。
可以推送多媒體文件到客戶端么?
推送消息本身是限定長度的文本。
不直接支持文件的推送,但可以通過推送 url 來實現。
即先推送文件下載 url,到客戶端觸發邏輯來通過 url 下載文件。
為什么應用程序無法收到 Push 消息(Android)?
確認 appKey(在Portal上生成的)已經正確的寫入 Androidmanifest.xml
確認測試手機(或者模擬器)已成功連入網絡
確認有客戶端 "Login succeed" 日志
日志:Java.lang.UnsatisfiedLinkError
此錯誤是由於沒有正確的加載libjpush.so文件,請檢查libjpush.so是否在正確的位置(libs–>armeabi–>libjpush.so)
JPush SDK 遷移到 Android Studio 需要添加 .SO 文件打包到APK的lib文件夾中,可以編輯 build.gradle 腳本,自定義 .so 目錄。
日志:The permission should be defined
此錯誤是沒有正確的定義permision,請添加權限:
<permission android:name="您應用的包名.permission.JPUSH_MESSAGE" android:protectionLevel="signature" ></permission>
<uses-permission android:name="您應用的包名.permission.JPUSH_MESSAGE" ></uses>
推送成功了,為什么有部分客戶端收不到推送?
請檢查收不到通知的手機:
請在logcat查看日志,確定客戶端的jpush是否集成成功,網絡是否有問題
請看日志或使用接口 isPushStopped來檢查是否調用了stoppush
檢查手機的JPush高級設置中是否設置了“允許推送時間”
手機的應用中是否勾選了“顯示通知”
Tag、Alias、Registrationid需要每次初始化時都重新設置嗎,會變化嗎?
tag、alias可以參考別名與標簽 API進行設置,每次設置是覆蓋設置,而不是增量設置。Tag和alias一經設置成功,除非取消或覆蓋,是不會變化的。設置好的tag、alias與客戶端的對應關系保存在JPush服務器,目前沒有從JPush服務器查詢這個對應關系的接口,所以需要客戶將對應關系保存在APP應用服務器。
Registrationid是客戶端SDK第一次成功連接到Jpush服務器時,Jpush服務器給分配的。可以通過獲取 RegistrationID API來獲取Registrationid進行推送。Registrationid對應一個應用的一個客戶端。
appkey是怎么對應的?
android 的包名和 appkey 需對應。
簡潔版自定義消息推送Demo
自定義消息又叫應用內消息,或者稱作透傳消息。此部分內容不會展示到通知欄上,JPush SDK 收到消息內容后透傳給 App,需要 App 自行處理。
關鍵字 | 類型 | 選項 | 含義 |
---|---|---|---|
msg_content | string | 必填 | 消息內容本身 |
title | string | 可選 | 消息標題 |
content_type | string | 可選 | 消息內容類型 |
extras | JSON Object | 可選 | JSON 格式的可選參數 |
在適當的時候初始化
private void initJPush() {
JPushInterface.setDebugMode(true); // 設置開啟日志,發布時請關閉日志
JPushInterface.init(this); // 初始化 JPush
}
Activity
public class JPushTestActivity extends ListActivity {
public static boolean isForeground = false;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView tv = new TextView(this);
tv.setTextColor(Color.BLUE);
String string = "AppKey: " + PushUtil.getAppKey(this) + "\n" +
"IMEI: " + PushUtil.getImei(this, "") + "\n" +
"RegId:" + JPushInterface.getRegistrationID(this) + "\n" +
"PackageName: " + getPackageName() + "\n" +
"deviceId:" + PushUtil.getDeviceId(this) + "\n" +
"Version: " + PushUtil.GetVersionName(this);
tv.setText(string);
getListView().addHeaderView(tv);
String[] array = {"initPush", "stopPush", "resumePush",};
setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new ArrayList<>(Arrays.asList(array))));
EventBus.getDefault().register(this);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
switch (position - 1) {
case 0:
JPushInterface.init(this);// 初始化 JPush。如果已經初始化,但沒有登錄成功,則執行重新登錄。
break;
case 1:
JPushInterface.stopPush(this);
break;
case 2:
JPushInterface.resumePush(this);
break;
}
}
@Override
protected void onResume() {
isForeground = true;
super.onResume();
}
@Override
protected void onPause() {
isForeground = false;
super.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onPushEvent(BasePushBean bean) {
TextView tv = new TextView(this);
tv.setTextColor(Color.BLUE);
tv.setText(bean.msg);
getListView().addFooterView(tv);
}
}
Receiver
/**
* 自定義接收器。如果不定義這個 Receiver,則默認用戶會打開主界面,接收不到自定義消息
*/
public class JPushReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null || intent.getAction() == null) return;
Bundle bundle = intent.getExtras();
Log.i("bqt", "【JPushReceiver】Action:" + intent.getAction() + "\nextras:" + printBundle(bundle));
switch (intent.getAction()) {
case JPushInterface.ACTION_MESSAGE_RECEIVED://將自定義消息轉發到需要的地方
Log.i("bqt", "【JPushReceiver】接收到推送下來的自定義消息");
if (bundle != null) {
PushMsgReceiverHelper.getInstance().onJPushMsgReceiver(bundle);
}
break;
case JPushInterface.ACTION_REGISTRATION_ID:
String regId = bundle == null ? "" : bundle.getString(JPushInterface.EXTRA_REGISTRATION_ID);
Log.i("bqt", "【JPushReceiver】接收Registration Id : " + regId);
break;
case JPushInterface.ACTION_NOTIFICATION_RECEIVED:
Log.i("bqt", "【JPushReceiver】接收到推送下來的通知");
int notifactionId = bundle == null ? -1 : bundle.getInt(JPushInterface.EXTRA_NOTIFICATION_ID);
Log.i("bqt", "【JPushReceiver】接收到推送下來的通知的ID: " + notifactionId);
break;
case JPushInterface.ACTION_NOTIFICATION_OPENED:
Log.i("bqt", "【JPushReceiver】用戶點擊打開了通知");
break;
case JPushInterface.ACTION_RICHPUSH_CALLBACK: // 根據 JPushInterface.EXTRA_EXTRA 的內容處理代碼
String extra = bundle == null ? "" : bundle.getString(JPushInterface.EXTRA_EXTRA);
Log.i("bqt", "【JPushReceiver】用戶收到到RICH PUSH CALLBACK: " + extra);
break;
case JPushInterface.ACTION_CONNECTION_CHANGE:
boolean connected = intent.getBooleanExtra(JPushInterface.EXTRA_CONNECTION_CHANGE, false);
Log.i("bqt", "【JPushReceiver】" + intent.getAction() + " connected state change to " + connected);
break;
default:
Log.i("bqt", "【JPushReceiver】Unhandled intent - " + intent.getAction());
break;
}
}
// 打印所有的 intent extra 數據
private static String printBundle(Bundle bundle) {
if (bundle == null) return "";
StringBuilder sb = new StringBuilder();
for (String key : bundle.keySet()) {
switch (key) {
case JPushInterface.EXTRA_NOTIFICATION_ID:
sb.append("\nkey:").append(key).append(", value:").append(bundle.getInt(key));
break;
case JPushInterface.EXTRA_CONNECTION_CHANGE:
sb.append("\nkey:").append(key).append(", value:").append(bundle.getBoolean(key));
break;
case JPushInterface.EXTRA_EXTRA:
if (TextUtils.isEmpty(bundle.getString(JPushInterface.EXTRA_EXTRA))) {
Log.i("bqt", "This message has no Extra data");
continue;
}
try {
JSONObject json = new JSONObject(bundle.getString(JPushInterface.EXTRA_EXTRA));
Iterator<String> it = json.keys();
while (it.hasNext()) {
String myKey = it.next();
sb.append("\nkey:").append(key)
.append(", value: [").append(myKey)
.append(" - ").append(json.optString(myKey)).append("]");
}
} catch (JSONException e) {
e.printStackTrace();
}
break;
default:
sb.append("\nkey:").append(key).append(", value:").append(bundle.getString(key));
break;
}
}
return sb.toString();
}
}
消息處理類
/**
* 處理推送SDK推過來的自定義消息(又叫應用內消息,或者透傳消息)
*/
public class PushMsgReceiverHelper {
private static PushMsgReceiverHelper instance = new PushMsgReceiverHelper();
private PushMsgReceiverHelper() {
}
public static PushMsgReceiverHelper getInstance() {
return instance;
}
/**
* 處理極光推送推過來的自定義消息
*/
public void onJPushMsgReceiver(Bundle bundle) {
String message = bundle.getString(JPushInterface.EXTRA_MESSAGE);//必填,消息內容本身
String title = bundle.getString(JPushInterface.EXTRA_TITLE);//可選,消息標題
String type = bundle.getString(JPushInterface.EXTRA_CONTENT_TYPE);//可選,消息內容類型
String extra = bundle.getString(JPushInterface.EXTRA_EXTRA);//可選,JSON 格式的可選參數
String content = "msg:" + message + "\t title:" + title + "\t type:" + type + "\t extra:" + extra;
Log.i("bqt", "【極光推送】" + content);
if (JPushTestActivity.isForeground) {
EventBus.getDefault().post(new BasePushBean(content, BasePushBean.TYPE_STRING));
}
}
/**
* 處理小米推送推過來的自定義消息
*/
public void onMiPushMsgReceiver(MiPushMessage message) {
}
/**
* 處理華為光推送推過來的自定義消息
*/
public void onHuaweiPushMsgReceiver(String message) {
}
/**
* 處理魅族推送推過來的自定義消息
*/
public void onMeiZhuPushMsgReceiver(String message) {
}
}
Bean
public class BasePushBean {
public static final int TYPE_STRING = 1;
public static final int TYPE_JSONOBJ = 2;
public String msg;
public int type;
public BasePushBean(String msg, int type) {
this.msg = msg;
this.type = type;
}
}
配置文件
build.gradle
implementation files('libs/jcore-android-1.2.0.jar')
implementation files('libs/jpush-android-3.1.2.jar')
implementation 'org.greenrobot:eventbus:3.1.1'
混淆文件:proguard-android.txt
-dontoptimize
-dontpreverify
-dontwarn cn.jpush.**
-keep class cn.jpush.** { *; }
#v2.0.5 及以上的版本由於引入了protobuf ,在上面基礎之上增加排除混淆的配置。
#==================gson==========================
-dontwarn com.google.**
-keep class com.google.gson.** {*;}
#==================protobuf======================
-dontwarn com.google.**
-keep class com.google.protobuf.** {*;}
AndroidManifest.xml
<!--====================== 推送SDK需要定義的權限【需要更改包名】 =====================-->
<!--極光-->
<permission
android:name="com.bqt.push.permission.JPUSH_MESSAGE"
android:protectionLevel="signature"/>
<uses-permission android:name="com.bqt.push.permission.JPUSH_MESSAGE"/>
<!--小米-->
<!--華為-->
<!--魅族-->
<!--========================= 極光推送需要注冊的組件 start =========================-->
<!-- User defined. 用戶自定義的廣播接收器-->
<receiver
android:name="com.bqt.push.receiver.JPushReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="cn.jpush.android.intent.REGISTRATION"/> <!--Required 用戶注冊SDK的intent-->
<action android:name="cn.jpush.android.intent.MESSAGE_RECEIVED"/> <!--Required 用戶接收SDK消息的intent-->
<action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED"/> <!--Required 用戶接收SDK通知欄信息的intent-->
<action android:name="cn.jpush.android.intent.NOTIFICATION_OPENED"/> <!--Required 用戶打開自定義通知欄的intent-->
<action android:name="cn.jpush.android.intent.CONNECTION"/><!-- 接收網絡變化 連接/斷開 since 1.6.3 -->
<category android:name="com.bqt.push"/>
</intent-filter>
</receiver>
<activity
android:name="cn.jpush.android.ui.PopWinActivity"
android:exported="false"
android:theme="@style/MyDialogStyle">
</activity>
<activity
android:name="cn.jpush.android.ui.PushActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:theme="@android:style/Theme.NoTitleBar">
<intent-filter>
<action android:name="cn.jpush.android.ui.PushActivity"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="com.bqt.push"/>
</intent-filter>
</activity>
<service
android:name="cn.jpush.android.service.PushService"
android:exported="false"
android:process=":mult">
<intent-filter>
<action android:name="cn.jpush.android.intent.REGISTER"/>
<action android:name="cn.jpush.android.intent.REPORT"/>
<action android:name="cn.jpush.android.intent.PushService"/>
<action android:name="cn.jpush.android.intent.PUSH_TIME"/>
</intent-filter>
</service>
<provider
android:name="cn.jpush.android.service.DataProvider"
android:authorities="com.bqt.push.DataProvider"
android:exported="false"/>
<!-- 可選項。用於同一設備中不同應用的JPush服務相互拉起的功能。 -->
<!-- 若不啟用該功能可刪除該組件,將不拉起其他應用也不能被其他應用拉起 -->
<service
android:name="cn.jpush.android.service.DaemonService"
android:enabled="true"
android:exported="true"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="cn.jpush.android.intent.DaemonService"/>
<category android:name="com.bqt.push"/>
</intent-filter>
</service>
<provider
android:name="cn.jpush.android.service.DownloadProvider"
android:authorities="com.bqt.push.DownloadProvider"
android:exported="true"
tools:ignore="ExportedContentProvider"/>
<receiver
android:name="cn.jpush.android.service.PushReceiver"
android:enabled="true"
android:exported="false">
<intent-filter android:priority="1000">
<action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED_PROXY"/> <!--Required 顯示通知欄 -->
<category android:name="com.bqt.push"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.USER_PRESENT"/>
<action
android:name="android.net.conn.CONNECTIVITY_CHANGE"
tools:ignore="BatteryLife"/>
</intent-filter>
<!-- Optional -->
<intent-filter>
<action android:name="android.intent.action.PACKAGE_ADDED"/>
<action android:name="android.intent.action.PACKAGE_REMOVED"/>
<data android:scheme="package"/>
</intent-filter>
</receiver>
<receiver
android:name="cn.jpush.android.service.AlarmReceiver"
android:exported="false"/>
<!-- Enable it you can get statistics data with channel -->
<meta-data
android:name="JPUSH_CHANNEL"
android:value="developer-default"/>
<meta-data
android:name="JPUSH_APPKEY"
android:value="7ced56a29466cb706362bb82"/> <!-- 值來自開發者平台取得的AppKey-->
<!--========================= 極光推送需要注冊的組件 end =========================-->
2018-4-9