本文簡介:本文前篇,能夠幫助朋友們快速集成極光推送。本文後篇,是我本身項目實踐的一些總結和心得,應該對讀者們仍是頗有參考價值的,相信讀完這篇文章,你會對極光推送有更加深刻的理解,而不只僅只是會集成而已。總之呢,集成第三方SDK,都不是很難的事情,仔細閱讀文檔,一步步來,遇到Bug,慢慢解決就行,實在解決不了,能夠問問客服小哥哥或者小姐姐,重要的是,你得有着解決它的決心和耐心。html
開發者集成 JPush Android SDK 到其應用裏,JPush Android SDK 建立到 JPush Cloud 的長鏈接,爲 App 提供永遠在線的能力。 當開發者想要及時地推送消息到達 App 時,只須要調用 JPush API 推送,或者使用其餘方便的智能推送工具,便可輕鬆與用戶交流。 JPush Android SDK 是做爲 Android Service 長期運行在後臺的,從而建立並保持長鏈接,保持永遠在線的能力。前端
假設你已經註冊了極光的帳號,登陸進來了。點擊當即體驗,建立本身的應用。 android
App端集成須要用到的AppKey。添加集成代碼,此處使用jcenter的方式集成。 1、確認android studio的 Project 根目錄的主 gradle 中配置了jcenter支持。(新建project默認配置就支持)編程
buildscript {
repositories {
jcenter()
}
......
}
allprojets {
repositories {
jcenter()
}
}
複製代碼
2、在 module 的 gradle 中添加依賴和AndroidManifest的替換變量。json
android {
......
defaultConfig {
applicationId "com.xxx.xxx" //JPush上註冊的包名.
......
ndk {
//選擇要添加的對應cpu類型的.so庫。
abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a'
// 還能夠添加 'x86', 'x86_64', 'mips', 'mips64'
}
manifestPlaceholders = [
JPUSH_PKGNAME : applicationId,
JPUSH_APPKEY : "你的appkey", //JPush上註冊的包名對應的appkey.
JPUSH_CHANNEL : "developer-default", //暫時填寫默認值便可.
]
......
}
......
}
dependencies {
......
compile 'cn.jiguang.sdk:jpush:3.1.1' // 此處以JPush 3.1.1 版本爲例。
compile 'cn.jiguang.sdk:jcore:1.1.9' // 此處以JCore 1.1.9 版本爲例。
......
}
複製代碼
3、AndroidManifest.xml中添加後端
<!--Jpush配置 所需權限start-->
<!-- Required -->
<permission
android:name="你的應用包名.permission.JPUSH_MESSAGE"
android:protectionLevel="signature" />
<!-- Required 一些系統要求的權限,如訪問網絡等-->
<uses-permission android:name="你的應用包名.permission.JPUSH_MESSAGE" />
<uses-permission android:name="android.permission.RECEIVE_USER_PRESENT" />
<!--<uses-permission android:name="android.permission.INTERNET" />-->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!--<uses-permission android:name="android.permission.READ_PHONE_STATE" />-->
<!--<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />-->
<!--<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />-->
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<!--<uses-permission android:name="android.permission.VIBRATE" />-->
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<!--<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />-->
<!--<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />-->
<!-- Optional for location -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <!-- 用於開啓 debug 版本的應用在6.0 系統上 層疊窗口權限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!--<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />-->
<!--<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />-->
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<!--<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />-->
<!--<uses-permission android:name="android.permission.GET_TASKS" />-->
<!--Jpush配置 所需權限end-->
<!--Jpush配置 start-->
<application
<!-- Rich push 核心功能 since 2.0.6-->
<activity
android:name="cn.jpush.android.ui.PopWinActivity"
android:theme="@style/MyDialogStyle"
android:exported="false">
</activity>
<!-- Required SDK核心功能-->
<activity
android:name="cn.jpush.android.ui.PushActivity"
android:configChanges="orientation|keyboardHidden"
android:theme="@android:style/Theme.NoTitleBar"
android:exported="false">
<intent-filter>
<action android:name="cn.jpush.android.ui.PushActivity" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="你的應用包名" />
</intent-filter>
</activity>
<!-- Required SDK 核心功能-->
<!-- 可配置android:process參數將PushService放在其餘進程中 -->
<service
android:name="cn.jpush.android.service.PushService"
android:enabled="true"
android:exported="false">
<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>
<!-- since 3.0.9 Required SDK 核心功能-->
<provider
android:authorities="你的應用包名.DataProvider"
android:name="cn.jpush.android.service.DataProvider"
android:exported="false"
/>
<!-- since 1.8.0 option 可選項。用於同一設備中不一樣應用的JPush服務相互拉起的功能。 -->
<!-- 若不啓用該功能可刪除該組件,將不拉起其餘應用也不能被其餘應用拉起 -->
<service
android:name="cn.jpush.android.service.DaemonService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="cn.jpush.android.intent.DaemonService" />
<category android:name="你的應用包名" />
</intent-filter>
</service>
<!-- since 3.1.0 Required SDK 核心功能-->
<provider
android:authorities="你的應用包名.DownloadProvider"
android:name="cn.jpush.android.service.DownloadProvider"
android:exported="true"
/>
<!-- Required SDK核心功能-->
<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="你的應用包名" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.USER_PRESENT" />
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</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>
<!-- Required SDK核心功能-->
<receiver android:name="cn.jpush.android.service.AlarmReceiver" android:exported="false"/>
<!-- User defined. For test only MyReceiver爲用戶自定義的廣播接收器-->
<receiver
android:name=".util.MyReceiver"
android:exported="false"
android:enabled="true">
<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="你的應用包名" />
</intent-filter>
</receiver>
<!-- Required . 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="應用的Appkey" /> <!-- </>值來自開發者平臺取得的AppKey-->
</application>
<!--Jpush配置 end-->
複製代碼
4、自定義一個廣播接收器MyReceiver(此處直接使用官方Demo中的MyReceiver)api
/**
* 自定義接收器
*
* 若是不定義這個 Receiver,則:
* 1) 默認用戶會打開主界面
* 2) 接收不到自定義消息
*/
public class MyReceiver extends BroadcastReceiver {
private static final String TAG = "JIGUANG-Example";
@Override
public void onReceive(Context context, Intent intent) {
try {
Bundle bundle = intent.getExtras();
Logger.d(TAG, "[MyReceiver] onReceive - " + intent.getAction() + ", extras: " + printBundle(bundle));
if (JPushInterface.ACTION_REGISTRATION_ID.equals(intent.getAction())) {
String regId = bundle.getString(JPushInterface.EXTRA_REGISTRATION_ID);
Logger.d(TAG, "[MyReceiver] 接收Registration Id : " + regId);
//send the Registration Id to your server...
} else if (JPushInterface.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) {
Logger.d(TAG, "[MyReceiver] 接收到推送下來的自定義消息: " + bundle.getString(JPushInterface.EXTRA_MESSAGE));
processCustomMessage(context, bundle);
} else if (JPushInterface.ACTION_NOTIFICATION_RECEIVED.equals(intent.getAction())) {
Logger.d(TAG, "[MyReceiver] 接收到推送下來的通知");
int notifactionId = bundle.getInt(JPushInterface.EXTRA_NOTIFICATION_ID);
Logger.d(TAG, "[MyReceiver] 接收到推送下來的通知的ID: " + notifactionId);
} else if (JPushInterface.ACTION_NOTIFICATION_OPENED.equals(intent.getAction())) {
Logger.d(TAG, "[MyReceiver] 用戶點擊打開了通知");
//打開自定義的Activity
Intent i = new Intent(context, TestActivity.class);
i.putExtras(bundle);
//i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP );
context.startActivity(i);
} else if (JPushInterface.ACTION_RICHPUSH_CALLBACK.equals(intent.getAction())) {
Logger.d(TAG, "[MyReceiver] 用戶收到到RICH PUSH CALLBACK: " + bundle.getString(JPushInterface.EXTRA_EXTRA));
//在這裏根據 JPushInterface.EXTRA_EXTRA 的內容處理代碼,好比打開新的Activity, 打開一個網頁等..
} else if(JPushInterface.ACTION_CONNECTION_CHANGE.equals(intent.getAction())) {
boolean connected = intent.getBooleanExtra(JPushInterface.EXTRA_CONNECTION_CHANGE, false);
Logger.w(TAG, "[MyReceiver]" + intent.getAction() +" connected state change to "+connected);
} else {
Logger.d(TAG, "[MyReceiver] Unhandled intent - " + intent.getAction());
}
} catch (Exception e){
}
}
// 打印全部的 intent extra 數據
private static String printBundle(Bundle bundle) {
StringBuilder sb = new StringBuilder();
for (String key : bundle.keySet()) {
if (key.equals(JPushInterface.EXTRA_NOTIFICATION_ID)) {
sb.append("\nkey:" + key + ", value:" + bundle.getInt(key));
}else if(key.equals(JPushInterface.EXTRA_CONNECTION_CHANGE)){
sb.append("\nkey:" + key + ", value:" + bundle.getBoolean(key));
} else if (key.equals(JPushInterface.EXTRA_EXTRA)) {
if (TextUtils.isEmpty(bundle.getString(JPushInterface.EXTRA_EXTRA))) {
Logger.i(TAG, "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:" + key + ", value: [" +
myKey + " - " +json.optString(myKey) + "]");
}
} catch (JSONException e) {
Logger.e(TAG, "Get message extra JSON error!");
}
} else {
sb.append("\nkey:" + key + ", value:" + bundle.getString(key));
}
}
return sb.toString();
}
//send msg to MainActivity
private void processCustomMessage(Context context, Bundle bundle) {
if (MainActivity.isForeground) {
String message = bundle.getString(JPushInterface.EXTRA_MESSAGE);
String extras = bundle.getString(JPushInterface.EXTRA_EXTRA);
Intent msgIntent = new Intent(MainActivity.MESSAGE_RECEIVED_ACTION);
msgIntent.putExtra(MainActivity.KEY_MESSAGE, message);
if (!ExampleUtil.isEmpty(extras)) {
try {
JSONObject extraJson = new JSONObject(extras);
if (extraJson.length() > 0) {
msgIntent.putExtra(MainActivity.KEY_EXTRAS, extras);
}
} catch (JSONException e) {
}
}
LocalBroadcastManager.getInstance(context).sendBroadcast(msgIntent);
}
}
}
複製代碼
5、在Application中的onCreate()方法中初始化極光推送bash
// 初始化 JPush
JPushInterface.init(this);
//發佈時關閉日誌
JPushInterface.setDebugMode(false);
複製代碼
6、最後不要忘了添加混淆代碼哦!服務器
請在工程的混淆文件proguard-rules.pro中添加如下配置:
-dontoptimize
-dontpreverify
-dontwarn cn.jpush.**
-keep class cn.jpush.** { *; }
-keep class * extends cn.jpush.android.helpers.JPushMessageReceiver { *; }
-dontwarn cn.jiguang.**
-keep class cn.jiguang.** { *; }
複製代碼
7、經過極光官網控制檯推送,測試推送結果。 網絡
以下圖,會在MyReceiver中收到RegistrationID的值。只要極光推送第一次註冊成功了,後期不會再發 RegistrationID 的廣播了。RegistrationID 就會被保留在手機內,下次即便你是無網狀態進入APP,你也能夠獲取到這個RegistrationID。有了這個標識,App 編程能夠把這個 RegistrationID 保存到本身的應用服務器上,而後就能夠根據 RegistrationID 來向設備推送消息或者通知。因此建議能夠在你的Application和MyReceiver中都對這個RegistrationID進行賦值。而後根據項目的業務須要,將RegistrationID和用戶標識的對應關係,上傳本身的服務端。
public class MyApplication extends Application{
public static String registrationID;
@Override
public void onCreate() {
// 初始化 JPush
JPushInterface.init(this);
registrationID = JPushInterface.getRegistrationID(this);
Log.d("TAG", "接收Registration Id : " + registrationID);
}
}
複製代碼
【注意】: 若是 App 不卸載,是直接覆蓋安裝,Android, iOS 上 RegistrationID 的值都不會變化。 若是 App 是卸載以後再次安裝:Android 上 RegistrationID 基本不會變; iOS 上若是啓用了 IDFA 變化可能性不大,若是未啓用 IDFA 則每次安裝 RegistrationID 都會變; 參考:極光推送的設備惟一性標識 RegistrationID
若是使用此種推送方式,你可能會遇到的問題:假設在一個設備中登陸不一樣的帳號,那此時上傳給服務器的都是同一個RegistrationID,由於設備沒有變化。那麼可能出現:原本服務器是根據A用戶找到它的RegistrationID進行推送,可是B用戶也是在A用戶登陸的設備登陸的,RegistrationID跟A的同樣,這樣就致使B用戶收到了推送給A用戶的推送內容。
舉例:在一個用戶要登陸的遊戲中,可能設置別名爲 userid。遊戲運營時,發現該用戶 3 天沒有玩遊戲了,則根據 userid 調用服務器端API發通知到客戶端提醒用戶。
深刻理解各類推送方式能夠參考:推送人羣的選擇 – 推送方式-技術篇
下面是一個JPush設置別名和標籤的輔助類。
public class JPushHelper {
private String TAG = "JPushHelper";
/**
* 設置別名與標籤
*
* @param UUID
*/
private void setAlias(String UUID) {
if (null != UUID) {
//恢復接收推送
JPushInterface.resumePush(MyApplication.getInstance());
JPushInterface.setAliasAndTags(MyApplication.getInstance(), UUID, null, mAliasCallback);
}
}
public void setAlias() {
if (MyApplication.isLogin) { //必須登陸
UserInfo userBean = ACT_Login.getLoginUser();
if (null != userBean) {
//根據具體業務需求,能夠再拼接上其餘相關字段
//String alias = "";
//StringBuilder stringBuffer = new StringBuilder();
// stringBuffer.append(StringUtil.getString(userBean.user_id));
Log.e(TAG, stringBuffer.toString());
alias = MD5Util.string2MD5(userBean.user_id);
//限制:alias 命名長度限制爲 40 字節。(判斷長度需採用UTF-8編碼)
Log.e(TAG, alias);
//setAlias("");
setAlias(alias);
}
}
}
/**
* 中止接收推送
*/
public void removeAlias() {
JPushInterface.clearAllNotifications(MyApplication.getInstance());
//setAlias("");//該句打開的話,若是退出登陸後,用戶就收不到離線的消息了。
JPushInterface.stopPush(MyApplication.getInstance());
}
private final TagAliasCallback mAliasCallback = new TagAliasCallback() {
@Override
public void gotResult(int code, String alias, Set<String> tags) {
String logs;
switch (code) {
case 0:
// 建議這裏往 SharePreference 裏寫一個成功設置的狀態。成功設置一次後,之後沒必要再次設置了。
logs = "Set tag and alias success";
Log.e(TAG, logs);
break;
case 6002:
logs = "Failed to set alias and tags due to timeout. Try again after 60s.";
// 延遲 60 秒來調用 Handler 設置別名
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SET_ALIAS, alias), 1000 * 6);
Log.e(TAG, logs + AppDateUtil.getTimeStamp(System.currentTimeMillis(), AppDateUtil.MM_DD_HH_MM_SS));
break;
default:
logs = "Failed with errorCode = " + code;
Log.e(TAG, logs);
}
}
};
private static final int MSG_SET_ALIAS = 1001;
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(android.os.Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case MSG_SET_ALIAS:
Log.e(TAG, "Set alias in handler." + ((String) msg.obj));
// 調用 JPush 接口來設置別名。
JPushInterface.setAliasAndTags(MyApplication.getInstance(),
(String) msg.obj,
null,
mAliasCallback);
break;
default:
Log.e(TAG, "Unhandled msg - " + msg.what);
}
}
};
// 校驗Tag Alias 只能是數字,英文字母和中文
public static boolean isValidTagAndAlias(String s) {
Pattern p = Pattern.compile("^[\u4E00-\u9FA50-9a-zA-Z_!@#$&*+=.|]+$");
Matcher m = p.matcher(s);
return m.matches();
}
}
複製代碼
關於這個問題能夠看看這位小姐姐的總結:
Android 關於App被殺死後,如何接收極光推送
極光推送相關閱讀參考:
官網集成
極光推送Android端API
詳解極光推送的 4 種消息形式 —— Android 篇
常見問題 - JPush 合集(持續更新)