关于webview嵌入web项目打包APP,android物理返回按钮的处理
- 首先我项目中没有用到mui.js,只是壳打包web项目和调用了h5+的支付
- 注册h5+的返回按钮事件 plus.key.addEventListener('backbutton',backListener,false);
- 事件中判断是不是在首页,然后进行处理
- w是创建的webview
- 目前实现到首页提示再按一次退出程序,和一级页面的返回,二级页面会只能返回一级,谁有好的解决方案望探讨
var first=null;
function backListener(){
//首次按键,提示‘再按一次退出应用’
if(w!=null){
var u=w.getURL();
if(u.lastIndexOf("welcome/index.htm")>0||u.lastIndexOf("ziyuan/index.htm")>0||u.lastIndexOf("xuqiu/index.htm")>0||u.lastIndexOf("paimai/index.htm")>0){
if (!first) {
console.log(u);
first = new Date().getTime();
plus.nativeUI.toast('再按一次退出程序');
setTimeout(function() {
first = null;
}, 1000);
} else {
if (new Date().getTime() - first < 1000) {
plus.runtime.quit();
}
}
}else{
w.canBack(function(e){
if(e.canBack){
w.back();
}else{
w.loadURL(weburl+'/welcome/index.htm');
}
});
w.canForward(function(e){
if(e.canForward){
w.forward();
}
});
}
}
}
- 首先我项目中没有用到mui.js,只是壳打包web项目和调用了h5+的支付
- 注册h5+的返回按钮事件 plus.key.addEventListener('backbutton',backListener,false);
- 事件中判断是不是在首页,然后进行处理
- w是创建的webview
- 目前实现到首页提示再按一次退出程序,和一级页面的返回,二级页面会只能返回一级,谁有好的解决方案望探讨
var first=null;
function backListener(){
//首次按键,提示‘再按一次退出应用’
if(w!=null){
var u=w.getURL();
if(u.lastIndexOf("welcome/index.htm")>0||u.lastIndexOf("ziyuan/index.htm")>0||u.lastIndexOf("xuqiu/index.htm")>0||u.lastIndexOf("paimai/index.htm")>0){
if (!first) {
console.log(u);
first = new Date().getTime();
plus.nativeUI.toast('再按一次退出程序');
setTimeout(function() {
first = null;
}, 1000);
} else {
if (new Date().getTime() - first < 1000) {
plus.runtime.quit();
}
}
}else{
w.canBack(function(e){
if(e.canBack){
w.back();
}else{
w.loadURL(weburl+'/welcome/index.htm');
}
});
w.canForward(function(e){
if(e.canForward){
w.forward();
}
});
}
}
}
收起阅读 »
APP的webview中拦截资源API可以进行大的优化
H5+里的那个拦截资源实在不好用啊,可以参考chrome插件的拦截。chrome的拦截只需要一个API,而不是像webview分拦截监听各种限制。
然后它可以分为多个阶段,如请求开始可以修改协议头,请求完成可以修改内容,重定向或则忽略都可以在回调中来实现。
所以来此提出建议,当然肯定涉及到很多方面,如果可以的话还是希望官方抽出些时间来优化下
H5+里的那个拦截资源实在不好用啊,可以参考chrome插件的拦截。chrome的拦截只需要一个API,而不是像webview分拦截监听各种限制。
然后它可以分为多个阶段,如请求开始可以修改协议头,请求完成可以修改内容,重定向或则忽略都可以在回调中来实现。
所以来此提出建议,当然肯定涉及到很多方面,如果可以的话还是希望官方抽出些时间来优化下
收起阅读 »多级PopPicke用setSelectedValuer设置默认值二三级不生效问题
用下面这个方法设置,结果发现只有第一级菜单设置成功了,后面两级压根就没执行。
// 设定省初始值
cityPicker.pickers[0].setSelectedValue(100000, 0, function() {
// 设定市初始值
cityPicker.pickers[1].setSelectedValue(101000, 0, function() {
// 设定区初始值
cityPicker.pickers[2].setSelectedValue(101001);
});
});
查看mui.picker源代码发现:
Picker.prototype.setSelectedValue = function(value, duration, callback) {
var self = this;
for (var index in self.items) {
var item = self.items[index];
if (item.value == value) {
self.setSelectedIndex(index, duration, callback);
return;
}
}
};
for循环中是个return,结束了循环不再往下执行,换成break即可。
用下面这个方法设置,结果发现只有第一级菜单设置成功了,后面两级压根就没执行。
// 设定省初始值
cityPicker.pickers[0].setSelectedValue(100000, 0, function() {
// 设定市初始值
cityPicker.pickers[1].setSelectedValue(101000, 0, function() {
// 设定区初始值
cityPicker.pickers[2].setSelectedValue(101001);
});
});
查看mui.picker源代码发现:
Picker.prototype.setSelectedValue = function(value, duration, callback) {
var self = this;
for (var index in self.items) {
var item = self.items[index];
if (item.value == value) {
self.setSelectedIndex(index, duration, callback);
return;
}
}
};
for循环中是个return,结束了循环不再往下执行,换成break即可。
收起阅读 »官方文档中h5+中的share组件实例代码有拼写错误
function shareAction(){
var s = shares[0];
if ( !s.authenticated ) {
s.authorize( functioin(){
console.log("认证完成!");
}, function(e){
console.log("未进行认证");
} )
}
}
上面是官方代码,将function写成了functioin
function shareAction(){
var s = shares[0];
if ( !s.authenticated ) {
s.authorize( functioin(){
console.log("认证完成!");
}, function(e){
console.log("未进行认证");
} )
}
}
上面是官方代码,将function写成了functioin
原生定位 - wap2app教程
场景说明
有定位需求的M站,通过手机浏览器访问时,经常因为定位问题导致用户体验下降、甚至用户流失,主要有以下几种情况:
- 部分手机不支持HTML5定位,因网络原因,非国产手机HTML5定位失败的概率更高
- 每次打开不同的M站,手机浏览器均会弹框提示定位权限,用户可能会误点击导致定位失败
- HTML5定位效率较低,重依赖定位的业务,在定位成功之前,无法显示业务数据,导致用户长时间等待
如下截图是大家经常碰见的情况:
wap2app运行在5+引擎环境下,5+引擎可以调用系统原生的定位,定位效率及精度远高于浏览器的HTML5定位,因此建议在有定位需求的页面,将HTML5定位替换升级为5+的原生定位。
升级方案
我们在GitHub上开源了一个plusto项目,该项目可以根据平台实现API的自动转换,比如在5+引擎环境下,将浏览器默认定位升级为5+原生定位,实现一套代码平滑迁移至多个平台。
目前关于定位的替换API已完善,参考plusGeolocation.js文件,该js文件判断在5+引擎环境下,将navigator.geolocation.getCurrentPosition()等方法重写为原生定位的方法,这样开发者就无需修改M站定位相关逻辑,就可以自动更换为原生定位。
目前geolocation/h5toplus.js已实现对如下3个HTML5定位方法的重写替换:
- navigator.geolocation.getCurrentPosition()
- navigator.geolocation.watchPosition()
- navigator.geolocation.clearwatch()
开发者只需按照如下步骤操作,即可将浏览器的HTML5定位升级为原生定位:
- 下载plusGeolocation.js(下载地址),并上传到M站的cdn服务器上
- 在M站上有定位需求的页面引用plusGeolocation.js,例如:
<script src="http://cdn.example.com/js/plusGeolocation.js" type="text/javascript" charset="utf-8"></script>
Tips1: plusGeolocation.js 是通过重写navigator.geolocation API来升级定位功能的,因此需确保在调用HTML5定位代码之前引用plusGeolocation.js;
Tips2:h5toplus.js文件开头就有运行环境的判断,若不是5+环境,则直接return,不会执行替换定位的他逻辑,因此开发者无需担心影响原有定位业务。
if(!navigator.userAgent.match(/Html5Plus/i)) {
//非5+引擎环境,直接return;
return;
} 场景说明
有定位需求的M站,通过手机浏览器访问时,经常因为定位问题导致用户体验下降、甚至用户流失,主要有以下几种情况:
- 部分手机不支持HTML5定位,因网络原因,非国产手机HTML5定位失败的概率更高
- 每次打开不同的M站,手机浏览器均会弹框提示定位权限,用户可能会误点击导致定位失败
- HTML5定位效率较低,重依赖定位的业务,在定位成功之前,无法显示业务数据,导致用户长时间等待
如下截图是大家经常碰见的情况:
wap2app运行在5+引擎环境下,5+引擎可以调用系统原生的定位,定位效率及精度远高于浏览器的HTML5定位,因此建议在有定位需求的页面,将HTML5定位替换升级为5+的原生定位。
升级方案
我们在GitHub上开源了一个plusto项目,该项目可以根据平台实现API的自动转换,比如在5+引擎环境下,将浏览器默认定位升级为5+原生定位,实现一套代码平滑迁移至多个平台。
目前关于定位的替换API已完善,参考plusGeolocation.js文件,该js文件判断在5+引擎环境下,将navigator.geolocation.getCurrentPosition()等方法重写为原生定位的方法,这样开发者就无需修改M站定位相关逻辑,就可以自动更换为原生定位。
目前geolocation/h5toplus.js已实现对如下3个HTML5定位方法的重写替换:
- navigator.geolocation.getCurrentPosition()
- navigator.geolocation.watchPosition()
- navigator.geolocation.clearwatch()
开发者只需按照如下步骤操作,即可将浏览器的HTML5定位升级为原生定位:
- 下载plusGeolocation.js(下载地址),并上传到M站的cdn服务器上
- 在M站上有定位需求的页面引用plusGeolocation.js,例如:
<script src="http://cdn.example.com/js/plusGeolocation.js" type="text/javascript" charset="utf-8"></script>
Tips1: plusGeolocation.js 是通过重写navigator.geolocation API来升级定位功能的,因此需确保在调用HTML5定位代码之前引用plusGeolocation.js;
Tips2:h5toplus.js文件开头就有运行环境的判断,若不是5+环境,则直接return,不会执行替换定位的他逻辑,因此开发者无需担心影响原有定位业务。
if(!navigator.userAgent.match(/Html5Plus/i)) {
//非5+引擎环境,直接return;
return;
} 收起阅读 »
原生分享 - wap2app教程
体验差距
因web能力限制,M站仅支持wap方式的分享,分享体验很糟糕,如下是一种典型实现(参考下方截图):
- 点击微信分享后,显示一个二维码,用户需要启动微信扫描二维码,先在微信中打开这篇文章,然后再通过微信右上角的菜单分享出去;分享路径太长,操作麻烦;
- 点击微博分享,需要登录微博wap站,完成授权后才能分享
wap2app运行在5+ 引擎下,是可以通过HTML5+的share模块直接调起系统原生分享的,同样场景,稍作改造,在5+引擎环境下调用原生分享,则体验会大大改观,如下为调用原生分享后的截图:
5+引擎还可以调起系统支持的更多分享,比如微博、QQ、短信、邮件等,如下为点击“更多分享”后的示例:
很明显,通过5+引擎调起原生分享后,分享路径更短、体验更好,更有利于分享内容的传播。
改造方案
要实现如上的分享体验,开发者只需要对M站稍作修改,判断是5+引擎的环境下,调用HTML5+的share模块API即可实现。
为简化开发,DCloud封装了plusShare.js函数,开发者引入该函数后,只需调用一个API,即可完成原生分享的改造。
引用plusShare.js文件
对M站上有分享功能的页面引入plusShare.js,js下载地址:GitHub,建议放在M站的cdn服务器上,如下:
<script src="http://cdn.example.com/js/plusShare.js" type="text/javascript" charset="utf-8"></script>
修改点击分享的实现
修改分享按钮的点击事件,假设之前的分享按钮点击实现如下:
document.getElementById("share").addEventListener("click", function() {
//原有wap分享实现
});
引入plusShare后,参考如下方式修改代码即可:
document.getElementById("share").addEventListener("click", function() {
if(navigator.userAgent.indexOf("Html5Plus") > -1) {
//5+ 原生分享
window.plusShare({
title: "my-app-name",//应用名字
content: "分享具体内容",
href: location.href,//分享出去后,点击跳转地址
thumbs: ["http://m.example.com/imgs/1.png"] //分享缩略图
}, function(result) {
//分享回调
});
} else {
//原有wap分享实现
}
});
注意:
- 具体分享内容,开发者可以根据M站业务自定义设置,比如动态读取当前页面顶部大图作为thumbs参数
- plusShare.js的具体用户参考plusShare教程
体验差距
因web能力限制,M站仅支持wap方式的分享,分享体验很糟糕,如下是一种典型实现(参考下方截图):
- 点击微信分享后,显示一个二维码,用户需要启动微信扫描二维码,先在微信中打开这篇文章,然后再通过微信右上角的菜单分享出去;分享路径太长,操作麻烦;
- 点击微博分享,需要登录微博wap站,完成授权后才能分享
wap2app运行在5+ 引擎下,是可以通过HTML5+的share模块直接调起系统原生分享的,同样场景,稍作改造,在5+引擎环境下调用原生分享,则体验会大大改观,如下为调用原生分享后的截图:
5+引擎还可以调起系统支持的更多分享,比如微博、QQ、短信、邮件等,如下为点击“更多分享”后的示例:
很明显,通过5+引擎调起原生分享后,分享路径更短、体验更好,更有利于分享内容的传播。
改造方案
要实现如上的分享体验,开发者只需要对M站稍作修改,判断是5+引擎的环境下,调用HTML5+的share模块API即可实现。
为简化开发,DCloud封装了plusShare.js函数,开发者引入该函数后,只需调用一个API,即可完成原生分享的改造。
引用plusShare.js文件
对M站上有分享功能的页面引入plusShare.js,js下载地址:GitHub,建议放在M站的cdn服务器上,如下:
<script src="http://cdn.example.com/js/plusShare.js" type="text/javascript" charset="utf-8"></script>
修改点击分享的实现
修改分享按钮的点击事件,假设之前的分享按钮点击实现如下:
document.getElementById("share").addEventListener("click", function() {
//原有wap分享实现
});
引入plusShare后,参考如下方式修改代码即可:
document.getElementById("share").addEventListener("click", function() {
if(navigator.userAgent.indexOf("Html5Plus") > -1) {
//5+ 原生分享
window.plusShare({
title: "my-app-name",//应用名字
content: "分享具体内容",
href: location.href,//分享出去后,点击跳转地址
thumbs: ["http://m.example.com/imgs/1.png"] //分享缩略图
}, function(result) {
//分享回调
});
} else {
//原有wap分享实现
}
});
注意:
- 具体分享内容,开发者可以根据M站业务自定义设置,比如动态读取当前页面顶部大图作为thumbs参数
- plusShare.js的具体用户参考plusShare教程
plusShare教程 - 分享到微信好友、朋友圈、微博等
简介
plusShare是基于HTML5+的share模块封装的社交分享函数,开发者只需调用一个API,即可调起微信好友、微信朋友圈、系统更多分享功能。
plusShare目前支持:
- 自动获取分享服务
- 自动检测微信是否已安装
- 设置分享消息标题、内容、缩略图、链接地址
plusShare的开源地址为GitHub
API介绍
plusShare只有一个方法,如下调用即可:
plusShare(message,callback);
其中:
- message:分享内容设置
- callback:分享结果回调
message
分享消息内容,类型为Object,主要包括如下属性:
- title:分享消息的标题,类型为String,目前仅分享到微信好友时支持。
- content:分享消息的文字内容,类型为String
- href:分享的页面链接(用户点击消息时的跳转地址),类型为String
- thumbs:分享消息的缩略图,类型为Array;
callback
分享结束的回调函数,函数包含一个参数res,boolean类型,分别表示:
- true:分享成功
- false:分享失败
备注:系统分享(更多分享)暂不支持判断分享是否成功
代码示例
如下是一个示例代码:
document.getElementById("share").addEventListener("click", function() {
//分享内容,开发者可自定义
var message = {
title: "plusShare示例", //应用名字
content: "plusShare基于HTML5+的share模块,开发者只需调用一个API,即可调起微信好友、微信朋友圈、系统更多分享功能",
href: "http://www.dcloud.io/hellomui", //分享出去后,点击跳转地址
thumbs: ["http://img-cdn-qiniu.dcloud.net.cn/icon3.png"] //分享缩略图
}
//调起分享
plusShare(message, function(res) {
//分享回调函数
if(res) {
plus.nativeUI.toast("分享成功");
} else {
plus.nativeUI.toast("分享失败");
}
})
});
真机运行,点击分享到微信消息、微信朋友圈结果如下:
点击“更多分享”,然后选择短信,结果如下:
简介
plusShare是基于HTML5+的share模块封装的社交分享函数,开发者只需调用一个API,即可调起微信好友、微信朋友圈、系统更多分享功能。
plusShare目前支持:
- 自动获取分享服务
- 自动检测微信是否已安装
- 设置分享消息标题、内容、缩略图、链接地址
plusShare的开源地址为GitHub
API介绍
plusShare只有一个方法,如下调用即可:
plusShare(message,callback);
其中:
- message:分享内容设置
- callback:分享结果回调
message
分享消息内容,类型为Object,主要包括如下属性:
- title:分享消息的标题,类型为String,目前仅分享到微信好友时支持。
- content:分享消息的文字内容,类型为String
- href:分享的页面链接(用户点击消息时的跳转地址),类型为String
- thumbs:分享消息的缩略图,类型为Array;
callback
分享结束的回调函数,函数包含一个参数res,boolean类型,分别表示:
- true:分享成功
- false:分享失败
备注:系统分享(更多分享)暂不支持判断分享是否成功
代码示例
如下是一个示例代码:
document.getElementById("share").addEventListener("click", function() {
//分享内容,开发者可自定义
var message = {
title: "plusShare示例", //应用名字
content: "plusShare基于HTML5+的share模块,开发者只需调用一个API,即可调起微信好友、微信朋友圈、系统更多分享功能",
href: "http://www.dcloud.io/hellomui", //分享出去后,点击跳转地址
thumbs: ["http://img-cdn-qiniu.dcloud.net.cn/icon3.png"] //分享缩略图
}
//调起分享
plusShare(message, function(res) {
//分享回调函数
if(res) {
plus.nativeUI.toast("分享成功");
} else {
plus.nativeUI.toast("分享失败");
}
})
});
真机运行,点击分享到微信消息、微信朋友圈结果如下:
点击“更多分享”,然后选择短信,结果如下:
个推+nettysocketio实现IM即时通讯
目标
技术选型
前端
后端java
MAVEN配置
离线消息
坑
相关材料
目标
实现WEB和APP通用的即时通讯功能。一份代码同时支持各种WEB浏览器以及IOS,Android环境下的APP
技术选型
使用基于netty的nettysocketio实现,后台为JS,前台为HTML5页面,部署时采用nginx中转,java部署在tomcat上
前端
var socket
if(/^plus|android/.test(this.agent)) {
//android的默认浏览器内核不支持websocket10草案
socket = socketIO.connect(LContext.imUrl, {
transports: ['polling']
})
} else {
socket = socketIO.connect(LContext.imUrl)
}
socket.on('reconnect_attempt', function() {
socket.io.opts.transports = ['polling']
})
socket.on('connect', function() {
var data = lpage.user.getData()
////console.log('连接成功..')
////console.log('<span class="connect-msg">Client has connected to the server!</span>\n' + JSON.stringify(data))
//登记当前会话关联的用户
socket.emit('startConnection', {
extra: {
id: data.id,
token: data.token
}
})
})
socket.on('startConnection', function() {
////console.log('连接到服务器')
lpage.socket = socket
// document.querySelector('header .mui-title').innerText = lpage.fromUserName
})
//注册事件,服务器通过事件调用本方法
socket.on('push', function(data, ackCallback) {
////console.log('接收消息' + JSON.stringify(data))
lpage.addNews(data)
if(ackCallback) {
////console.log('接收到消息后,返回消息给服务器确认')
ackCallback('返回到服务器的确认消息')
}
})
socket.on('disconnect', function() {
////console.log('断开连接')
lpage.socket = undefined
setTimeout(function() {
lpage.initConnection()
}, 5000)
//5秒后尝试恢复连接
// document.querySelector('header .mui-title').innerText = '未连接(点击重新连接)'
})
// socket.on('error', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
//
// socket.on('connect_error', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
//
// socket.on('connect_timeout', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
awk
后端java
@Service
public class IMHandleService implements ApplicationListener<ContextRefreshedEvent> {
Logger logger = Logger.getLogger(IMHandleService.class);
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
logger.info("运行init event on IMHandleService");
if (event.getApplicationContext().getParent() != null)// root
// application
// context
// 没有parent,他就是老大.
{
// 需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
logger.info("\n\n\n\n\n__\n\n\n已经加载了ContextRefreshedEvent\n\n\n\n");
return;
}
// 或者下面这种方式
// if (event.getApplicationContext().getDisplayName().equals("Root
// WebApplicationContext")) {
// System.out.println("\n\n\n\n\n加载一次的 \n\n ____\n\n\n\n");
//
// logger.info("\n\n\n\n\n__\n\n\n加载一次的
// \n\n_____\n\n");
// }
Configuration config = new Configuration();
config.setHostname("localhost");
config.setPort(10086);
config.setUpgradeTimeout(60000000);// 设置websocket过期时间
// config.setFirstDataTimeout(60000000);
// config.setPingInterval(28);
SocketIOServer server = new SocketIOServer(config);
logger.info(server.getConfiguration().getFirstDataTimeout() + " " + server.getConfiguration().getPingInterval()
- " " + server.getConfiguration().getUpgradeTimeout() + " "
- server.getConfiguration().getPingTimeout());
server.addConnectListener(new ConnectListener() {// 添加客户端连接监听器
@Override
public void onConnect(SocketIOClient client) {
logger.info(client.getRemoteAddress().toString() + " " + client.getTransport().getValue() + " "- client.getHandshakeData().getUrl() + " 接入" + client.getSessionId());
// 调用客户端的事件
client.sendEvent("startConnection", "hello");
}
});
server.addDisconnectListener(new DisconnectListener() {
@Override
public void onDisconnect(SocketIOClient client) {
logger.info(client.getRemoteAddress().toString() + " " + client.getTransport().getValue() + " " - client.getHandshakeData().getUrl() + " 断开" + client.getSessionId());
// 清理连接的session
// 检测登录状态,已登录
// 保存当前会话
String userId = sessionUserMap.get("sessionUserMap", client.getSessionId().toString());
if (userId == null) {
return;
}
// 清除在线状态
sessionUserMap.delete("sessionUserMap", client.getSessionId().toString());
userChatSession.remove(SESSION_STORAGE_PREFIX + userId, client.getSessionId().toString());
logger.info("清理session userId= " + userId);
logger.info("session列表长度 " + userChatSession.members(SESSION_STORAGE_PREFIX + userId).toArray().length);
}
});
/**- 监听事件,客户端连接成功后调用本方法
*/
server.addEventListener("startConnection", Operation.class, new DataListener<Operation>() {
@Override
public void onData(final SocketIOClient client, Operation data, AckRequest ackRequest) {
if (data.extra == null || data.extra.id == null || data.extra.token == null
|| !tokenManager.checkToken(new TokenModel(data.extra.id.toString(), data.extra.token))) {
return;
}
// 检测登录状态,已登录
// 保存当前会话
sessionUserMap.put("sessionUserMap", client.getSessionId().toString(), data.extra.id.toString());
userChatSession.add(SESSION_STORAGE_PREFIX + data.extra.id.toString(),
client.getSessionId().toString());
logger.info("确认连接成功,绑定userId= " + data.extra.id.toString() + " sessionId "
- 监听事件,客户端连接成功后调用本方法
- client.getSessionId().toString());
logger.info("session列表长度 " - userChatSession.members(SESSION_STORAGE_PREFIX + data.extra.id.toString()).toArray().length);
// 握手
// if (data.getMessage().equals("hello")) {
// Long userid = data.getUser();
// logger.info(Thread.currentThread().getName() +
// "web读取到的userid:" + userid);
// // send message back to client with ack callback
// // WITH data
// client.sendEvent("push", new
// AckCallback<String>(String.class) {
// @Override
// public void onSuccess(String result) {
// logger.info("ack from client: " + client.getSessionId() + "
// data: " + result);
// }
// }, context.SESSION_TIME);
//
// } else {
// logger.info("行情接收到了不应该有的web客户端请求1111...");
// }
}
});
/**- 监听事件,客户端通过事件调用本方法
*/
server.addEventListener("message", Operation.class, new DataListener<Operation>() {
@Override
public void onData(final SocketIOClient client, Operation data, AckRequest ackRequest) {
// 握手
if (data.getMessage().equals("hello")) {
Long userid = data.getTargetuser();
logger.info(Thread.currentThread().getName() + "web读取到的userid:" + userid);
// send message back to client with ack callback
// WITH data
client.sendEvent("push", new AckCallback<String>(String.class) {
@Override
public void onSuccess(String result) {
logger.info("接收到客户端反馈 " + client.getSessionId() + " data: " + result);
}
});
} else {
logger.info("接收到了不应该有的web客户端请求...");
}
}
});
if (serverBuff == null) {
logger.info("初始化 IMHandleService的 监听server");
try {
server.start();
serverBuff = server;
} catch (Exception e) {
e.printStackTrace();
logger.error("\n\nIM服务器启动失败...\n\n");
}
// try {
// Thread.sleep(Integer.MAX_VALUE);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// server.stop();
} else {
logger.info(" IMHandleService的 监听server已启动");
}
}
@PreDestroy
public void closeSocket() {
logger.info(" IMHandleService 关闭中...");
if (serverBuff != null) {
serverBuff.stop();
for (String sessionId : sessionUserMap.keys("sessionUserMap")) {
String userId = sessionUserMap.get("sessionUserMap", sessionId);
userChatSession.remove(SESSION_STORAGE_PREFIX + userId, sessionId);
sessionUserMap.delete("sessionUserMap", sessionId);
logger.info("清理session " + userId + " 当前长度为" + sessionUserMap.keys("sessionUserMap").size() + " "
- 监听事件,客户端通过事件调用本方法
- userChatSession.size(SESSION_STORAGE_PREFIX + userId));
}
logger.info(" IMHandleService 已关闭完成");
}
}
}
http
MAVEN配置
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.12</version>
</dependency>
xml
离线消息
对于所有消息,默认都通过在线服务发送,当发送失败时,启用个推的JAVA SDK进行消息推送即可,具体参考个推官网
- client.getHandshakeData().getUrl() + " 接入" + client.getSessionId());
坑
在WEB和IOS设备上都可以正常使用,但是在androidAPP中会出现一分钟左右就断线一次的问题。最后在一篇博客上找到了问题所在,由于android的默认浏览器内核支持的websocket版本为10,而Netty的默认实现目前还只支持到7.6,如果有兴趣自己升级的话,可以参考netty升级websocket草案10,
本文链接:http://blog.betweenfriends.cn/post/imnettysocketio.html
目标
技术选型
前端
后端java
MAVEN配置
离线消息
坑
相关材料
目标
实现WEB和APP通用的即时通讯功能。一份代码同时支持各种WEB浏览器以及IOS,Android环境下的APP
技术选型
使用基于netty的nettysocketio实现,后台为JS,前台为HTML5页面,部署时采用nginx中转,java部署在tomcat上
前端
var socket
if(/^plus|android/.test(this.agent)) {
//android的默认浏览器内核不支持websocket10草案
socket = socketIO.connect(LContext.imUrl, {
transports: ['polling']
})
} else {
socket = socketIO.connect(LContext.imUrl)
}
socket.on('reconnect_attempt', function() {
socket.io.opts.transports = ['polling']
})
socket.on('connect', function() {
var data = lpage.user.getData()
////console.log('连接成功..')
////console.log('<span class="connect-msg">Client has connected to the server!</span>\n' + JSON.stringify(data))
//登记当前会话关联的用户
socket.emit('startConnection', {
extra: {
id: data.id,
token: data.token
}
})
})
socket.on('startConnection', function() {
////console.log('连接到服务器')
lpage.socket = socket
// document.querySelector('header .mui-title').innerText = lpage.fromUserName
})
//注册事件,服务器通过事件调用本方法
socket.on('push', function(data, ackCallback) {
////console.log('接收消息' + JSON.stringify(data))
lpage.addNews(data)
if(ackCallback) {
////console.log('接收到消息后,返回消息给服务器确认')
ackCallback('返回到服务器的确认消息')
}
})
socket.on('disconnect', function() {
////console.log('断开连接')
lpage.socket = undefined
setTimeout(function() {
lpage.initConnection()
}, 5000)
//5秒后尝试恢复连接
// document.querySelector('header .mui-title').innerText = '未连接(点击重新连接)'
})
// socket.on('error', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
//
// socket.on('connect_error', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
//
// socket.on('connect_timeout', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
awk
后端java
@Service
public class IMHandleService implements ApplicationListener<ContextRefreshedEvent> {
Logger logger = Logger.getLogger(IMHandleService.class);
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
logger.info("运行init event on IMHandleService");
if (event.getApplicationContext().getParent() != null)// root
// application
// context
// 没有parent,他就是老大.
{
// 需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
logger.info("\n\n\n\n\n__\n\n\n已经加载了ContextRefreshedEvent\n\n\n\n");
return;
}
// 或者下面这种方式
// if (event.getApplicationContext().getDisplayName().equals("Root
// WebApplicationContext")) {
// System.out.println("\n\n\n\n\n加载一次的 \n\n ____\n\n\n\n");
//
// logger.info("\n\n\n\n\n__\n\n\n加载一次的
// \n\n_____\n\n");
// }
Configuration config = new Configuration();
config.setHostname("localhost");
config.setPort(10086);
config.setUpgradeTimeout(60000000);// 设置websocket过期时间
// config.setFirstDataTimeout(60000000);
// config.setPingInterval(28);
SocketIOServer server = new SocketIOServer(config);
logger.info(server.getConfiguration().getFirstDataTimeout() + " " + server.getConfiguration().getPingInterval()
- " " + server.getConfiguration().getUpgradeTimeout() + " "
- server.getConfiguration().getPingTimeout());
server.addConnectListener(new ConnectListener() {// 添加客户端连接监听器
@Override
public void onConnect(SocketIOClient client) {
logger.info(client.getRemoteAddress().toString() + " " + client.getTransport().getValue() + " "- client.getHandshakeData().getUrl() + " 接入" + client.getSessionId());
// 调用客户端的事件
client.sendEvent("startConnection", "hello");
}
});
server.addDisconnectListener(new DisconnectListener() {
@Override
public void onDisconnect(SocketIOClient client) {
logger.info(client.getRemoteAddress().toString() + " " + client.getTransport().getValue() + " " - client.getHandshakeData().getUrl() + " 断开" + client.getSessionId());
// 清理连接的session
// 检测登录状态,已登录
// 保存当前会话
String userId = sessionUserMap.get("sessionUserMap", client.getSessionId().toString());
if (userId == null) {
return;
}
// 清除在线状态
sessionUserMap.delete("sessionUserMap", client.getSessionId().toString());
userChatSession.remove(SESSION_STORAGE_PREFIX + userId, client.getSessionId().toString());
logger.info("清理session userId= " + userId);
logger.info("session列表长度 " + userChatSession.members(SESSION_STORAGE_PREFIX + userId).toArray().length);
}
});
/**- 监听事件,客户端连接成功后调用本方法
*/
server.addEventListener("startConnection", Operation.class, new DataListener<Operation>() {
@Override
public void onData(final SocketIOClient client, Operation data, AckRequest ackRequest) {
if (data.extra == null || data.extra.id == null || data.extra.token == null
|| !tokenManager.checkToken(new TokenModel(data.extra.id.toString(), data.extra.token))) {
return;
}
// 检测登录状态,已登录
// 保存当前会话
sessionUserMap.put("sessionUserMap", client.getSessionId().toString(), data.extra.id.toString());
userChatSession.add(SESSION_STORAGE_PREFIX + data.extra.id.toString(),
client.getSessionId().toString());
logger.info("确认连接成功,绑定userId= " + data.extra.id.toString() + " sessionId "
- 监听事件,客户端连接成功后调用本方法
- client.getSessionId().toString());
logger.info("session列表长度 " - userChatSession.members(SESSION_STORAGE_PREFIX + data.extra.id.toString()).toArray().length);
// 握手
// if (data.getMessage().equals("hello")) {
// Long userid = data.getUser();
// logger.info(Thread.currentThread().getName() +
// "web读取到的userid:" + userid);
// // send message back to client with ack callback
// // WITH data
// client.sendEvent("push", new
// AckCallback<String>(String.class) {
// @Override
// public void onSuccess(String result) {
// logger.info("ack from client: " + client.getSessionId() + "
// data: " + result);
// }
// }, context.SESSION_TIME);
//
// } else {
// logger.info("行情接收到了不应该有的web客户端请求1111...");
// }
}
});
/**- 监听事件,客户端通过事件调用本方法
*/
server.addEventListener("message", Operation.class, new DataListener<Operation>() {
@Override
public void onData(final SocketIOClient client, Operation data, AckRequest ackRequest) {
// 握手
if (data.getMessage().equals("hello")) {
Long userid = data.getTargetuser();
logger.info(Thread.currentThread().getName() + "web读取到的userid:" + userid);
// send message back to client with ack callback
// WITH data
client.sendEvent("push", new AckCallback<String>(String.class) {
@Override
public void onSuccess(String result) {
logger.info("接收到客户端反馈 " + client.getSessionId() + " data: " + result);
}
});
} else {
logger.info("接收到了不应该有的web客户端请求...");
}
}
});
if (serverBuff == null) {
logger.info("初始化 IMHandleService的 监听server");
try {
server.start();
serverBuff = server;
} catch (Exception e) {
e.printStackTrace();
logger.error("\n\nIM服务器启动失败...\n\n");
}
// try {
// Thread.sleep(Integer.MAX_VALUE);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
// server.stop();
} else {
logger.info(" IMHandleService的 监听server已启动");
}
}
@PreDestroy
public void closeSocket() {
logger.info(" IMHandleService 关闭中...");
if (serverBuff != null) {
serverBuff.stop();
for (String sessionId : sessionUserMap.keys("sessionUserMap")) {
String userId = sessionUserMap.get("sessionUserMap", sessionId);
userChatSession.remove(SESSION_STORAGE_PREFIX + userId, sessionId);
sessionUserMap.delete("sessionUserMap", sessionId);
logger.info("清理session " + userId + " 当前长度为" + sessionUserMap.keys("sessionUserMap").size() + " "
- 监听事件,客户端通过事件调用本方法
- userChatSession.size(SESSION_STORAGE_PREFIX + userId));
}
logger.info(" IMHandleService 已关闭完成");
}
}
}
http
MAVEN配置
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.12</version>
</dependency>
xml
离线消息
对于所有消息,默认都通过在线服务发送,当发送失败时,启用个推的JAVA SDK进行消息推送即可,具体参考个推官网
- client.getHandshakeData().getUrl() + " 接入" + client.getSessionId());
坑
在WEB和IOS设备上都可以正常使用,但是在androidAPP中会出现一分钟左右就断线一次的问题。最后在一篇博客上找到了问题所在,由于android的默认浏览器内核支持的websocket版本为10,而Netty的默认实现目前还只支持到7.6,如果有兴趣自己升级的话,可以参考netty升级websocket草案10,
本文链接:http://blog.betweenfriends.cn/post/imnettysocketio.html
收起阅读 »分享mui启动第三方应用遇到的坑 android&ios
if ( plus.os.name == "Android" ) {
plus.runtime.launchApplication( {
pname:"com.tencent.mm"}, function ( e ) {
plus.nativeUI.confirm( "检查到您未安装\"微信\",是否到商城搜索下载?", function(i){
if ( i.index == 0 ) {
androidMarket( "com.tencent.mm" );
}
} );
} );
} else if ( plus.os.name == "iOS" ) {
plus.runtime.launchApplication( {
action:"weixin://RnUbAwvEilb1rU9g9yBU"}, function ( e ) {
plus.nativeUI.confirm( "检查到您未安装\"微信\",是否到商城搜索下载?", function(i){
if ( i.index == 0 ) {
i osAppstore( "itunes.apple.com/cn/app/wechat/id414478124?mt=8" );
}
} );
} );
}
上边例子是启动微信的,如果是自己公司开发的app 就换成响应的
android和ios不同点
1 pname 和 action
2 android用包名启动 ios用scheme启动
if ( plus.os.name == "Android" ) {
plus.runtime.launchApplication( {
pname:"com.tencent.mm"}, function ( e ) {
plus.nativeUI.confirm( "检查到您未安装\"微信\",是否到商城搜索下载?", function(i){
if ( i.index == 0 ) {
androidMarket( "com.tencent.mm" );
}
} );
} );
} else if ( plus.os.name == "iOS" ) {
plus.runtime.launchApplication( {
action:"weixin://RnUbAwvEilb1rU9g9yBU"}, function ( e ) {
plus.nativeUI.confirm( "检查到您未安装\"微信\",是否到商城搜索下载?", function(i){
if ( i.index == 0 ) {
i osAppstore( "itunes.apple.com/cn/app/wechat/id414478124?mt=8" );
}
} );
} );
}
上边例子是启动微信的,如果是自己公司开发的app 就换成响应的
android和ios不同点
1 pname 和 action
2 android用包名启动 ios用scheme启动
找个兼职的MUI前端 稳定长期合作的
来个能稳定长期合作的联系下QQ16800606
太黑的离谱的就算了哦~
项目会出好设计搞,需要用MUI实现
接口会提供接口文档
来个能稳定长期合作的联系下QQ16800606
太黑的离谱的就算了哦~
项目会出好设计搞,需要用MUI实现
接口会提供接口文档
模板事件 - NView模板 - wap2app教程
onclick事件
在NView模板中,很多控件都可以通过onclick属性配置控件的点击事件,配置方式如下:
<element onclick={this.func_name}></element>
func_name是一个函数,需要在<script>节点下的methods中声明,示例如下:
<script>
module.exports = {
data: {},//数据
methods: {
func_name: function(event) {
//点击事件触发
}
}
};
</script>
点击事件的回调函数自带一个event参数,通过event.detail可以访问当前NView模板中绑定的数据(即data对象),如下是一个完整示例,点击按钮,打印当前的产品名称和版本号:
<template>
<nviews cachemaxage="86400">
<nview style="height:100px">
<canvas>
<button onclick={this.clickBtn}>按钮</button>
</canvas>
</nview>
</nviews>
</template>
<script>
module.exports = {
data: {
name: "wap2app",
version: "1.0.0"
},
methods: {
clickBtn: function(event) { //按钮点击事件触发
var data = event.detail;
console.log("name:" + data.name + ",version:" + data.version);
}
}
};
</script>
NView模板中,列表组件(<list>)和流式布局控件(<richtext>)的点击事件相对特殊,下文分别介绍。
list组件
<list>标签的onclick点击事件同上,但列表项<item>的点击事件相对特殊,如果列表项是通过n-for指令循环渲染的,则在点击回调函数中,除了event参数外,还有item、index两个参数,分别表示当前列表项的数据及列表项索引,详细参考NView模板绘制原生列表教程。
richtext布局控件
<richtext>布局控件也支持监听点击事件,配置方式同上;但<richtext>内部嵌套的子控件不支持监听onclick事件,用户点击<richtext>内部控件时,会向上冒泡到父容器<richtext>标签上,触发<richtext>的onclick回调。
同普通控件的点击回调,<richtext>的点击回调函数,自带一个event参数,通过event.detail可以访问当前NView模板中绑定的数据(即data对象);另外,还可以通过event访问点击目标对象的属性(tagName、href、src),具体情况如下:
- 如果点击的是<richtext>中的<a>标签,则回调函数event参数中包含tagName(值为"a")和href属性;
- 如果点击的是<richtext>中的<img>标签,则回调函数event参数中包含tagName(值为"img")和src属性;
- 如果点击的是<richtext>中的其它区域或控件,则回调函数event参数中包含tagName(值为"")
如下是一个示例代码:
<template>
<nviews cachemaxage="86400">
<nview style="height:100px;" >
<richtext onclick={this.handleRichText}>
<img src="logo.png" width="20px" height="20px"></img>
<a href="http://www.example.com">
http://www.example.com
</a>
</richtext>
</nview>
</nviews>
</template>
<script>
module.exports = {
methods: {
handleRichText:function(e){
console.log("tagName:" + e.tagName);
console.log("href:" + e.href);
console.log("src:" + e.src);
}
}
};
</script>
事件冒泡
NView模板中,点击事件默认会从子控件向父控件冒泡。
如下示例中,nview和button标签同时监听了点击事件,当用户点击button按钮时,会先弹框提醒“click button!”,然后点击事件向父容器冒泡,接着弹框提醒"click nview!"。
<template>
<nviews cachemaxage="86400">
<nview style="height:100px" onclick={this.clickNView}>
<canvas>
<button style="top:10px;left:20px;" onclick={this.clickBtn}>按钮</button>
<img style="top:10px;left:100px;" src="_www/img/demo.png"></img>
</canvas>
</nview>
</nviews>
</template>
<script>
module.exports = {
data: {
//默认数据
},
init: function(url) {
//动态初始化数据
},
methods: {
clickNView:function(event){//nview点击事件触发
alert("click nview!");
},
clickBtn:function(event){//按钮点击事件触发
alert("click button!");
}
}
};
</script>
如果想阻止事件冒泡,可以在子控件的点击函数增加return false语句,如下:
<template>
<nviews cachemaxage="86400">
<nview style="height:100px;" onclick={this.clickNView}>
<canvas>
<button style="top:10px;left:20px;" onclick={this.clickBtn}>按钮</button>
<img style="top:10px;left:100px;" src="_www/img/demo.png"></img>
</canvas>
</nview>
</nviews>
</template>
<script>
module.exports = {
data: {
//默认数据
},
init: function(url) {
//动态初始化数据
},
methods: {
clickNView:function(event){//nview点击事件触发
alert("click nview!");
},
clickBtn:function(event){//按钮点击事件触发
alert("click button!");
return false;//阻止事件冒泡
}
}
};
</script>
阻止事件冒泡后,用户再次点击button按钮,则会弹框提醒“click button!”,不会再弹框提醒"click nview!"。
事件拦截
subNView作为webview的一个子控件,覆盖在webview上方,当用户点击subNView控件时,subNView默认会拦截touch事件,不会将touch事件透传给下层的webview中的DOM元素。
wap2app支持通过设置nview标签的intercept属性配置touch事件是否拦截,默认为true,即拦截touch事件,下层webview中的DOM元素无法触发touch事件;如果设置为false,则不拦截touch事件,下层webview中的DOM元素将接收到用户的touch事件,从而触发原来HTML页面的touch事件逻辑。
如下示例代码中,nview1设置了不拦截touch事件,则在nview1区域点击时,都会透传给下方的webview页面中的DOM元素,触发DOM元素的点击事件.
<template>
<nviews cachemaxage="86400">
<nview id="nview1" style="position:static" intercept=“false”>
...
</nview>
</nviews>
</template>
<script>
module.exports = {
};
</script>
Tips:仅nview标签支持intercept属性设置。
onclick事件
在NView模板中,很多控件都可以通过onclick属性配置控件的点击事件,配置方式如下:
<element onclick={this.func_name}></element>
func_name是一个函数,需要在<script>节点下的methods中声明,示例如下:
<script>
module.exports = {
data: {},//数据
methods: {
func_name: function(event) {
//点击事件触发
}
}
};
</script>
点击事件的回调函数自带一个event参数,通过event.detail可以访问当前NView模板中绑定的数据(即data对象),如下是一个完整示例,点击按钮,打印当前的产品名称和版本号:
<template>
<nviews cachemaxage="86400">
<nview style="height:100px">
<canvas>
<button onclick={this.clickBtn}>按钮</button>
</canvas>
</nview>
</nviews>
</template>
<script>
module.exports = {
data: {
name: "wap2app",
version: "1.0.0"
},
methods: {
clickBtn: function(event) { //按钮点击事件触发
var data = event.detail;
console.log("name:" + data.name + ",version:" + data.version);
}
}
};
</script>
NView模板中,列表组件(<list>)和流式布局控件(<richtext>)的点击事件相对特殊,下文分别介绍。
list组件
<list>标签的onclick点击事件同上,但列表项<item>的点击事件相对特殊,如果列表项是通过n-for指令循环渲染的,则在点击回调函数中,除了event参数外,还有item、index两个参数,分别表示当前列表项的数据及列表项索引,详细参考NView模板绘制原生列表教程。
richtext布局控件
<richtext>布局控件也支持监听点击事件,配置方式同上;但<richtext>内部嵌套的子控件不支持监听onclick事件,用户点击<richtext>内部控件时,会向上冒泡到父容器<richtext>标签上,触发<richtext>的onclick回调。
同普通控件的点击回调,<richtext>的点击回调函数,自带一个event参数,通过event.detail可以访问当前NView模板中绑定的数据(即data对象);另外,还可以通过event访问点击目标对象的属性(tagName、href、src),具体情况如下:
- 如果点击的是<richtext>中的<a>标签,则回调函数event参数中包含tagName(值为"a")和href属性;
- 如果点击的是<richtext>中的<img>标签,则回调函数event参数中包含tagName(值为"img")和src属性;
- 如果点击的是<richtext>中的其它区域或控件,则回调函数event参数中包含tagName(值为"")
如下是一个示例代码:
<template>
<nviews cachemaxage="86400">
<nview style="height:100px;" >
<richtext onclick={this.handleRichText}>
<img src="logo.png" width="20px" height="20px"></img>
<a href="http://www.example.com">
http://www.example.com
</a>
</richtext>
</nview>
</nviews>
</template>
<script>
module.exports = {
methods: {
handleRichText:function(e){
console.log("tagName:" + e.tagName);
console.log("href:" + e.href);
console.log("src:" + e.src);
}
}
};
</script>
事件冒泡
NView模板中,点击事件默认会从子控件向父控件冒泡。
如下示例中,nview和button标签同时监听了点击事件,当用户点击button按钮时,会先弹框提醒“click button!”,然后点击事件向父容器冒泡,接着弹框提醒"click nview!"。
<template>
<nviews cachemaxage="86400">
<nview style="height:100px" onclick={this.clickNView}>
<canvas>
<button style="top:10px;left:20px;" onclick={this.clickBtn}>按钮</button>
<img style="top:10px;left:100px;" src="_www/img/demo.png"></img>
</canvas>
</nview>
</nviews>
</template>
<script>
module.exports = {
data: {
//默认数据
},
init: function(url) {
//动态初始化数据
},
methods: {
clickNView:function(event){//nview点击事件触发
alert("click nview!");
},
clickBtn:function(event){//按钮点击事件触发
alert("click button!");
}
}
};
</script>
如果想阻止事件冒泡,可以在子控件的点击函数增加return false语句,如下:
<template>
<nviews cachemaxage="86400">
<nview style="height:100px;" onclick={this.clickNView}>
<canvas>
<button style="top:10px;left:20px;" onclick={this.clickBtn}>按钮</button>
<img style="top:10px;left:100px;" src="_www/img/demo.png"></img>
</canvas>
</nview>
</nviews>
</template>
<script>
module.exports = {
data: {
//默认数据
},
init: function(url) {
//动态初始化数据
},
methods: {
clickNView:function(event){//nview点击事件触发
alert("click nview!");
},
clickBtn:function(event){//按钮点击事件触发
alert("click button!");
return false;//阻止事件冒泡
}
}
};
</script>
阻止事件冒泡后,用户再次点击button按钮,则会弹框提醒“click button!”,不会再弹框提醒"click nview!"。
事件拦截
subNView作为webview的一个子控件,覆盖在webview上方,当用户点击subNView控件时,subNView默认会拦截touch事件,不会将touch事件透传给下层的webview中的DOM元素。
wap2app支持通过设置nview标签的intercept属性配置touch事件是否拦截,默认为true,即拦截touch事件,下层webview中的DOM元素无法触发touch事件;如果设置为false,则不拦截touch事件,下层webview中的DOM元素将接收到用户的touch事件,从而触发原来HTML页面的touch事件逻辑。
如下示例代码中,nview1设置了不拦截touch事件,则在nview1区域点击时,都会透传给下方的webview页面中的DOM元素,触发DOM元素的点击事件.
<template>
<nviews cachemaxage="86400">
<nview id="nview1" style="position:static" intercept=“false”>
...
</nview>
</nviews>
</template>
<script>
module.exports = {
};
</script>
Tips:仅nview标签支持intercept属性设置。
收起阅读 »




