基于信鸽设计一个微型推送服务

本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

前言

App的推送服务是一个较为重要的功能,适度的消息推送可以提高用户活跃度和留存率,但过度的推送也会令用户感到反感。iOS平台的App可以使用苹果公司提供的APNS进行消息推送,Android平台的App理论上可以使用谷歌公司提供的FCM,但实际上只能走第三方推送平台,终端厂商通道,或自建推送通道。本篇文章讲解的消息推送教程,所使用的免费推送平台是腾讯移动推送——信鸽,就是使用Spring Boot + 信鸽服务端SDK + 信鸽Android SDK来设计一个微型的推送服务。

阅读建议

为了让读者更好地阅读本文,希望读者们有如下的知识基础 :

  • 有Java Web的基础。
  • 对Spring框架有所了解
  • 对计算机网络的知识有所了解
  • 若以上三点不太熟识的朋友,可以阅读我写的上一篇文章后再阅读本章。
    《简易服务器接口开发》

开发前准备

一、去腾讯信鸽官网注册一个账号,填写好基本资料。信鸽官网链接:https://xg.qq.com

二、在应用管理里面创建一个新应用。

基于信鸽设计一个微型推送服务_第1张图片

三、在选择平台中选择"Google Android",然后点击“下一步”。

基于信鸽设计一个微型推送服务_第2张图片

四、填写好配置,点击“下一步”

基于信鸽设计一个微型推送服务_第3张图片

五、完成了应用创建,可以看到信鸽给你提供的APP ID、SECRET KEY、ACCESS ID和ACCESS KEY,这些ID和KEY只需按信鸽的文档提示配置到项目中即可。然后点“完成配置”。

基于信鸽设计一个微型推送服务_第4张图片

六、然后在官网中找到下载SDK的入口,把服务端JAVA SDK下载下来,准备工作已完成。服务端的SDK下载链接如下:

https://xg.qq.com/docs/server_api/other.html

开发计划

任务:开发一个带有账号体系的Android应用,然后每个账号绑定信鸽的推送服务,当用户A需要给用户B发送一条留言信息,用户A把相关的数据发送到自建的服务端后台,后台再调用信鸽后台API把数据传到信鸽服务器,信鸽服务器再把消息推送到用户B的账号上,当用户B打开app,登录他的账号,就会收到来自用户A的消息。简单来说就是设计一个简易的留言工具。

服务端开发

一、启动 IDEA,创建一个Spring Boot项目,在出现下图的界面中勾选"web",然后一直点击"Next",填写项目名称和项目保存的路径,最后点击"Finish"。

基于信鸽设计一个微型推送服务_第5张图片

二、在项目的根目录下创建一个lib目录,然后把从信鸽官网下载的服务端SDK放到lib目录里面,再右键点击该jar包,在弹出的菜单中选择"Add as Library"选项,导入信鸽服务端SDK完成。
基于信鸽设计一个微型推送服务_第6张图片

三、创建一个名称为PushService的类,该类用于设置推送的消息进行消息的推送。PushService类的accessId和secretKey在下面的代码中不再给出,请读者自行去信鸽官网上获取。本章使用到信鸽SDK提供的Message类和XingeApp类中相关的方法请阅读代码中的注释,同时建议阅读信鸽官方提供的Java服务端的文档。PushService类的代码如下:

package com.example.demo;

import com.tencent.xinge.Message;
import com.tencent.xinge.XingeApp;
import org.json.JSONObject;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

class PushService {

    //填写你信鸽提供给你的accessId
    private final static long ACCESS_ID = 0;

    //填写你信鸽提供给你的secretKey
    private final static String SECRET_KEY = "";

    /**
     * 调用信鸽后台API,让信鸽把消息推送到移动端。
     * @param sender
     * @param receiver
     * @param content
     */
    public static String push(String sender, String receiver, String content) {
        //设置消息
        Message message = new Message();
        //设置推送消息的类型
        message.setType(Message.TYPE_MESSAGE);
        //设置推送消息的标题。
        message.setTitle("来自 " + sender + " 的留言");
        //创建Map对象
        Map map = new HashMap<>();
        map.put("content", content);    //消息的内容
        map.put("date", new Date());    //该条消息的时间
        message.setCustom(map);         //设置自定义的key-value

        //推送消息
        XingeApp xingeApp = new XingeApp(ACCESS_ID, SECRET_KEY);
        //调用推送消息给单个账号的方法
        //参数一填0即可,参数二为推送目标账号,参数三为消息内容
        //返回值为推送结果
        JSONObject result = xingeApp.pushSingleAccount(0, receiver, message);

        return String.valueOf(result);
    }
}

