🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
# 概述 市面上的推送平台比较多,比如极光推送、力推、友盟推送、信鸽推送等,相比较而言,极光推送还处于非常不错的位置。 # 账号和概念 登录网站<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; } } ~~~