現(xiàn)在大不分應(yīng)用都有推送的功能罢坝,今天總結(jié)下最近用到的極光推動(dòng)的android端和服務(wù)器端開(kāi)發(fā)過(guò)程世蔗。
1.1推送的原理
分服務(wù)器和客戶端分別進(jìn)行說(shuō)明:
1.推送框架圖
- 推送的數(shù)據(jù)源:自己開(kāi)發(fā)的服務(wù)器端或者使用極光推送官網(wǎng)的WEB后臺(tái)(++用于客戶自定義服務(wù)器向推送框架進(jìn)行推送++)
- JPush API:部署在服務(wù)器端,開(kāi)發(fā)者的服務(wù)器端發(fā)起推送時(shí),將數(shù)據(jù)傳到JPush API中铆铆,然后向下傳遞
- 建立長(zhǎng)鏈接:集成JPush的SDK客戶端啟動(dòng)后會(huì)建立一個(gè)到JPush Cloud的長(zhǎng)鏈接(++手機(jī)客戶端收到推送消息++)
1.2客戶端原理
客戶端需要和服務(wù)器保存長(zhǎng)連接狀態(tài)庄萎。jpush SDK提供了TCP Long Connection辙纬。實(shí)現(xiàn)的原理:
- 心跳:為了長(zhǎng)時(shí)間保持外網(wǎng)IP生兆,需要客戶端定期發(fā)送心跳給運(yùn)營(yíng)商瓷马,以便刷新NAT列表
- Timer定時(shí)方法:該類計(jì)劃循環(huán)執(zhí)行定時(shí)任務(wù)拴还,但是使用該類會(huì)使CPU保持喚醒狀態(tài),比較費(fèi)電欧聘。
- AlarmManager定時(shí)方法:該類封裝了Android手機(jī)的RTC硬件時(shí)鐘模塊片林,可以在CPU休眠時(shí)正常運(yùn)行,保持任務(wù)執(zhí)行時(shí)再喚醒CPU怀骤,這樣做到了電量節(jié)省费封。
2.實(shí)現(xiàn)步驟
2.1注冊(cè)極光推送
極光推送網(wǎng)址:https://www.jiguang.cn
進(jìn)行自行注冊(cè),然后進(jìn)入控制臺(tái)蒋伦,添加自己的應(yīng)用弓摘。在應(yīng)用設(shè)置中就能看到自己的AppKey的值,這個(gè)是此用于的唯一標(biāo)示符凉敲,在客戶端開(kāi)發(fā)和服務(wù)器端開(kāi)發(fā)中都會(huì)用到此值衣盾。
2.2集成android客戶端
按照上圖的提示進(jìn)行炒作即可。
下面在android studio中介紹下集成流程:
- builde.gradle中添加引用爷抓,即導(dǎo)入依賴包
compile 'cn.jiguang.sdk:jpush:3.0.0'
compile 'cn.jiguang.sdk:jcore:1.0.0'
2.添加統(tǒng)計(jì)信息势决,也可以不使用
@Override
protected void onResume() {
super.onResume();
MobclickAgent.onResume(this);
}
@Override
protected void onPause() {
super.onPause();
MobclickAgent.onPause(this);
}
3.jpush初始化
public class MyApplication extends Application {
public static String APPID = "915a5566e71a88d33a2851e0d2b19512";
@Override
public void onCreate() {
JPushInterface.setDebugMode(true); // 設(shè)置開(kāi)啟日志,發(fā)布時(shí)請(qǐng)關(guān)閉日志
JPushInterface.init(this); // 初始化 JPush
}
}
4.在AndroidMainfest.xml添加
<!-- Required 極光推送添加 -->
<permission
android:name="com.ylink.MGessTraderYlink_gxjszx.permission.JPUSH_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="com.ylink.MGessTraderYlink_gxjszx.permission.JPUSH_MESSAGE" />
<uses-permission android:name="android.permission.RECEIVE_USER_PRESENT" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<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.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- Required SDK核心功能 -->
<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.ylink.MGessTraderYlink_gxjszx" />
</intent-filter>
</activity>
<!-- Required SDK核心功能 -->
<service
android:name="cn.jpush.android.service.DownloadService"
android:enabled="true"
android:exported="false" />
<!-- Required SDK 核心功能 -->
<!-- 可配置android:process參數(shù)將PushService放在其他進(jìn)程中 -->
<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>
<!-- Required SDK核心功能 -->
<receiver
android:name="cn.jpush.android.service.PushReceiver"
android:enabled="true">
<intent-filter android:priority="1000">
<action android:name="cn.jpush.android.intent.NOTIFICATION_RECEIVED_PROXY" /> <!-- Required 顯示通知欄 -->
<category android:name="com.ylink.MGessTraderYlink_gxjszx" />
</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 用戶自定義的廣播接收器 -->
<receiver
android:name=".MyReceiver"
android:enabled="true"
android:exported="false">
<intent-filter>
<action android:name="cn.jpush.android.intent.REGISTRATION" /> <!-- Required 用戶注冊(cè)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 用戶打開(kāi)自定義通知欄的intent -->
<action android:name="cn.jpush.android.intent.CONNECTION" /> <!-- 接收網(wǎng)絡(luò)變化 連接/斷開(kāi) since 1.6.3 -->
<category android:name="com.ylink.MGessTraderYlink_gxjszx" />
</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="yourappkey" />
5.自定義MyReceiver
/**
* 自定義接收器
*
* 如果不定義這個(gè) Receiver,則:
* 1) 默認(rèn)用戶會(huì)打開(kāi)主界面
* 2) 接收不到自定義消息
*/
public class MyReceiver extends BroadcastReceiver {
private NotificationManager notificationManager;
private int reqCode = 0;
private NotificationImpl notificationImpl;
@Override
public void onReceive(Context context, Intent intent) {
if (notificationManager == null) {
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
}
Bundle bundle = intent.getExtras();
MyLog.i("[MyReceiver] onReceive - " + intent.getAction() + ", extras: " + printBundle(bundle));
if (JPushInterface.ACTION_REGISTRATION_ID.equals(intent.getAction())) {
String regId = bundle.getString(JPushInterface.EXTRA_REGISTRATION_ID);
MyLog.i("[MyReceiver] 接收Registration Id : " + regId);
} else if (JPushInterface.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) {
MyLog.i("[MyReceiver] 接收到推送下來(lái)的自定義消息: " + bundle.getString(JPushInterface.EXTRA_MESSAGE));
showNotification(context,bundle);
} else if (JPushInterface.ACTION_NOTIFICATION_RECEIVED.equals(intent.getAction())) {
MyLog.i("[MyReceiver] 接收到推送下來(lái)的通知");
int notifactionId = bundle.getInt(JPushInterface.EXTRA_NOTIFICATION_ID);
MyLog.i("[MyReceiver] 接收到推送下來(lái)的通知的ID: " + notifactionId);
receivingNotification(context,bundle);
} else if (JPushInterface.ACTION_NOTIFICATION_OPENED.equals(intent.getAction())) {
MyLog.i("[MyReceiver] 用戶點(diǎn)擊打開(kāi)了通知");
openNotification(context,bundle);
} else if (JPushInterface.ACTION_RICHPUSH_CALLBACK.equals(intent.getAction())) {
MyLog.i("[MyReceiver] 用戶收到到RICH PUSH CALLBACK: " + bundle.getString(JPushInterface.EXTRA_EXTRA));
//在這里根據(jù) JPushInterface.EXTRA_EXTRA 的內(nèi)容處理代碼蓝撇,比如打開(kāi)新的Activity果复, 打開(kāi)一個(gè)網(wǎng)頁(yè)等..
} else if(JPushInterface.ACTION_CONNECTION_CHANGE.equals(intent.getAction())) {
boolean connected = intent.getBooleanExtra(JPushInterface.EXTRA_CONNECTION_CHANGE, false);
MyLog.i("[MyReceiver]" + intent.getAction() +" connected state change to "+connected);
} else {
MyLog.i("[MyReceiver] Unhandled intent - " + intent.getAction());
}
}
//自定義通知框
private void showNotification(Context context, Bundle bundle) {
String extras = bundle.getString(JPushInterface.EXTRA_EXTRA);
reqCode++;
String notificationType = "0";
try {
JSONObject extrasJson = new JSONObject(extras);
notificationType = extrasJson.optString("Notificaction");
} catch (Exception e) {
MyLog.e("Unexpected: extras is not a valid json" + e.toString());
}
if (notificationImpl == null) {
notificationImpl = new NotificationImpl(context);
}
notificationImpl.setRequestCode(reqCode);
switch (notificationType){
case "0":
notificationImpl.notify_normal_singLine(bundle);
break;
case "1":
notificationImpl.notify_normal_moreLine(bundle);
break;
case "2":
notificationImpl.notify_mailbox(bundle);
break;
case "3":
notificationImpl.notify_bigPic(bundle);
break;
case "4":
notificationImpl.notify_customview(bundle);
break;
case "5":
notificationImpl.notify_buttom(bundle);
break;
case "6":
notificationImpl.notify_progress(bundle);
break;
case "7":
notificationImpl.notify_headUp(bundle);
break;
default:
break;
}
}
private void receivingNotification(Context context, Bundle bundle) {
CustomPushNotificationBuilder builder = new
CustomPushNotificationBuilder(context,
R.layout.yyb_notification,
R.id.icon,
R.id.title,
R.id.text);
// 指定定制的 Notification Layout
builder.statusBarDrawable = R.drawable.down;
// 指定最頂層狀態(tài)欄小圖標(biāo)
builder.layoutIconDrawable = R.drawable.arrow_up;
// 指定下拉狀態(tài)欄時(shí)顯示的通知圖標(biāo)
JPushInterface.setPushNotificationBuilder(2, builder);
String title = bundle.getString(JPushInterface.EXTRA_NOTIFICATION_TITLE);
MyLog.i( " title : " + title);
String message = bundle.getString(JPushInterface.EXTRA_ALERT);
MyLog.i( "message : " + message);
String extras = bundle.getString(JPushInterface.EXTRA_EXTRA);
MyLog.i( "extras : " + extras);
}
private void openNotification(Context context, Bundle bundle) {
String extras = bundle.getString(JPushInterface.EXTRA_EXTRA);
String myValue = "";
try {
JSONObject extrasJson = new JSONObject(extras);
myValue = extrasJson.optString("myKey");
} catch (Exception e) {
MyLog.e("Unexpected: extras is not a valid json" + e.toString());
return;
}
}
// 打印所有的 intent extra 數(shù)據(jù)
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))) {
MyLog.d("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().toString();
sb.append("\nkey:" + key + ", value: [" +
myKey + " - " +json.optString(myKey) + "]");
}
} catch (JSONException e) {
MyLog.e("Get message extra JSON error!");
}
} else {
sb.append("\nkey:" + key + ", value:" + bundle.getString(key));
}
}
return sb.toString();
}
}
ps:
- 通知信息和自定義信息的區(qū)別是:通知會(huì)在通知欄中顯示,自定義需要自己出來(lái)渤昌,可以自定義顯示虽抄。
- 通知狀態(tài)欄也可以自行定義。
- showNotification可以自行定義通知框的顯示独柑。但是需要后臺(tái)服務(wù)器中下發(fā)對(duì)應(yīng)的字段進(jìn)行控制迈窟。這里使用了一個(gè)開(kāi)源通知庫(kù): https://github.com/wenmingvs/NotifyUtil。 但是小米mui8手機(jī)上通知信息達(dá)不到效果忌栅。在其他的手機(jī)上還是有效果车酣。
6.定向推送
在開(kāi)發(fā)中為了定向推送到指定的手機(jī)上,所以需要在客戶端中綁定相應(yīng)的別名,這樣就可以定向推送到指定的手機(jī)上了湖员。因?yàn)榭蛻舳撕头?wù)器端都是自己開(kāi)發(fā)贫悄,可以通過(guò)用戶名做為別名進(jìn)行綁定。
// 調(diào)用 Handler 來(lái)異步設(shè)置別名
if (!user_id.equals(SpUtils.get(getApplicationContext(),"TAG_ALIAS",""))){
SpUtils.remove(getApplicationContext(),"TAG_ALIAS");
mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_ALIAS, MUtils.user_id));
}
private final TagAliasCallback mAliasCallback = new TagAliasCallback() {
@Override
public void gotResult(int code, String alias, Set<String> tags) {
String logs ;
switch (code) {
case 0:
logs = "Set tag and alias success";
MyLog.i(logs);
SpUtils.put(getApplicationContext(),"TAG_ALIAS",alias);
break;
case 6002:
logs = "Failed to set alias and tags due to timeout. Try again after 60s.";
MyLog.i( logs);
// 延遲 60 秒來(lái)調(diào)用 Handler 設(shè)置別名
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SET_ALIAS, alias), 1000 * 60);
break;
default:
logs = "Failed with errorCode = " + code;
MyLog.i(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:
MyLog.i("Set alias in handler.");
// 調(diào)用 JPush 接口來(lái)設(shè)置別名娘摔。
JPushInterface.setAliasAndTags(getApplicationContext(),
(String) msg.obj,
null,
mAliasCallback);
break;
default:
MyLog.i( "Unhandled msg - " + msg.what);
}
}
};
這里使用SharedPreferences
public class SpUtils
{
/**
* 保存在手機(jī)里面的文件名
*/
public static final String FILE_NAME = "share_data";
/**
* 保存數(shù)據(jù)的方法窄坦,我們需要拿到保存數(shù)據(jù)的具體類型,然后根據(jù)類型調(diào)用不同的保存方法
*
* @param context
* @param key
* @param object
*/
public static void put(Context context, String key, Object object)
{
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
if (object instanceof String)
{
editor.putString(key, (String) object);
} else if (object instanceof Integer)
{
editor.putInt(key, (Integer) object);
} else if (object instanceof Boolean)
{
editor.putBoolean(key, (Boolean) object);
} else if (object instanceof Float)
{
editor.putFloat(key, (Float) object);
} else if (object instanceof Long)
{
editor.putLong(key, (Long) object);
} else
{
editor.putString(key, object.toString());
}
SharedPreferencesCompat.apply(editor);
}
/**
* 得到保存數(shù)據(jù)的方法凳寺,我們根據(jù)默認(rèn)值得到保存的數(shù)據(jù)的具體類型鸭津,然后調(diào)用相對(duì)于的方法獲取值
*
* @param context
* @param key
* @param defaultObject
* @return
*/
public static Object get(Context context, String key, Object defaultObject)
{
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
Context.MODE_PRIVATE);
if (defaultObject instanceof String)
{
return sp.getString(key, (String) defaultObject);
} else if (defaultObject instanceof Integer)
{
return sp.getInt(key, (Integer) defaultObject);
} else if (defaultObject instanceof Boolean)
{
return sp.getBoolean(key, (Boolean) defaultObject);
} else if (defaultObject instanceof Float)
{
return sp.getFloat(key, (Float) defaultObject);
} else if (defaultObject instanceof Long)
{
return sp.getLong(key, (Long) defaultObject);
}
return null;
}
/**
* 移除某個(gè)key值已經(jīng)對(duì)應(yīng)的值
* @param context
* @param key
*/
public static void remove(Context context, String key)
{
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.remove(key);
SharedPreferencesCompat.apply(editor);
}
/**
* 清除所有數(shù)據(jù)
* @param context
*/
public static void clear(Context context)
{
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.clear();
SharedPreferencesCompat.apply(editor);
}
/**
* 查詢某個(gè)key是否已經(jīng)存在
* @param context
* @param key
* @return
*/
public static boolean contains(Context context, String key)
{
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
Context.MODE_PRIVATE);
return sp.contains(key);
}
/**
* 返回所有的鍵值對(duì)
*
* @param context
* @return
*/
public static Map<String, ?> getAll(Context context)
{
SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
Context.MODE_PRIVATE);
return sp.getAll();
}
/**
* 創(chuàng)建一個(gè)解決SharedPreferencesCompat.apply方法的一個(gè)兼容類
*
* @author zhy
*
*/
private static class SharedPreferencesCompat
{
private static final Method sApplyMethod = findApplyMethod();
/**
* 反射查找apply的方法
*
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private static Method findApplyMethod()
{
try
{
Class clz = SharedPreferences.Editor.class;
return clz.getMethod("apply");
} catch (NoSuchMethodException e)
{
}
return null;
}
/**
* 如果找到則使用apply執(zhí)行,否則使用commit
*
* @param editor
*/
public static void apply(SharedPreferences.Editor editor)
{
try
{
if (sApplyMethod != null)
{
sApplyMethod.invoke(editor);
return;
}
} catch (IllegalArgumentException e)
{
} catch (IllegalAccessException e)
{
} catch (InvocationTargetException e)
{
}
editor.commit();
}
}
}
到此android客戶端集成結(jié)束读第。
2.3服務(wù)端集成
- 1.這里我使用的是java服務(wù)器端曙博。源碼地址為: https://github.com/jpush/jpush-api-java-client
- 2.這里還需要依賴一個(gè)公共包。源碼: https://github.com/jpush/jiguang-java-client-common
- 3.如果只是使用可以直接下載jar包即可 https://github.com/jpush/jpush-api-java-client/releases
引入到現(xiàn)有工程中怜瞒。把1中的jar和測(cè)試文件到入到工程中父泳,把3中的jar文件導(dǎo)入到工程中。
詳細(xì)的使用可以參考1中的介紹吴汪。我這只對(duì)發(fā)通知和消息和指定對(duì)象推送進(jìn)行測(cè)試
public class Test {
protected static final Logger LOG = LoggerFactory.getLogger(Test.class);
public static final String ALERT = "數(shù)據(jù)";
public static void main(String[] args) {
testSendPush();
}
public static void testSendPush() {
JPushClient jpushClient = new JPushClient(Config.masterSecret, Config.appKey);
PushPayload payload = buildPushObject_all_all_alert();
try {
PushResult result = jpushClient.sendPush(payload);
LOG.info("Got result - " + result);
} catch (APIConnectionException e) {
LOG.error("Connection error. Should retry later. ", e);
} catch (APIRequestException e) {
LOG.error("Error response from JPush server. Should review and fix it. ", e);
LOG.info("HTTP Status: " + e.getStatus());
LOG.info("Error Code: " + e.getErrorCode());
LOG.info("Error Message: " + e.getErrorMessage());
LOG.info("Msg ID: " + e.getMsgId());
}
}
public static PushPayload buildPushObject_all_all_alert() {
// PushPayload payload = PushPayload
// .newBuilder()
// .setPlatform(Platform.android())
// .setAudience(Audience.tag("tag1"))
// .setNotification(
// Notification
// .newBuilder()
// .setAlert(ALERT)
// .addPlatformNotification(
// IosNotification.newBuilder().incrBadge(1)
// .addExtra("extra_key", "extra_value").build())
// .build()).build();
// return payload;
Map<String, String> extras = new HashMap<String, String>();
extras.put("Notificaction", "0");
extras.put("URL", "www.baidu.com");
Message.content("數(shù)據(jù)");
PushPayload payload = PushPayload.newBuilder()
.setPlatform(Platform.android())
.setAudience(Audience.alias("0123456789"))
.setOptions(Options.sendno())
.setNotification(Notification.android("一個(gè)驚喜", "號(hào)外", extras))
//.setMessage(Message.newBuilder().setMsgContent("").addExtra("Notificaction", "0").build())
// .setMessage(
// Message.newBuilder().setMsgContent("數(shù)據(jù)").addExtra("Notificaction", "7").setTitle("大爺?shù)?)
// .build())
.build();
return payload;
}
}
到處服務(wù)端集成成功惠窄。