四、创建一个名称为DataPack的实体类,该类用于获取移动端上传的相关数据,后台再把这些数据后在传入到信鸽后台进行推送。DataPack类的代码如下:

package com.example.demo;

public class DataPack {
    //发送者
    private String sender;
    //接收者
    private String receiver;
    //内容
    private String content;

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getSender() {
        return sender;
    }

    public void setReceiver(String receiver) {
        this.receiver = receiver;
    }

    public String getReceiver() {
        return receiver;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }
}

五、最后创建一个名称为ApiController的类,然后设计一个Web接口,接口地址为http:// ip address : 8080/api/mailbox。假设使用用户A需要给用户B留言,这时用户A留言内容会先上传到后台,后台把留言的内容交给信鸽,信鸽再把留言的内容推送到用户B的移动设备上。代码中@PostMapping注解里面的produces参数是指该方法返回值格式为json,编码为UTF-8。

package com.example.demo;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/api")
public class ApiController {

    @PostMapping(value = "/mailbox", produces = "application/json;charset=UTF-8")
    public String mailbox(DataPack pack) {
        return PushService.push(pack.getSender(), pack.getReceiver(), pack.getContent());
    }

}

六、服务端的代码已完成,项目中所编写的类只有三个。项目的文件目录如下图红框所示,为了简单直观起见,所有的类直接放在demo目录里面,不再分类。

基于信鸽设计一个微型推送服务_第7张图片

七、在IDEA中启动服务端程序,进行接口测试,测试结果如下图所示,信鸽后台返回错误码48,意思是推送的账号不存在,说明接口调用过程还是成功的,下面准备开始进行客户端的设计。说明一下,测试接口的工具返回值出现乱码,不是后台的问题,是测试工具的问题,所以不用慌。
基于信鸽设计一个微型推送服务_第8张图片

客户端开发

服务端的功能已经写好了,现在开始编辑客户端的功能,让其和后台进行对接,开发前建议先阅读信鸽的Android SDK开发文档再着手开发。

一、新建一个项目,然后配置app模块的build.gradle文件,需要填写哪些内容,请阅读下面源码的注释,也可以阅读信鸽官网的文档。如果在添加以上 abiFilter 配置之后 Android Studio 出现以下提示:"NDK integration is deprecated in the current plugin. Consider trying the new experimental plugin.",则在 Project 根目录的 gradle.properties 文件中添加:android.useDeprecatedNdk = true

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        //信鸽官网上注册的包名.注意application ID 和当前的应用包名以及 信鸽官网上注册应用的包名必须一致。
        applicationId "com.example.demo"
        minSdkVersion 23
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"

        //根据需要 自行选择添加的对应cpu类型的.so库。
        ndk {
            abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a'
            // 还可以添加 'x86', 'x86_64', 'mips', 'mips64'
        }

        //填写accessid和和accesskey
        manifestPlaceholders = [
                XG_ACCESS_ID:"注册应用的accessid",
                XG_ACCESS_KEY:"注册应用的accesskey",
        ]
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'

    //信鸽普通版本jar,不包含厂商通道
    implementation  'com.tencent.xinge:xinge:4.0.5-release'
    //jg包
    implementation'com.tencent.jg:jg:1.1'
    //wup包
    implementation 'com.tencent.wup:wup:1.0.0.E-release'
    //mid包,minSdkVersion 14
    implementation 'com.tencent.mid:mid:4.0.7-Release'

    //引入OkGo框架进行后台数据请求
    implementation 'com.lzy.net:okgo:3.0.4'
    //EventBus用于广播与活动之间的通信
    implementation 'org.greenrobot:eventbus:3.1.1'
    //解析json数据
    implementation 'com.google.code.gson:gson:2.8.5'
}

二、配置Androidmanifest.xml,信鸽文档中对Androidmanifest.xml配置的内容分为【必须】、【可选】,【常用】三种类型,而本项目示例只提取信鸽文档中比较重要的部分内容进行配置,请细心阅读下面的源码注释部分,需要填写包名,id,key这些内容以你实际创建的项目为准。




    
    
    
    
    
    
    

    
        
        
            
                
                
            
        
        
        

        
        
            
                
                
                
                
                
                
                
                
                
                
            
        

        
        

        
        
            
                
                
            
        

        
        

        
        

        
        

        
        

        
        

    


