# 概述
市面上的推送平台比较多,比如极光推送、力推、友盟推送、信鸽推送等,相比较而言,极光推送还处于非常不错的位置。
# 账号和概念
登录网站<https://www.jiguang.cn/>申请。如何在后台创建应用、[查阅文档](http://docs.jiguang.cn/jpush/client/Android/android_sdk/)不再赘述。
明确几个概念
1.通知:即手机端通知栏显示的内容方式
2.自定义消息:手机端能收到消息,但是不显示在通知栏,用于处理业务逻辑
3.富媒体消息:Android端使用,iOS不支持,可以发送广告信息或者版本升级提醒
另外,关系极光的其他一组概念
1.RegisterationId:某个设备上某个APP的唯一标识
2.Tag:标签,一个APP可以设置多个标签
3.Alias:别名,一个APP只能设置一个别名
# Android端
根据以往经验,在Android端使用的主要功能概括为:
- 可以正常接收推送过来的广播消息
- 可以根据特定的标识单独推送消息,比如安全提醒等
- 可以根据某组用户推动,比如将化妆品信息只推送给女性
- 有设备唯一标识,比如封禁某个设备
- 可以根据推送过来的不同消息类型,跳转到不同页面或其他作用
新建一个Android项目,打开module下的gradle文件,查看applicationId是否和后台配置的文件包名一致,如果不一定会发送失败。同时在defaultConfig中添加ndk和manifestPlaceholders。
~~~
defaultConfig {
applicationId "com.fymod.jpush"
minSdkVersion 14
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
ndk {
//选择要添加的对应cpu类型的.so库。
abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a' // 还可以添加 'x86', 'x86_64', 'mips', 'mips64'
}
manifestPlaceholders = [
JPUSH_PKGNAME : applicationId,
JPUSH_APPKEY : "201dc8c87b057a824b49ac94", //JPush上注册的包名对应的appkey.
JPUSH_CHANNEL : "developer-default", //暂时填写默认值即可.
]
}
~~~
在dependencies中添加极光的引入,同时为了方便,我增加了butterknife的引入
~~~
compile 'cn.jiguang.sdk:jpush:3.1.5' // 此处以JPush 3.1.5 版本为例。
compile 'cn.jiguang.sdk:jcore:1.2.3' // 此处以JCore 1.2.3 版本为例。
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
~~~
为了方便统一管理,我新建了一个Application,并在AndroidManifest.xml中设置为application的android:name。同时也添加了一个receiver,用于处理广播。
~~~
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.fymod.jpush">
<application
android:name=".MainApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name="com.fymod.jpush.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver
android:name=".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="com.fymod.jpush" />
</intent-filter>
</receiver>
</application>
</manifest>
~~~
初始化代码为
~~~
package com.fymod.jpush;
import android.app.Application;
import cn.jpush.android.api.JPushInterface;
public class MainApplication extends Application {
// 广播消息Action名称
public static final String MESSAGE_RECEIVED_ACTION = "com.fymod.jpush.MESSAGE_RECEIVED_ACTION";
// 对应极光消息的文本内容字段
public static final String KEY_MESSAGE = "message";
// 对应极光消息的附件信息字段
public static final String KEY_EXTRAS = "extras";
@Override
public void onCreate() {
super.onCreate();
JPushInterface.setDebugMode(true); //开启调试模式
JPushInterface.init(this); //初始化
}
}
~~~
对应上面的广播,需要定义MyReceiver。如果不定义这个Receiver,则默认用户会打开主界面,同时接收不到自定义消息。
~~~
package com.fymod.jpush;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Iterator;
import cn.jpush.android.api.JPushInterface;
public class MyReceiver extends BroadcastReceiver {
private static final String TAG = "fymod-jpush";
@Override
public void onReceive(Context context, Intent intent) {
try {
Bundle bundle = intent.getExtras();
Log.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);
Log.d(TAG, "收到了RegistrationId,一般用户连同其他信息发送到服务器中。RegistrationId为: " + regId);
} else if (JPushInterface.ACTION_MESSAGE_RECEIVED.equals(intent.getAction())) {
// 此种(通知栏无效果情况),我们使用主页面查看效果。如果在前端运行,那么就给显示到TextView上
Log.d(TAG, "收到推送的自定义消息: " + bundle.getString(JPushInterface.EXTRA_MESSAGE));
processCustomMessage(context, bundle);
} else if (JPushInterface.ACTION_NOTIFICATION_RECEIVED.equals(intent.getAction())) {
// 通知是有效果的,只知道有通知发送过来了,并没有太大实际用途
int notifactionId = bundle.getInt(JPushInterface.EXTRA_NOTIFICATION_ID);
Log.d(TAG, "收到推送下来的通知的ID: " + notifactionId);
} else if (JPushInterface.ACTION_NOTIFICATION_OPENED.equals(intent.getAction())) {
// 用户点击了同组织,此种情况下是很有用的,可以根据通知不同内容调整到不同的页面
// 比如bundle.getString(JPushInterface.EXTRA_EXTRA)可以获取附件信息数据
Log.d(TAG, "用户点击打开了通知");
Intent i = new Intent(context, MainActivity.class);
i.putExtras(bundle);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP );
context.startActivity(i);
} else if (JPushInterface.ACTION_RICHPUSH_CALLBACK.equals(intent.getAction())) {
//在这里根据 JPushInterface.EXTRA_EXTRA 的内容处理代码,比如打开新的Activity, 打开一个网页等..
Log.d(TAG, "用户收到到富媒体消息,携带参数" + bundle.getString(JPushInterface.EXTRA_EXTRA));
} else if(JPushInterface.ACTION_CONNECTION_CHANGE.equals(intent.getAction())) {
// 网络状态变更
boolean connected = intent.getBooleanExtra(JPushInterface.EXTRA_CONNECTION_CHANGE, false);
Log.w(TAG, "[MyReceiver]" + intent.getAction() +" connected state change to "+connected);
} else {
Log.d(TAG, "其他未分析的类型在这里 " + intent.getAction());
}
} catch (Exception e){
e.printStackTrace();
}
}
// 打印所有的 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))) {
Log.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) {
Log.e(TAG, "Get message extra JSON error!");
}
} else {
sb.append("\nkey:" + key + ", value:" + bundle.get(key));
}
}
return sb.toString();
}
//send msg to MainActivity
private void processCustomMessage(Context context, Bundle bundle) {
// 如果MainActivity在前端运行,就把正在显示的TextView赋值上
if (MainActivity.isForeground) {
String message = bundle.getString(JPushInterface.EXTRA_MESSAGE);
String extras = bundle.getString(JPushInterface.EXTRA_EXTRA);
Intent msgIntent = new Intent(MainApplication.MESSAGE_RECEIVED_ACTION);
msgIntent.putExtra(MainApplication.KEY_MESSAGE, message);
if (extras != null) {
try {
JSONObject extraJson = new JSONObject(extras);
if (extraJson.length() > 0) {
msgIntent.putExtra(MainApplication.KEY_EXTRAS, extras);
}
} catch (JSONException e) {
}
}
LocalBroadcastManager.getInstance(context).sendBroadcast(msgIntent);
}
}
}
~~~
接下来我们看下布局:只有四个按钮(获取RegisterationId、设置标签、添加标签、设置别名)和一个文本内容(用户显示接收到的消息),至于其他诸如清除标签,移除别名等与此类似,不再赘述。
~~~
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.fymod.jpush.MainActivity">
<Button
android:id="@+id/btn_get_registeration_id"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="获取RegisterationId"
android:textAllCaps="false" />
<Button
android:id="@+id/btn_set_tag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="设置标签woman和beijing"
android:textAllCaps="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_add_tag"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="添加标签swim"
android:textAllCaps="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/btn_set_alias"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="设置Alias"
android:textAllCaps="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/txt_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="用来显示推送过来的消息"
android:textAllCaps="false" />
</LinearLayout>
~~~
对应布局的Activity
~~~
package com.fymod.jpush;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import java.util.HashSet;
import java.util.Set;
import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import cn.jpush.android.api.JPushInterface;
public class MainActivity extends AppCompatActivity {
// 标识当前的Activity是否在前端运行
public static boolean isForeground = false;
@BindView(R.id.txt_message)
TextView txtMessage; //用来显示文本消息
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
registerMessageReceiver(); //注册广播
}
@Override
protected void onResume() {
isForeground = true;
super.onResume();
}
@Override
protected void onPause() {
isForeground = false;
super.onPause();
}
@OnClick({R.id.btn_get_registeration_id, R.id.btn_set_tag, R.id.btn_add_tag, R.id.btn_set_alias})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.btn_get_registeration_id:
String rid = JPushInterface.getRegistrationID(getApplicationContext());
if (!rid.isEmpty()) {
Toast.makeText(this, rid, Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "获取失败", Toast.LENGTH_SHORT).show();
}
break;
case R.id.btn_set_tag:
Set<String> tags = new HashSet<>();
tags.add("woman");
tags.add("beijing");
JPushInterface.setTags(this, 0, tags);
break;
case R.id.btn_add_tag:
Set<String> addtags = new HashSet<>();
addtags.add("swim");
JPushInterface.addTags(this, 0, addtags);
break;
case R.id.btn_set_alias:
JPushInterface.setAlias(this, 0, "fymod");
break;
}
}
/**
* 注册广播
*/
public void registerMessageReceiver() {
MessageReceiver mMessageReceiver = new MessageReceiver();
IntentFilter filter = new IntentFilter();
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
filter.addAction(MainApplication.MESSAGE_RECEIVED_ACTION);
LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver, filter);
}
/**
* 广播接收器
*/
public class MessageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
try {
if (MainApplication.MESSAGE_RECEIVED_ACTION.equals(intent.getAction())) {
String messge = intent.getStringExtra(MainApplication.KEY_MESSAGE);
String extras = intent.getStringExtra(MainApplication.KEY_EXTRAS);
StringBuilder showMsg = new StringBuilder();
showMsg.append(MainApplication.KEY_MESSAGE + " : " + messge + "\n");
if (extras != null) {
showMsg.append(MainApplication.KEY_EXTRAS + " : " + extras + "\n");
}
setCostomMsg(showMsg.toString());
}
} catch (Exception e) {
}
}
}
private void setCostomMsg(String msg) {
txtMessage.setText(msg);
}
}
~~~
运行项目,同时打开极光推送网页。
1.点击获取RegisterationId,会将对应数据返回
2.后台给标签woman发送通知,收不到。点击Android的设置标签woman和beijing后,后台给woman发送通知,能收到
3.后台给标签swim发送通知自,收不到,点击Android的添加标签swim后,后台给swim发送通知,能收到
4.后台给别名fymod发送通知,收不到,点击设置Alias后,后台给fymod发送通知,能收到
5.后台使用自定义消息,发送内容,同时可在可选设置中添加附加字段,发送到APP中,TextView的文本会更改为后台设置的数据
对于富媒体消息,目前极光只能通过网页后台发送,可以选择不同的模板或者URL,也可以和上面的一样指定用户发送。用一次就会喜欢上的那种展示效果。
# 服务端SDK
通过极光推送服务端SDK的使用,可以实现后台的绝大部分功能,比如设置安卓展示样式、接收推送的声音等均和实现。以下内容只介绍加单的基本推送,其他情形大体类似,可查询极光文档。
maven引入
~~~
<dependency>
<groupId>cn.jpush.api</groupId>
<artifactId>jpush-client</artifactId>
<version>3.3.7</version>
</dependency>
~~~
整个代码的核心部分就是如何得到推送的PushPayload,有了PushPayload之后就能直接推送。对于发送自定义消息(Message),可以很简单构造出适合的PushPayload;而对于发送通知的情况,因为需要的参数过多,我根据自己的业务场景又重新封装了一个类:
- 新建个实体类,对应发送的所有限制项,比如标题、平台、标签等
- 写个辅助方法,将辅助类转化为PushPayload
- 发送方法接收实体类参数,然后利用辅助方法转化到PushPayload
- 利用PushPayload来发送
最终的测试代码为
~~~
package com.fymod.jpush;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import cn.jiguang.common.ClientConfig;
import cn.jiguang.common.resp.APIConnectionException;
import cn.jiguang.common.resp.APIRequestException;
import cn.jiguang.common.utils.StringUtils;
import cn.jpush.api.JPushClient;
import cn.jpush.api.push.PushResult;
import cn.jpush.api.push.model.Message;
import cn.jpush.api.push.model.Platform;
import cn.jpush.api.push.model.PushPayload;
import cn.jpush.api.push.model.audience.Audience;
import cn.jpush.api.push.model.notification.AndroidNotification;
import cn.jpush.api.push.model.notification.IosNotification;
import cn.jpush.api.push.model.notification.Notification;
public class PushExample {
// 极光后台分配的pApKey和Master Secret
protected static final String APP_KEY = "201dc8c87b057a824b49ac94";
protected static final String MASTER_SECRET = "f6e564a3065727c24ac3bf21";
public static void main(String[] args) {
testSendMessage();
testSendPush();
}
public static void testSendMessage() {
Map<String, String> extras = new HashMap<>();
extras.put("k1", "v1");
extras.put("键2", "值2");
PushPayload payload = PushPayload.newBuilder().setPlatform(Platform.all()).setAudience(Audience.all())
.setMessage(Message.newBuilder().setMsgContent("消息内容").addExtras(extras).build()).build();
ClientConfig clientConfig = ClientConfig.getInstance();
final JPushClient jpushClient = new JPushClient(MASTER_SECRET, APP_KEY, null, clientConfig);
try {
PushResult result = jpushClient.sendPush(payload);
System.out.println(result);
} catch (APIConnectionException e) {
// API连接异常
System.out.println(payload.getSendno());
} catch (APIRequestException e) {
// API请求异常
e.printStackTrace();
}
}
public static void testSendPush() {
PushPayloadParam param = new PushPayloadParam();
// param.setTitle("安卓端的标题");
param.setContent("这是推送显示内容");
final PushPayload payload = buildPushPayloadObject(param);
ClientConfig clientConfig = ClientConfig.getInstance();
final JPushClient jpushClient = new JPushClient(MASTER_SECRET, APP_KEY, null, clientConfig);
try {
PushResult result = jpushClient.sendPush(payload);
System.out.println(result);
} catch (APIConnectionException e) {
// API连接异常
System.out.println(payload.getSendno());
} catch (APIRequestException e) {
// API请求异常
e.printStackTrace();
}
}
/**
* 广播
*/
public static PushPayload buildAllPushObject(String content) {
return PushPayload.alertAll(content);
}
public static PushPayload buildPushPayloadObject(PushPayloadParam param) {
// 安卓端的参数
cn.jpush.api.push.model.notification.AndroidNotification.Builder androidBuilder = AndroidNotification
.newBuilder();
if (!StringUtils.isEmpty(param.getTitle())) {
androidBuilder = androidBuilder.setTitle(param.getTitle());
}
if (param.getExtras() != null) {
androidBuilder = androidBuilder.addExtras(param.getExtras());
}
// iOS端的参数,incrBadge是桌面图片上显示的数量加1,需要iOS在打开APP的时候清零
cn.jpush.api.push.model.notification.IosNotification.Builder iosBuilder = IosNotification.newBuilder()
.incrBadge(1);
if (param.getExtras() != null) {
iosBuilder = iosBuilder.addExtras(param.getExtras());
}
// registrationId 标签和别名
Audience audience = Audience.all();
if (param.getRegistrationIds() != null && param.getRegistrationIds().size() > 0) {
audience = Audience.registrationId(param.getRegistrationIds());
} else if (param.getTags() != null && param.getTags().size() > 0) {
audience = Audience.tag(param.getTags());
} else if (param.getAlias() != null && param.getAlias().size() > 0) {
audience = Audience.alias(param.getAlias());
}
return PushPayload.newBuilder().setPlatform(param.getPlatform()).setAudience(audience)
.setNotification(Notification.newBuilder().setAlert(param.getContent())
.addPlatformNotification(androidBuilder.build()).addPlatformNotification(iosBuilder.build())
.build())
.build();
}
}
class PushPayloadParam {
private String title; // 标题,仅安卓端有效
private String content; // 通知栏显示的内容
private Map<String, String> extras; // 附加数据,用于业务场景
private Platform platform = Platform.all(); // 平台,默认全部,可以单独指定某个或某几个,比如指定安卓和iOS
private Collection<String> registrationIds;
private Collection<String> alias; // 别名
private Collection<String> tags; // 标签
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Map<String, String> getExtras() {
return extras;
}
public void setExtras(Map<String, String> extras) {
this.extras = extras;
}
public Platform getPlatform() {
return platform;
}
public void setPlatform(Platform platform) {
this.platform = platform;
}
public Collection<String> getAlias() {
return alias;
}
public void setAlias(Collection<String> alias) {
this.alias = alias;
}
public Collection<String> getTags() {
return tags;
}
public void setTags(Collection<String> tags) {
this.tags = tags;
}
public Collection<String> getRegistrationIds() {
return registrationIds;
}
public void setRegistrationIds(Collection<String> registrationIds) {
this.registrationIds = registrationIds;
}
}
~~~