混合移动应用消息推送
混合应用一般使用 Cordova 之类的中间件,以 WebView 作为用户界面层,以 Javascript
作为基本逻辑,以及和中间件通讯,再由中间件访问底层 API 的方式,进行应用开发。开发时可能不采用或者大部分不采用原生语言,但是却有所有原生应用的特性。而开发消息推送功能时,我们就既可以使用
Native App 的系统自带推送如 GCM 和 APNS,又可以使用基于 html5 的 websocket
推送。
对于混合应用的 websocket 消息推送,其基本原理如下:
图 1. Hybrid app websocket
工作流程
而原生应用的消息推送,其基本原理如下图:
图 2. 原生 app 消息推送工作流程
目前后者主要应用于原生 app, 而前者由于开发周期短,跨平台性好,维护成本低,一般可以用于混合应用的消息推送。基于
websocket 的消息推送,我们就可以自己去实现消息推送的服务端,这样我们就掌握了推送服务的主动权,对于安全性极高的企业,websocket
推送无疑是最好的选择,因为如果使用 GCM 或者 APNS 推送,我们不得不将信息发送到 GCM server
或者 APNS server, 再由 GCM 或者 APNS 服务端转发到客户端,信息安全性不得而知。一旦推送服务器出现异常,我们的消息推送将变得非常被动。
但是 GCM 和 APNS 也是使用长连接进行消息推送,而且一个手机上的所有 app 共用一个长连接,对于手机性能将会有极大的帮助。两种推送各有利弊,读者可自选选择。本文将针对
websocket 的消息推送进行一系列介绍。
Websocket 接口简介
WebSocket 的实现分为客户端和服务端两部分,客户端(通常为浏览器)发出 WebSocket 连接请求,服务端响应,实现类似
TCP 握手的动作,从而在浏览器客户端和 WebSocket 服务端之间形成一条 HTTP 长连接快速通道。两者之间后续进行直接的数据互相传送,不再需要发起连接和响应。同时两者都可以关闭这个长连接。我们正是利用了混合移动应用的
webview 可以支持 websocket 的这个特性来实现服务器端对客户端的一个消息推送。Websocket
针对客户端而言,性能,资源使用以及及时性要比传统的轮询更好。
Websocket 客户端 API
对于 websocket 客户端,目前主流的移动操作系统的 webview 层都已经支持 websocket
服务。以下列举了常见的移动系统支持情况:
表 1. 主流移动系统的 webview 对 websocket 的支持情况
常见浏览器和移动系统的 webview 都已经实现了 w3 规范的 websocket 接口,具体接口参考清单
1
清单 1. Websocket 客户端接口
[Constructor(in DOMString url, in optional DOMString protocol)] interface WebSocket { readonly attribute DOMString URL; // ready state const unsigned short CONNECTING = 0; const unsigned short OPEN = 1; const unsigned short CLOSED = 2; readonly attribute unsigned short readyState; readonly attribute unsigned long bufferedAmount; //networking attribute Function onopen; attribute Function onmessage; attribute Function onclose; boolean send(in DOMString data); void close(); }; WebSocket implements EventTarget; |
其中 URL 属性代表 WebSocket 服务器的网络地址,协议通常”ws”或者”wss“,send
方法就是发送数据到服务器端,close 方法就是关闭连接。除了这些方法,还有一些很重要的事件:onopen,onmessage,onerror
以及 onclose。详细解释请参考表 2
表 2. Websocket 的对象方法属性
下面一段代码展示了建立 websocket 实例:
清单 2. Websocket 创建连接实例
var ws= new WebSocket("ws://localhost:8080/PushNotification"); ws.onopen = function (event) { console.log("connected to server"); }; ws.onmessage = function (event) { //when a new message coming, we will call Cordova plugin here }; |
当有新消息到达时,onmessage 会自动触发,我们可以在这个方法里利用 Cordova plugin
去实现调用 android 或者 ios 的 notification。
Websocket 服务器端 API
对于 websocket 服务器端,目前主流的 web 服务器都已经支持。以下列举了常见的服务器支持情况:
表 3. 主流 web 服务器对 websocket 的支持情况
以下我们使用 websphere liberty 8.5.5.5 作为 web 服务器,websocket
功能需要另外安装,步骤如下:
1.安装 websocket,执行以下命令
bin/featureManager install websocket-1.0 --when-file-exists=ignore |
2.在 server.xml 中引入如下配置
<featureManager> <feature>websocket-1.0</feature> </featureManager> |
3.重启 websphere liberty 即可生效
WebSocket 服务端的代码示例如下:
清单 3. Websocket 服务器端接口
@ServerEndpoint(value = "/PushNotification") public class PushNotifyWithWebSocket { private static Set<Session> sessions = Collections.newSetFromMap(new ConcurrentHashMap<Session,Boolean>()); @OnMessage public void receiveMessage(String message) { //todo } @OnOpen public void onOpen(Session session, EndpointConfig ec) { sessions.add(currentSession); } @OnClose public void onClose(Session session, CloseReason reason) { //todo } @OnError public void onError(Throwable t) { //todo } /** * Send a message to a all client * @param message */ public void sendMessage(JSONArray message) throws Exception { ObjectMapper mapper = new ObjectMapper(); //send message to all online user for (Session session: sessions){ session.getBasicRemote().sendText(message. toString()); } } } |
使用 ServerEndpoint 注释的类必须有一个公共的无参数构造函数,@onMessage 注解的
Java 方法用于接收传入的 WebSocket 信息,这个信息可以是文本格式,也可以是二进制格式。
OnOpen 在这个端点一个新的连接建立时被调用。参数提供了连接的另一端的更多细节。Session 表明两个
WebSocket 端点对话连接的另一端,可以理解为类似 HTTPSession 的概念,我们可以将多个
Session 保存到 server 端,以便我们与客户端通信。OnClose 在连接被终止时调用。参数
closeReason 可封装更多细节,如为什么一个 WebSocket 连接关闭。OnError 在
websocket 通信出现异常才会调用。
在清单 3 中我们可以看出,如果我们有新消息需要推送到客户端,我们就可以调 sendMessage 方法,一旦客户端收到
message 就可以通过 Cordova plugin 调用系统 API 来实现消息提示。下文将着重讲解如何创建一个
Cordova plugin。
利用 Cordova plugin 调用本地 notification
以上我们了解了如何在 Hybrid App 中创建 websocket 连接,当我们收到信息时,这个时候只需要调用
IOS 或者 Android 的本地消息推送即可让用户知道有新的信息到达。这个时候我们就要创建一个新的
Cordova plugin 去触发 notification。
创建一个 Cordova plugin
Cordova plugin 是我们通过 JavaScript 调用系统 API 的中间件,一般情况下我们通过
JavaScript 不能够完成而系统 API 可以完成的任务时,我们就要创建一个 plugin。不同的平台在调用底层
API 时会有不同,但是前段代码不会改变。 以下我们将创建一个 Cordova plugin 用来调用本地消息推送。下文以
android 平台为例,其他平台思路类似。
1.在 cordova_plugins.js 中引入新的 plugin
{ "file": "plugins/com.test.notification/www/notification.js", "id": "com.test.notification.localNotification", "clobbers": [ "cordova.plugins.localNotification" ] } |
2.然后在 plugins/com.test.notification/www
目录下边创建 notification.js,代码示例如下:
cordova.define("com.test.notification.localNotification", function(require, exports, module) { var argscheck = require('cordova/argscheck'), utils = require('cordova/utils'), exec = require('cordova/exec'); var localNotification = function() { }; localNotification.sendNotify = function(message,success, error) { cordova.exec(success, error, 'localNotification', 'sendNotify', message); }; module.exports = localNotification; }); |
3. 创建一个 java 类来继承 Cordova 接口,代码示例如下
package com.test.notification; import org.apache.cordova.CallbackContext; import org.apache.cordova.CordovaInterface; import org.apache.cordova.CordovaPlugin; import org.apache.cordova.CordovaWebView; import org.apache.cordova.PluginResult; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject;
//ellipsis code
// …… .
public class localNotification extends CordovaPlugin
{
//ellipsis code
// … ..
@Override
public boolean execute(String action, final JSONArray
args,
final CallbackContext callbackContext) throws
JSONException {
if ("sendNotify".equals(action)) {
NotificationManager manager = (NotificationManager)
this.cordova
.getActivity().getSystemService(
Context.NOTIFICATION_SERVICE);
String title = args.getString(0);
String text = args.getString(1);
Notification notification
= new Notification.Builder(this.cordova.getActivity())
.setTicker("New notification").setDefaults(1)
.setSmallIcon(R.drawable.icon)
.setAutoCancel(true)
.setContentTitle(title).setContentText(text)
.setContentIntent(PendingIntent.getActivity(this.cordova.getActivity(),
0, this.cordova.getActivity().getIntent(), 0)).build();
manager.notify(1, notification);
return true;
}
}
} |
在 res/xml/config.xml 配置 plugin feature
<feature name=" localNotification "> <param name="android-package" value="com.test.notification. localNotification" /> </feature> |
至此一个完整的 plugin 已经建立完成。然后我们只需要在 JavaScript 端调用这个 plugin
就可以触发一个 notification,具体调用方式参考清单 4:
清单 4. 客户端调用系统 notification
var ws= new WebSocket("ws://localhost:8080/PushNotification"); ws.onopen = function (event) { console.log("connected to server"); }; ws.onmessage = function (event) { var notifys = jQuery.parseJSON(evnt.data); var message = ["New Job Notification",notifys[0].message]; cordova.plugins.localNotification.sendNotify(message); }; |
当执行 cordova.plugins.localNotification.sendNotify 方法时,cordova
会向 webview 发送一个 XMLHttpRequest 请求并且包含的参数中有 sendNotify
关键字,这个请求会被我们已经实现的 Cordova plugin 拦截住,并且执行一个 android
Notification,这个时候一个消息就成功推送到客户端。如下图
图 3. 通过 websocket server 成功将信息推送到客户端
Websocket 消息推送的利弊
Websocket 消息推送优点 :
开发周期短,维护成本低。
消息不经转第三方服务器,直接由服务器发送到客户端,安全性好。基于 GCM 或者 APNS 的消息推送会把消息发送
GCM 服务器或 APNS 服务器,再由他们转发到客户端。
自己开发服务端,可扩展性好。
对于客户端而言,长连接比轮询的方式性能和及时性更好。
Websocket 消息推送缺点 :
长连接浪费服务端资源。
不能后台运行,一旦 app 退出就不能收到 notification。
由于服务器保持多个长连接,性能将会下降,最大连接数也会有限制。
总结
随着手机性能的不断改善,hybrid app 的性能几乎接近原生 app 的体验,最重要的是还可以跨平台,所以
hybrid app 越来越受到开发者的青睐,尤其是前端开发者。他们既能利用熟悉的 html5, css3,angular
js 作为主体开发语言,又能适时利用 Cordova plugin 调用底层的 API,这样既节约了开发成本,又能体验原生开发的乐趣。本文就利用
Cordova 跨移动平台框架,开发了一组调用 local notification 的 android
plugin, 前端代码不需要改变,我们只需要编写 iOS 端的 local notification
就可以适配一下 ios 平台。利用这种方法,我们同样可以扩展更多 plugin。而 websocket
作为 HTML5 的新特性,它不像传统的轮询查询服务端的方式而是主动 push 的方式向客户端推送消息,websocket
这种长连接的特性不仅适合消息推送,对于实时在线聊天功能也是非常适合。本文就结合了 websocket 和
Cordova 的特性开发了混合移动的消息推送。
|