三、设计两个布局界面,第一个为登录界面,用于设置自己的ID帐号,第二个为主界面,用于给对方发送消息和展现对方的消息。效果图如下图所示。

基于信鸽设计一个微型推送服务_第9张图片

四、编写登录界面的LoginActivity,调用信鸽Android SDK中XGPushManager类的bindAccount方法进行绑定帐号注册,帐号可以是邮箱、QQ号、手机号、用户名等任意类别的业务帐号。理论上来说,app每次启动都应该调用该方法进行帐号绑定,绑定成功后,信鸽服务器会自动把与该帐号有关的消息都推送到移动端上。

package com.example.demo;

import android.app.ProgressDialog;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;

import com.tencent.android.tpush.XGIOperateCallback;
import com.tencent.android.tpush.XGPushManager;

public class LoginActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        initView();
    }

    /**
     * 初始化布局
     */
    private void initView() {
        final EditText activity_login_id_et = findViewById(R.id.activity_login_id_et);

        //确认按钮
        findViewById(R.id.activity_login_confirm_btn)
                .setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        String id = activity_login_id_et.getText().toString();
                        if (TextUtils.isEmpty(id)) {
                            Utils.showToast(LoginActivity.this, "ID 不能为空");
                        }else {
                            setAccount(id);
                        }
                    }
                });
    }

    /**
     * 信鸽绑定账号注册
     * @param id
     */
    private void setAccount(final String id) {
        String message = "设置ID中,请稍后...";
        Utils.showProgressDialog(this, message, new Utils.OnRunningListener() {
            @Override
            public void onRunning(final ProgressDialog dialog) {
                //id账号绑定信鸽推送服务
                XGPushManager.bindAccount(LoginActivity.this, id, new XGIOperateCallback() {
                    @Override
                    public void onSuccess(Object o, int i) {
                        dialog.dismiss();
                        Intent intent = new Intent(LoginActivity.this, MainActivity.class);
                        intent.putExtra("id", id);
                        startActivity(intent);
                        finish();
                    }

                    @Override
                    public void onFail(Object o, int i, String s) {
                        dialog.dismiss();
                        Utils.showToast(LoginActivity.this, "错误信息:" + s);
                    }
                });
            }
        });
    }
}

五、创建一个名称为MessageEvent的事件类,该类配合EventBus一起使用,当应用接收到信鸽推送的广播后,然后通过EventBus把信鸽的消息从广播传到活动里。

package com.example.demo;

public class MessageEvent {

    private String sender;
    private String content;
    private String date;

    public MessageEvent(String sender, String content, String date) {
        this.sender = sender;
        this.content = content;
        this.date = date;
    }

    public String getSender() {
        return sender;
    }

    public String getContent() {
        return content;
    }

    public String getDate() {
        return date;
    }
}

六、创建一个名称为PushReceiver的类,该类继承信鸽Android SDK中提供的XGPushBaseReceiver类,并重载相关的方法。当信鸽推送消息到应用上,PushReceiver广播类就会收到相关的消息,开发者只需在PushReceiver类重载的方法中写处理消息的逻辑即可。这些方法的功能和PushReceiver类的代码如下:

基于信鸽设计一个微型推送服务_第10张图片

package com.example.demo;

import android.content.Context;

import com.google.gson.Gson;
import com.tencent.android.tpush.XGPushBaseReceiver;
import com.tencent.android.tpush.XGPushClickedResult;
import com.tencent.android.tpush.XGPushRegisterResult;
import com.tencent.android.tpush.XGPushShowedResult;
import com.tencent.android.tpush.XGPushTextMessage;

import org.greenrobot.eventbus.EventBus;

import java.util.HashMap;
import java.util.Map;

public class PushReceiver extends XGPushBaseReceiver {

    @Override
    public void onRegisterResult(Context context, int i, XGPushRegisterResult xgPushRegisterResult) {

    }

    @Override
    public void onUnregisterResult(Context context, int i) {

    }

    @Override
    public void onSetTagResult(Context context, int i, String s) {

    }

    @Override
    public void onDeleteTagResult(Context context, int i, String s) {

    }

