极光推送客户端和服务器端开发

现在大不分应用都有推送的功能,今天总结下最近用到的极光推动的android端和服务器端开发过程。

1.1推送的原理

分服务器和客户端分别进行说明:

1.推送框架图

极光推送客户端和服务器端开发_第1张图片
jpush.png
  • 推送的数据源:自己开发的服务器端或者使用极光推送官网的WEB后台(++用于客户自定义服务器向推送框架进行推送++)
  • JPush API:部署在服务器端,开发者的服务器端发起推送时,将数据传到JPush API中,然后向下传递
  • 建立长链接:集成JPush的SDK客户端启动后会建立一个到JPush Cloud的长链接(++手机客户端收到推送消息++)

1.2客户端原理

客户端需要和服务器保存长连接状态。jpush SDK提供了TCP Long Connection。实现的原理:

  • 心跳:为了长时间保持外网IP,需要客户端定期发送心跳给运营商,以便刷新NAT列表
  • Timer定时方法:该类计划循环执行定时任务,但是使用该类会使CPU保持唤醒状态,比较费电。
  • AlarmManager定时方法:该类封装了Android手机的RTC硬件时钟模块,可以在CPU休眠时正常运行,保持任务执行时再唤醒CPU,这样做到了电量节省。

2.实现步骤

2.1注册极光推送

极光推送网址:https://www.jiguang.cn
进行自行注册,然后进入控制台,添加自己的应用。在应用设置中就能看到自己的AppKey的值,这个是此用于的唯一标示符,在客户端开发和服务器端开发中都会用到此值。

2.2集成android客户端

极光推送客户端和服务器端开发_第2张图片
集成android.png

按照上图的提示进行炒作即可。
下面在android studio中介绍下集成流程:

  1. builde.gradle中添加引用,即导入依赖包
compile 'cn.jiguang.sdk:jpush:3.0.0'
compile 'cn.jiguang.sdk:jcore:1.0.0'

2.添加统计信息,也可以不使用

@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);  // 设置开启日志,发布时请关闭日志
        JPushInterface.init(this);          // 初始化 JPush
    }

}

4.在AndroidMainfest.xml添加

    
    

    
    
    
    
    
    
    
    
    
    
    
    
    
 
  
        
            
                

                
                
            
        
        
        

        
        
        
            
                
                
                
                
            
        
        
        
            
                 
                
            
            
                
                
            
            
            
                
                

                
            
        

        
        
        
        
            
                 
                 
                 
                 
                 
                
            
        
        
        
        

5.自定义MyReceiver

/**
 * 自定义接收器
 *
 * 如果不定义这个 Receiver,则:
 * 1) 默认用户会打开主界面
 * 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] 接收到推送下来的自定义消息: " + bundle.getString(JPushInterface.EXTRA_MESSAGE));
            showNotification(context,bundle);

        } else if (JPushInterface.ACTION_NOTIFICATION_RECEIVED.equals(intent.getAction())) {
            MyLog.i("[MyReceiver] 接收到推送下来的通知");
            int notifactionId = bundle.getInt(JPushInterface.EXTRA_NOTIFICATION_ID);
            MyLog.i("[MyReceiver] 接收到推送下来的通知的ID: " + notifactionId);
            receivingNotification(context,bundle);


        } else if (JPushInterface.ACTION_NOTIFICATION_OPENED.equals(intent.getAction())) {
            MyLog.i("[MyReceiver] 用户点击打开了通知");
            openNotification(context,bundle);
        } else if (JPushInterface.ACTION_RICHPUSH_CALLBACK.equals(intent.getAction())) {
            MyLog.i("[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);
            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;
        // 指定最顶层状态栏小图标
        builder.layoutIconDrawable = R.drawable.arrow_up;
        // 指定下拉状态栏时显示的通知图标
        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 数据
    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 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:

  • 通知信息和自定义信息的区别是:通知会在通知栏中显示,自定义需要自己出来,可以自定义显示。
  • 通知状态栏也可以自行定义。
  • showNotification可以自行定义通知框的显示。但是需要后台服务器中下发对应的字段进行控制。这里使用了一个开源通知库: https://github.com/wenmingvs/NotifyUtil。 但是小米mui8手机上通知信息达不到效果。在其他的手机上还是有效果。

6.定向推送

在开发中为了定向推送到指定的手机上,所以需要在客户端中绑定相应的别名,这样就可以定向推送到指定的手机上了。因为客户端和服务器端都是自己开发,可以通过用户名做为别名进行绑定。

  // 调用 Handler 来异步设置别名
  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 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 秒来调用 Handler 设置别名
                    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.");
                    // 调用 JPush 接口来设置别名。
                    JPushInterface.setAliasAndTags(getApplicationContext(),
                            (String) msg.obj,
                            null,
                            mAliasCallback);
                    break;
                default:
                    MyLog.i( "Unhandled msg - " + msg.what);
            }
        }
    };

这里使用SharedPreferences

public class SpUtils
{
    /**
     * 保存在手机里面的文件名
     */
    public static final String FILE_NAME = "share_data";

    /**
     * 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法
     *
     * @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);
    }

    /**
     * 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值
     *
     * @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;
    }

    /**
     * 移除某个key值已经对应的值
     * @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);
    }

    /**
     * 清除所有数据
     * @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);
    }

    /**
     * 查询某个key是否已经存在
     * @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);
    }

    /**
     * 返回所有的键值对
     *
     * @param context
     * @return
     */
    public static Map getAll(Context context)
    {
        SharedPreferences sp = context.getSharedPreferences(FILE_NAME,
                Context.MODE_PRIVATE);
        return sp.getAll();
    }

    /**
     * 创建一个解决SharedPreferencesCompat.apply方法的一个兼容类
     *
     * @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执行,否则使用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客户端集成结束。

2.3服务端集成

  • 1.这里我使用的是java服务器端。源码地址为: https://github.com/jpush/jpush-api-java-client
  • 2.这里还需要依赖一个公共包。源码: https://github.com/jpush/jiguang-java-client-common
  • 3.如果只是使用可以直接下载jar包即可 https://github.com/jpush/jpush-api-java-client/releases

引入到现有工程中。把1中的jar和测试文件到入到工程中,把3中的jar文件导入到工程中。

极光推送客户端和服务器端开发_第3张图片
jpush_web.png

详细的使用可以参考1中的介绍。我这只对发通知和消息和指定对象推送进行测试

public class Test {
    protected static final Logger LOG = LoggerFactory.getLogger(Test.class);
    public static final String ALERT = "数据";

    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 extras = new HashMap();
        extras.put("Notificaction", "0");
        extras.put("URL", "www.baidu.com");

        Message.content("数据");
        PushPayload payload = PushPayload.newBuilder()
                   .setPlatform(Platform.android())
                   .setAudience(Audience.alias("0123456789"))
                   .setOptions(Options.sendno())
                   .setNotification(Notification.android("一个惊喜", "号外", extras))
                   //.setMessage(Message.newBuilder().setMsgContent("").addExtra("Notificaction", "0").build())
//                   .setMessage(
//                        Message.newBuilder().setMsgContent("数据").addExtra("Notificaction", "7").setTitle("大爷的")
//                                .build())
                   .build();
                   
                   
        return payload;
        
    }

}

到处服务端集成成功。

你可能感兴趣的:(极光推送客户端和服务器端开发)