    @Override
    public void onTextMessage(Context context, XGPushTextMessage xgPushTextMessage) {
        //获取信鸽推送的消息
        String title = xgPushTextMessage.getTitle();
        String custom = xgPushTextMessage.getCustomContent();

        //解析消息中的相关内容
        Gson gson = new Gson();
        Map map = new HashMap<>();
        map = gson.fromJson(custom, map.getClass());
        String content = (String) map.get("content");
        String date = (String) map.get("date");

        //把消息的内容从广播传到活动中
        MessageEvent event = new MessageEvent(title, content, date);
        EventBus.getDefault().post(event);
    }

    @Override
    public void onNotifactionClickedResult(Context context, XGPushClickedResult xgPushClickedResult) {

    }

    @Override
    public void onNotifactionShowedResult(Context context, XGPushShowedResult xgPushShowedResult) {

    }
}

七、PushReceiver广播类写好后,然后在AndroidManifest.xml文件中...层级进行广播的静态注册,广播类的名称以你实际项目中创建的类为主,代码如下:





    
    
    
    


八、编写MainActivity类的功能,在onCreat方法中进行初始化布局和EventBus的事件注册。在onDestroy方法中进行EventBus的事件注销。onEvent方法用于响应并展示留言消息到文本框中。delivery方法功能用于调用后台接口,把相关数据交给后台,后台再转发给信鸽进行推送给对方。onResume方法用于每次回到主界面时绑定一次帐号,拉起被系统杀死的信鸽服务(我使用的是小米6,每次后台挂起Demo,去看个新闻回来,发现信鸽服务被系统杀了,所以只能每次回到应用主界面一次就拉起信鸽服务一次)。常量URL是后台服务器的接口地址。

package com.example.demo;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

import com.lzy.okgo.OkGo;
import com.lzy.okgo.callback.StringCallback;
import com.lzy.okgo.model.Response;
import com.tencent.android.tpush.XGPushManager;

import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;

public class MainActivity extends AppCompatActivity {

    private final static String URL = "http://192.168.43.188:8080/api/mailbox";

    private EditText activity_main_id_et;
    private EditText activity_main_content_et;
    private TextView activity_main_message_tv;

    private String myId;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        //注册事件
        EventBus.getDefault().register(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        //每次回到主界面时绑定一次帐号,拉起被系统杀死的信鸽服务
        XGPushManager.bindAccount(this, myId);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //解除注册
        if(EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().unregister(this);
        }
    }

    @Subscribe(threadMode = ThreadMode.POSTING)
    public void onEvent(MessageEvent event) {
        String text = event.getSender()
                + "\n\n留言内容:"
                + event.getContent()
                + "\n留言时间:"
                + event.getDate();
        activity_main_message_tv.setText(text);
    }

    /**
     * 初始化布局
     */
    private void initView() {
        myId = getIntent().getStringExtra("id");

        activity_main_id_et = findViewById(R.id.activity_main_id_et);
        activity_main_content_et = findViewById(R.id.activity_main_content_et);
        activity_main_message_tv = findViewById(R.id.activity_main_message_tv);

        findViewById(R.id.activity_main_send_btn)
                .setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        String userId = activity_main_id_et.getText().toString();
                        String content = activity_main_content_et.getText().toString();

                        if (TextUtils.isEmpty(userId) || TextUtils.isEmpty(content)) {
                            Utils.showToast(MainActivity.this, "对方的ID或内容不能为空");
                        }else {
                            delivery(myId, userId, content);
                        }
                    }
                });
    }

    /**
     * 调用后台接口,把相关数据交给后台,后台再转发给信鸽进行推送给对方。
     * @param sender
     * @param receiver
     * @param content
     */
    private void delivery(String sender, String receiver, String content) {
        OkGo.post(URL)
                .params("sender", sender)
                .params("receiver", receiver)
                .params("content", content)
                .execute(new StringCallback() {
                    @Override
                    public void onSuccess(Response response) {
                        String str = response.body();
                        if (str.equals("{\"ret_code\":0}")) {
                            Utils.showToast(MainActivity.this, "发送成功");
                            activity_main_content_et.setText("");
                        }else {
                            Utils.showToast(MainActivity.this, "发送失败:" + str);
                        }
                    }
                });
    }
}

九、保证电脑和手机连接到同一个WiFi,启动服务端的程序,再启动移动端的Demo应用,进行消息推送测试。先测试一下自己给自己的ID推送一条留言消息,测试成功!效果图如下:

基于信鸽设计一个微型推送服务_第11张图片

把服务端程序部署到服务器

整个推送服务开发已完成,现在把服务端的项目打包,然后部署到Linux云服务器上。若没有云服务器的朋友可以跳过该部分,本章内容算差不多完成了。

一、在IDEA中,选择菜单栏的"File" -> "Project Structure",出现如下界面,然后按图示的步骤操作。

基于信鸽设计一个微型推送服务_第12张图片

二、然后出现一个"Create JAR from Modules"的界面,选择好相关的内容,注意第三个红框中划红线的部分,然后点击"OK"。
基于信鸽设计一个微型推送服务_第13张图片

三、上一步完成后,会出现如下界面,然后点击"OK"即可。
基于信鸽设计一个微型推送服务_第14张图片

四、在IDEA主菜单栏中,选择"Build" -> "Build Artifacts",如图所示:
基于信鸽设计一个微型推送服务_第15张图片

五、弹出这个小框时,选择"Build"即可编译整个项目。编译完成后,可以看到项目中多了一个out目录,把它里面的Demo_jar目录复制出来部署到服务器上即可。

基于信鸽设计一个微型推送服务_第16张图片

六、我使用的是阿里云服务器,打开阿里云App,然后在里面添加安全组规则,开放8080端口。再到Linux系统的防火墙里,放行8080端口(我使用宝塔面板进行操作)。

基于信鸽设计一个微型推送服务_第17张图片

基于信鸽设计一个微型推送服务_第18张图片

七、把刚刚打包的Demo_jar目录从Windows系统上传到Linux系统的home目录中,然后在终端窗口通过命令启动服务端的程序,若服务器没什么意外的问题,该推送服务程序就可以24小时不间断地运行。Linux命令如下:
cd —— 切换目录
nohup java -jar xxx.jar & —— 让Java的程序后台运行
ps -ef | grep java —— 查看正在运行的Java进程
kill pid —— 杀死进程号为该pid的进程,例如杀死推送服务的程序用kill 23911。

基于信鸽设计一个微型推送服务_第19张图片

八、把Android项目中该行URL的私网IP改为服务器的公网IP,若公网IP已绑定了一个域名,可以使用域名代替IP地址部分,然后把Android端的项目编译成apk安装到手机上即可。

九、真机测试与后台统计,我手上有两台真机,现在使用真机测试一下,我手上的小米6启动应用,然后在登录界面设置一个ID,该ID注册了信鸽服务成功后会跳转到应用的主界面。

基于信鸽设计一个微型推送服务_第20张图片

使用小米6给使用小米5c的ID帐号发送一条留言信息。当小米5c收到来自小米6的留言信息时,小米5c再给对方回复一条留言。因为本示例服务端的代码设置了推送的消息类型是应用内消息,所以移动端收到推送时是不会出现在通知栏的,若要显示通知栏消息,可以在后台代码中修改成相应的消息类型
基于信鸽设计一个微型推送服务_第21张图片

基于信鸽设计一个微型推送服务_第22张图片

本文示例中使用的是单个帐号推送,消息类型是应用内消息,创建的推送方法是API调用,在信鸽的操控台上可以看到相关的统计数据。
基于信鸽设计一个微型推送服务_第23张图片

总结

本文关于如何设计一个微型推送服务已写完,通过开发一个简单留言功能来了解推送的服务端和移动端的开发过程,希望读者看完有所收获。推送服务的其他应用场景,例如:好友的点赞通知,好友的评论通知,定时提醒通知,活动提醒通知等。本文仅供学习参考,因篇幅有限,所以关于安卓的进程保活,信鸽的厂商推送通道设置,通知栏提醒设置等内容就不能详写了,但信鸽的文档也有详细的说明。本文项目示例已放到Github上,只需到信鸽后台创建一个应用,再按Github上的文档操作,最后编译出来即可使用。Github的仓库地址:https://github.com/mailhu/pigeon

想了解更多关于网络和推送的知识,个人推荐学习以下内容,仅个人推荐,不是广告啦!在微信阅读中搜索书籍《计算机网络》——作者:谢希仁;慕课网搜索视频《见证Android消息推送时刻》——讲师:郭霖

感谢阅读

你可能感兴趣的:(基于信鸽设计一个微型推送服务)