
如何用AST语法树对代码“动手脚”
作为程序猿,每天都在写代码,但是有没有想过通过代码对写好的代码”动点手脚”呢?今天就与大家分享——
先抛一个问题:如何将图一代码改写为图二?
图1
图2
此题需要把代码中和程序逻辑无关的字符串提取出来,替换为id。比如个推日志输出类,缩短日志描述信息后,输出的日志就随之变短,根据映射表可以恢复真实原始日志。
通过何种方案改写?
你可能会想通过万能的“正则表达式”匹配替换,但当代码较为复杂时(如下图所示),使用“正则表达法”则会将问题复杂化,难以确保所有代码的完美覆盖并匹配。若通过AST语法树,可以很好地解决此问题。
什么是AST语法树?
AST(Abstract syntax tree)即为“抽象语法树”,简称语法树,指代码在计算机内存的一种树状数据结构,便于计算机理解和阅读。
一般只有语言的编译器开发人员或者从事语言设计的人员才涉及到语法树的提取和处理,所以很多人会对这个概念比较陌生。
上图即为语法树,左边树的节点对应右边相同颜色覆盖的代码块。
众所周知,Java 编译流程(上图)中也有对AST语法树的提取处理,那是否可以在此环节操作语法树呢?由于编译链代码栈太深,鲜有对外的接口和文档,使得其可操作性不强。不过,如果采用迂回战术如下图所示,可以对其进行操作。
个推log-rewrite项目改写日志,就是用AST语法树进行的,流程图如下图所示。
先把所有源码解析为AST语法树,遍历每一个编译单元与单元的类声明,在类声明里根据日志方法的签名找到所有的方法调用,然后遍历每个方法调用,将方法调用的第二个参数表达式放入递归方法,对字符串字面值进行改写。
对应的代码较为简短, 使用github的 Netflix-Skunkworks/rewrite开源库与kotlin语言,能读懂Java的你也一定能读明白。
如果想将日志恢复原样,可根据前缀、后缀定制正则表达式,逐行匹配替换。如下图所示。
AST有哪些应用场景?
1、 编译工具从ant到gradle的切换
此项目起步于ant主流时期,随着技术日渐成熟,gradle逐渐取代了ant的位置,演变成官方的编译打包方式。因为历史原因,若直接将上图类似预编译的代码切换到gradle较为棘手,通过AST语法树重写,再用gradle编译,就可以解决此问题。
上图的#debug和#mdebug指令,也可以通过AST改写之后再进行编译。
2、 自动静态埋点
代码中需要运营统计、数据分析等,需要通过代码埋点进行用户行为数据收集。传统的做法是手动在代码中添加埋点代码,但此过程较为繁琐,可能会对业务代码造成干扰,倘若通过改写AST语法树,在编译打包期添加这种类似的埋点代码,就可减少不必要的繁琐过程,使其更加高效。
最后附推荐操作AST类库链接&完整项目源码地址,希望可以帮助大家打开脑洞,设想更多的应用场景。
推荐操作AST类库链接
https://github.com/Netflix-Skunkworks/rewrite
https://github.com/Javaparser/Javaparser
https://github.com/antlr/antlr4
完整项目源码地址如下,欢迎fork&start
https://github.com/foxundermoon/log-rewrite
作为程序猿,每天都在写代码,但是有没有想过通过代码对写好的代码”动点手脚”呢?今天就与大家分享——
先抛一个问题:如何将图一代码改写为图二?
图1
图2
此题需要把代码中和程序逻辑无关的字符串提取出来,替换为id。比如个推日志输出类,缩短日志描述信息后,输出的日志就随之变短,根据映射表可以恢复真实原始日志。
通过何种方案改写?
你可能会想通过万能的“正则表达式”匹配替换,但当代码较为复杂时(如下图所示),使用“正则表达法”则会将问题复杂化,难以确保所有代码的完美覆盖并匹配。若通过AST语法树,可以很好地解决此问题。
什么是AST语法树?
AST(Abstract syntax tree)即为“抽象语法树”,简称语法树,指代码在计算机内存的一种树状数据结构,便于计算机理解和阅读。
一般只有语言的编译器开发人员或者从事语言设计的人员才涉及到语法树的提取和处理,所以很多人会对这个概念比较陌生。
上图即为语法树,左边树的节点对应右边相同颜色覆盖的代码块。
众所周知,Java 编译流程(上图)中也有对AST语法树的提取处理,那是否可以在此环节操作语法树呢?由于编译链代码栈太深,鲜有对外的接口和文档,使得其可操作性不强。不过,如果采用迂回战术如下图所示,可以对其进行操作。
个推log-rewrite项目改写日志,就是用AST语法树进行的,流程图如下图所示。
先把所有源码解析为AST语法树,遍历每一个编译单元与单元的类声明,在类声明里根据日志方法的签名找到所有的方法调用,然后遍历每个方法调用,将方法调用的第二个参数表达式放入递归方法,对字符串字面值进行改写。
对应的代码较为简短, 使用github的 Netflix-Skunkworks/rewrite开源库与kotlin语言,能读懂Java的你也一定能读明白。
如果想将日志恢复原样,可根据前缀、后缀定制正则表达式,逐行匹配替换。如下图所示。
AST有哪些应用场景?
1、 编译工具从ant到gradle的切换
此项目起步于ant主流时期,随着技术日渐成熟,gradle逐渐取代了ant的位置,演变成官方的编译打包方式。因为历史原因,若直接将上图类似预编译的代码切换到gradle较为棘手,通过AST语法树重写,再用gradle编译,就可以解决此问题。
上图的#debug和#mdebug指令,也可以通过AST改写之后再进行编译。
2、 自动静态埋点
代码中需要运营统计、数据分析等,需要通过代码埋点进行用户行为数据收集。传统的做法是手动在代码中添加埋点代码,但此过程较为繁琐,可能会对业务代码造成干扰,倘若通过改写AST语法树,在编译打包期添加这种类似的埋点代码,就可减少不必要的繁琐过程,使其更加高效。
最后附推荐操作AST类库链接&完整项目源码地址,希望可以帮助大家打开脑洞,设想更多的应用场景。
推荐操作AST类库链接
https://github.com/Netflix-Skunkworks/rewrite
https://github.com/Javaparser/Javaparser
https://github.com/antlr/antlr4
完整项目源码地址如下,欢迎fork&start
https://github.com/foxundermoon/log-rewrite
收起阅读 »
DCloud APP升级代码的实现
//系统自动更新
update: function() {
if(window.plus) {
this.updateCheck();
} else {
document.addEventListener('plusready', this.updateCheck, false);
}
},
//系统升级检测
updateCheck: function() {
var appVer = ''; //app版本
var newVer = ''; //最新版本
//获得当前版本
plus.runtime.getProperty(plus.runtime.appid, function(inf) {
appVer = inf.version;
$('.copyright').html('湖南互联移动科技有限公司提供技术支持v' + appVer);
//发起新版本请求检测
sysCom.get(urlCom.apiUrl.update_path + "/version.html?v=" + (new Date().getTime()), {}, function(data) {
newVer = data.version;
if(appVer != newVer) {
var waiting = plus.nativeUI.showWaiting("正在下载数据更新资源包 0%...", {
width: '100%',
height: '100%',
back: 'none',
round: 1,
});
//模拟下载百分比动画
var _sec = 1;
var _add = 1;
var _filename = ''; //下载好的更新文件
var _timer = setInterval(function() {
_add = Math.floor(Math.random() * 10);
_sec += _add;
if(_sec >= 100) {
_sec = 99;
if(_filename) {
clearInterval(_timer);
waiting.setTitle('正在下载数据资源包 100%...');
// 安装更新包
plus.runtime.install(_filename, {}, function() {
waiting.setTitle('数据资源包下载完成,正在重启应用...');
//更新完毕删除更新包
plus.io.resolveLocalFileSystemURL(_filename, function(entity) {
entity.remove();
}, function(error) {
debugCom.log(error.message);
});
//应用重启并显示启动页
dataCom.remove(dataBase.names.data_guide);
setTimeout(function() {
plus.nativeUI.closeWaiting();
plus.runtime.restart();
}, 1000)
}, function(e) {
waiting.setTitle('更新失败:' + e.message);
});
}
}
waiting.setTitle('正在下载数据资源包 ' + _sec + '%...');
}, 500);
//--
//下载更新包
plus.downloader.createDownload(urlCom.apiUrl.update_path + "/" + newVer + ".wgt", {
filename: "_doc/update/"
}, function(d, status) {
if(status == 200) {
_filename = d.filename;
} else {
debugCom.log('下载资源包出现问题:' + status)
}
}).start();
//--
}
});
})
}
//系统自动更新
update: function() {
if(window.plus) {
this.updateCheck();
} else {
document.addEventListener('plusready', this.updateCheck, false);
}
},
//系统升级检测
updateCheck: function() {
var appVer = ''; //app版本
var newVer = ''; //最新版本
//获得当前版本
plus.runtime.getProperty(plus.runtime.appid, function(inf) {
appVer = inf.version;
$('.copyright').html('湖南互联移动科技有限公司提供技术支持v' + appVer);
//发起新版本请求检测
sysCom.get(urlCom.apiUrl.update_path + "/version.html?v=" + (new Date().getTime()), {}, function(data) {
newVer = data.version;
if(appVer != newVer) {
var waiting = plus.nativeUI.showWaiting("正在下载数据更新资源包 0%...", {
width: '100%',
height: '100%',
back: 'none',
round: 1,
});
//模拟下载百分比动画
var _sec = 1;
var _add = 1;
var _filename = ''; //下载好的更新文件
var _timer = setInterval(function() {
_add = Math.floor(Math.random() * 10);
_sec += _add;
if(_sec >= 100) {
_sec = 99;
if(_filename) {
clearInterval(_timer);
waiting.setTitle('正在下载数据资源包 100%...');
// 安装更新包
plus.runtime.install(_filename, {}, function() {
waiting.setTitle('数据资源包下载完成,正在重启应用...');
//更新完毕删除更新包
plus.io.resolveLocalFileSystemURL(_filename, function(entity) {
entity.remove();
}, function(error) {
debugCom.log(error.message);
});
//应用重启并显示启动页
dataCom.remove(dataBase.names.data_guide);
setTimeout(function() {
plus.nativeUI.closeWaiting();
plus.runtime.restart();
}, 1000)
}, function(e) {
waiting.setTitle('更新失败:' + e.message);
});
}
}
waiting.setTitle('正在下载数据资源包 ' + _sec + '%...');
}, 500);
//--
//下载更新包
plus.downloader.createDownload(urlCom.apiUrl.update_path + "/" + newVer + ".wgt", {
filename: "_doc/update/"
}, function(d, status) {
if(status == 200) {
_filename = d.filename;
} else {
debugCom.log('下载资源包出现问题:' + status)
}
}).start();
//--
}
});
})
}
收起阅读 »

DCloud APP中更改状态栏背景色和字体颜色
//更改状态栏颜色
mui.plusReady(function() {
plus.navigator.setStatusBarStyle("UIStatusBarStyleBlackOpaque");
plus.navigator.setStatusBarBackground('#1A2448');
})
//更改状态栏颜色
mui.plusReady(function() {
plus.navigator.setStatusBarStyle("UIStatusBarStyleBlackOpaque");
plus.navigator.setStatusBarBackground('#1A2448');
})
收起阅读 »

DCloud图片上传和压缩
define(['common', 'config'], function(com, con) {
/*
* 打开摄像头、打开图库 模块
*/
return {
//model对象
_model: {
result: true,
id: 0,
img: ""
},
//记录上传文件数
upTimer: {
count: 0,
imgs: []
},
//打开摄像头
open: function(obj, callback) {
my = this;
mui.plusReady(function() {
if($(obj).attr('data-camera-gallery') == "1") {
var a = [{
title: "拍照"
}, {
title: "从手机相册选择"
}];
plus.nativeUI.actionSheet({
//title: "修改用户头像",
cancel: "取消",
buttons: a
}, function(b) {
/*actionSheet 按钮点击事件*/
switch(b.index) {
case 0:
break;
case 1:
my.getImage(obj, callback);
break;
case 2:
my.galleryImg(obj, callback);
break;
default:
break;
}
});
} else {
my.getImage(obj, callback);
}
});
},
//拍照
getImage: function(obj, callback) {
var my = this;
var photoType = $(obj).attr("data-PhotoType") ? $(obj).attr("data-PhotoType") : "";
mui.plusReady(function() {
var data = {
result: false,
msg: '',
data: null
};
var c = plus.camera.getCamera();
c.captureImage(function(e) {
$(obj).css('opacity', '0.3').attr('disabled', true);
plus.io.resolveLocalFileSystemURL(e, function(entry) {
var s = entry.toLocalURL();
data.result = true;
data.data = s;
my.upload(obj, con.urlCONFIG.post_upload, s, callback, photoType);
}, function(e) {
data.data = "读取拍照文件错误:" + e.message;
callback(data);
});
}, function(error) {
data.msg = error.message;
//callback(data);
})
});
},
//打开相册
galleryImg: function(obj, callback) {
var my = this;
plus.gallery.pick(function(a) {
var photoType = $(obj).attr("data-PhotoType") ? $(obj).attr("data-PhotoType") : "";
var data = {
result: false,
msg: '',
data: null
};
$(obj).css('opacity', '0.3').attr('disabled', true);
plus.io.resolveLocalFileSystemURL(a, function(entry) {
var s = entry.toLocalURL();
data.result = true;
data.data = s;
my.upload(obj, com.urlCONFIG.post_upload, s, callback, photoType);
}, function(e) {
data.data = "读取拍照文件错误:" + e.message;
callback(data);
});
}, function(a) {}, {
filter: "image"
})
},
//上传图片到服务器
upload: function(obj, url, filename, callback, photoType) {
var my = this;
my.upTimer.count++;
var tips = com.msg(my.upTimer.count + '个文件正在上传', 999);
mui.plusReady(function() {
var task = plus.uploader.createUpload(url, {
method: "POST",
blocksize: 0,
priority: 100
},
function(t, status) {
$(obj).css('opacity', '1').removeAttr('disabled');
var json_data = t.responseText.replace('null(','').replace(');null','');
//console.log(json_data)
var data = JSON.parse(json_data);
if(data) {
// 上传完成
if(status == 200) {
my.upTimer.count--;
if(my.upTimer.count <= 0) {
com.msg('所有图片上传完毕', 1);
}
var model = _.clone(my._model);
model.id = new Date().getTime();
model.img = data.data;
my.upTimer.imgs.push(model);
callback(model);
} else {
callback(data);
}
} else {
com.msg('上传失败:' + data.msg, 0);
}
}
);
//图片压缩
my.resize(filename, function(zipSrc) {
task.addFile(zipSrc, {
key: "upload"
});
task.addData(zipSrc, zipSrc);
task.start();
});
});
},
//压缩(需要获取本地文件权限)
resize: function(src, callback) {
var filename = src.substring(src.lastIndexOf('/') + 1);
plus.zip.compressImage({
src: src,
dst: '_doc/' + filename,
overwrite: true,
width: '1000px', //这里指定了宽度,同样可以修改
format: 'jpg',
quality: 90 //图片质量不再修改,以免失真
},
function(e) {
callback(e.target);
},
function(err) {
com.alert('未知错误!', 0, function() {
mui.back();
})
})
}
};
})
define(['common', 'config'], function(com, con) {
/*
* 打开摄像头、打开图库 模块
*/
return {
//model对象
_model: {
result: true,
id: 0,
img: ""
},
//记录上传文件数
upTimer: {
count: 0,
imgs: []
},
//打开摄像头
open: function(obj, callback) {
my = this;
mui.plusReady(function() {
if($(obj).attr('data-camera-gallery') == "1") {
var a = [{
title: "拍照"
}, {
title: "从手机相册选择"
}];
plus.nativeUI.actionSheet({
//title: "修改用户头像",
cancel: "取消",
buttons: a
}, function(b) {
/*actionSheet 按钮点击事件*/
switch(b.index) {
case 0:
break;
case 1:
my.getImage(obj, callback);
break;
case 2:
my.galleryImg(obj, callback);
break;
default:
break;
}
});
} else {
my.getImage(obj, callback);
}
});
},
//拍照
getImage: function(obj, callback) {
var my = this;
var photoType = $(obj).attr("data-PhotoType") ? $(obj).attr("data-PhotoType") : "";
mui.plusReady(function() {
var data = {
result: false,
msg: '',
data: null
};
var c = plus.camera.getCamera();
c.captureImage(function(e) {
$(obj).css('opacity', '0.3').attr('disabled', true);
plus.io.resolveLocalFileSystemURL(e, function(entry) {
var s = entry.toLocalURL();
data.result = true;
data.data = s;
my.upload(obj, con.urlCONFIG.post_upload, s, callback, photoType);
}, function(e) {
data.data = "读取拍照文件错误:" + e.message;
callback(data);
});
}, function(error) {
data.msg = error.message;
//callback(data);
})
});
},
//打开相册
galleryImg: function(obj, callback) {
var my = this;
plus.gallery.pick(function(a) {
var photoType = $(obj).attr("data-PhotoType") ? $(obj).attr("data-PhotoType") : "";
var data = {
result: false,
msg: '',
data: null
};
$(obj).css('opacity', '0.3').attr('disabled', true);
plus.io.resolveLocalFileSystemURL(a, function(entry) {
var s = entry.toLocalURL();
data.result = true;
data.data = s;
my.upload(obj, com.urlCONFIG.post_upload, s, callback, photoType);
}, function(e) {
data.data = "读取拍照文件错误:" + e.message;
callback(data);
});
}, function(a) {}, {
filter: "image"
})
},
//上传图片到服务器
upload: function(obj, url, filename, callback, photoType) {
var my = this;
my.upTimer.count++;
var tips = com.msg(my.upTimer.count + '个文件正在上传', 999);
mui.plusReady(function() {
var task = plus.uploader.createUpload(url, {
method: "POST",
blocksize: 0,
priority: 100
},
function(t, status) {
$(obj).css('opacity', '1').removeAttr('disabled');
var json_data = t.responseText.replace('null(','').replace(');null','');
//console.log(json_data)
var data = JSON.parse(json_data);
if(data) {
// 上传完成
if(status == 200) {
my.upTimer.count--;
if(my.upTimer.count <= 0) {
com.msg('所有图片上传完毕', 1);
}
var model = _.clone(my._model);
model.id = new Date().getTime();
model.img = data.data;
my.upTimer.imgs.push(model);
callback(model);
} else {
callback(data);
}
} else {
com.msg('上传失败:' + data.msg, 0);
}
}
);
//图片压缩
my.resize(filename, function(zipSrc) {
task.addFile(zipSrc, {
key: "upload"
});
task.addData(zipSrc, zipSrc);
task.start();
});
});
},
//压缩(需要获取本地文件权限)
resize: function(src, callback) {
var filename = src.substring(src.lastIndexOf('/') + 1);
plus.zip.compressImage({
src: src,
dst: '_doc/' + filename,
overwrite: true,
width: '1000px', //这里指定了宽度,同样可以修改
format: 'jpg',
quality: 90 //图片质量不再修改,以免失真
},
function(e) {
callback(e.target);
},
function(err) {
com.alert('未知错误!', 0, function() {
mui.back();
})
})
}
};
})
收起阅读 »

DCloud 定位代码
define(['common'], function(com) {
/*
* GPS定位模块
*/
return {
/**
* @constructor
* @description 得到定位信息
*/
getlocation: function(callback) {
mui.plusReady(function() {
var data = {
result: false,
code: '',
msg: '',
position: null
};
plus.geolocation.getCurrentPosition(function(p) {
//debugCom.log(JSON.stringify(p))
data.result = true;
data.position = p;
data.code = 0;
data.msg = '';
//回调
callback(data);
}, function(e) {
console.log(JSON.stringify(e))
data.result = false;
data.code = e.code;
switch(e.code) {
case 1:
data.msg = "GPS访问被拒绝 或 GPS未开启";
break;
case 2:
data.msg = "位置信息不可用";
break;
case 3:
data.msg = "获取用户位置的请求超时";
break;
default:
data.msg = e.message;
break;
}
if(data.msg == '')
data.msg = "获取用户位置的请求超时";
//回调
callback(data);
}, {
provider: 'baidu'
});
/*
provider: (String 类型 )优先使用的定位模块可取以下供应者: "system":表示系统定位模块,支持wgs84坐标系; "baidu":表示百度定位模块,支持gcj02/bd09/bd09ll坐标系; "amap":表示高德定位模板,支持gcj02坐标系。 默认值按以下优先顺序获取(amap>baidu>system),若指定的provider不存在或无效则返回错误回调。 注意:百度/高德定位模块需要配置百度/高德地图相关参数才能正常使用。
* */
});
},
/**
* @constructor
* @description 得到地址
*/
getaddress: function(callback) {
this.getlocation(function(data) {
var resdata = {
result: false,
msg: '',
data: ''
}
if(data.result) {
resdata.result = true;
resdata.data = data.position.addresses;
callback(resdata);
} else {
resdata.msg = data.msg;
callback(resdata);
}
})
}
};
});
define(['common'], function(com) {
/*
* GPS定位模块
*/
return {
/**
* @constructor
* @description 得到定位信息
*/
getlocation: function(callback) {
mui.plusReady(function() {
var data = {
result: false,
code: '',
msg: '',
position: null
};
plus.geolocation.getCurrentPosition(function(p) {
//debugCom.log(JSON.stringify(p))
data.result = true;
data.position = p;
data.code = 0;
data.msg = '';
//回调
callback(data);
}, function(e) {
console.log(JSON.stringify(e))
data.result = false;
data.code = e.code;
switch(e.code) {
case 1:
data.msg = "GPS访问被拒绝 或 GPS未开启";
break;
case 2:
data.msg = "位置信息不可用";
break;
case 3:
data.msg = "获取用户位置的请求超时";
break;
default:
data.msg = e.message;
break;
}
if(data.msg == '')
data.msg = "获取用户位置的请求超时";
//回调
callback(data);
}, {
provider: 'baidu'
});
/*
provider: (String 类型 )优先使用的定位模块可取以下供应者: "system":表示系统定位模块,支持wgs84坐标系; "baidu":表示百度定位模块,支持gcj02/bd09/bd09ll坐标系; "amap":表示高德定位模板,支持gcj02坐标系。 默认值按以下优先顺序获取(amap>baidu>system),若指定的provider不存在或无效则返回错误回调。 注意:百度/高德定位模块需要配置百度/高德地图相关参数才能正常使用。
* */
});
},
/**
* @constructor
* @description 得到地址
*/
getaddress: function(callback) {
this.getlocation(function(data) {
var resdata = {
result: false,
msg: '',
data: ''
}
if(data.result) {
resdata.result = true;
resdata.data = data.position.addresses;
callback(resdata);
} else {
resdata.msg = data.msg;
callback(resdata);
}
})
}
};
});
收起阅读 »

Dcloud APP中实现微信登录第三方登录获取用户信息
【参考资料】
- 授权登录插件配置:http://ask.dcloud.net.cn/article/192
- OAuth模块管理客户端的用户登录授权验证功能,允许应用访问第三方平台的资源:http://www.html5plus.org/doc/zh_cn/oauth.html
【代码封装】
define([], function() {
return {
_getServices: function(fun) {
mui.plusReady(function() {
plus.oauth.getServices(function(services) {
fun(services);
}, function(e) {
debugCom.log("获取分享服务列表失败:" + e.message + " - " + e.code);
return null;
});
})
},
login: function() {
this._getServices(function(auths) {
var s = auths[0];
if(!s.authResult) {
s.login(function(e) {
// 获取登录操作结果
s.getUserInfo(function(e) {
debugCom.log("获取用户信息成功:" + JSON.stringify(s.userInfo));
}, function(e) {
debugCom.log("获取用户信息失败:" + e.message + " - " + e.code);
});
}, function(e) {
debugCom.log("登录认证失败!");
});
} else {
debugCom.log("已经登录认证!");
}
})
}
}
})
【获取到的结果如下】:
{
"openid":"oRrdQt_8w0_WBlaSFoe-lENTxhvg",
"nickname":"大能猫灬Michael",
"sex":1,
"language":"zh_CN",
"city":"长宁",
"province":"上海",
"country":"中国",
"headimgurl":"http://wx.qlogo.cn/mmopen/ajNVdqHZLLCeX1pAR5AGjdTtIjHWgcUJmN1CSTGB4GrY3iccKmV5UkxxVrFZ0gPjn71Me758aO7aMxpicCGDr6ww/0",
"privilege":[],
"unionid":"oU5Yyt26Id2qvDXpBdfT9NbohmCM"
}
【参考资料】
- 授权登录插件配置:http://ask.dcloud.net.cn/article/192
- OAuth模块管理客户端的用户登录授权验证功能,允许应用访问第三方平台的资源:http://www.html5plus.org/doc/zh_cn/oauth.html
【代码封装】
define([], function() {
return {
_getServices: function(fun) {
mui.plusReady(function() {
plus.oauth.getServices(function(services) {
fun(services);
}, function(e) {
debugCom.log("获取分享服务列表失败:" + e.message + " - " + e.code);
return null;
});
})
},
login: function() {
this._getServices(function(auths) {
var s = auths[0];
if(!s.authResult) {
s.login(function(e) {
// 获取登录操作结果
s.getUserInfo(function(e) {
debugCom.log("获取用户信息成功:" + JSON.stringify(s.userInfo));
}, function(e) {
debugCom.log("获取用户信息失败:" + e.message + " - " + e.code);
});
}, function(e) {
debugCom.log("登录认证失败!");
});
} else {
debugCom.log("已经登录认证!");
}
})
}
}
})
【获取到的结果如下】:
{
"openid":"oRrdQt_8w0_WBlaSFoe-lENTxhvg",
"nickname":"大能猫灬Michael",
"sex":1,
"language":"zh_CN",
"city":"长宁",
"province":"上海",
"country":"中国",
"headimgurl":"http://wx.qlogo.cn/mmopen/ajNVdqHZLLCeX1pAR5AGjdTtIjHWgcUJmN1CSTGB4GrY3iccKmV5UkxxVrFZ0gPjn71Me758aO7aMxpicCGDr6ww/0",
"privilege":[],
"unionid":"oU5Yyt26Id2qvDXpBdfT9NbohmCM"
}

【示例】html5plus应用实现沉浸式状态栏
开启沉浸式状态栏
沉浸式状态不支持动态设置,属于应用级的。此类参数信息,通常在manifest.json中配置。
manifest.json->plus节点下
"statusbar": {
"immersed": true //开启沉浸式状态栏
}
配置后,运行看到首页是这样的。
我的天,Webview里面的内容上天了,这显然不是我们期望的沉浸式状态栏的样子。
设置Webview的状态栏颜色
上面这种情况,在html5plus应用开发中,可以通过设置Webview的状态栏样式,模拟出一个状态条来填充状态栏部分的空间。
终端支持
- Android5及以上系统支持
- iOS7.0及以上系统支持
首页
无论是launchwebview还是secondwebview,都是在manifest.json中配置相关的参数信息。
manifest.json->plus->launchwebview
"launchwebview": {
"statusbar": {
"background": "#FF3333"
}
}
此时,再次运行,我们看到的效果是这样的。
Webview的内容出现在了正常的位置,这才是正确的样子。
普通Webview
普通的Webview,可以在创建的时候,设置statusbar的background来填充状态栏颜色。
webview = plus.webview.create('test.html', 'test', {
statusbar: {
background: '#1478ee'
}
});
有titleNView的Webview
如果Webview配置了titleNView,创建Webview时,会取titleNView下的backgroundColor值,填充状态栏颜色。
webview = plus.webview.create('test.html', 'test', {
titleNView: {
titleText: '测试页',
backgroundColor: '#fb6f18'
}
});
效果是这样的
为了区分开来,这里特地使用了不同的颜色。
Webview窗口的系统状态栏区域样式
窗口标题栏控件样式
前景色
调整状态栏颜色后,有时会出现系统状态栏部分的文字不清楚的情况。这时候,需要调调整前景色。
前景色的设置,受到的限制更多,只有“dark”和“light”两个选项。
同时前景色处理在终端支持方面
- Android5只有小米和魅族支持,Android6及以上所有安卓支持
- iOS7及以上支持
manifest.json配置
manifest.json->plus->statusbar
"statusbar": {
"style": "dark"
}
调用API
也可以通过调用相关API进行设置调整,设置系统状态栏样式。
plus.navigator.setStatusBarStyle('dark');
注意事项
- 黑白灰等以及接近黑白灰的颜色不支持设置为状态栏前景色,防止与前景色一致导致状态栏文字看不到。
更多
更多关于状态栏的设置,参考状态栏大全-状态栏透明(沉浸式)、变色及全屏的区别等相关文章。
附件中提供了一个最简的沉浸式状态栏示例工程,下载后真机运行即可体验。
开启沉浸式状态栏
沉浸式状态不支持动态设置,属于应用级的。此类参数信息,通常在manifest.json中配置。
manifest.json->plus节点下
"statusbar": {
"immersed": true //开启沉浸式状态栏
}
配置后,运行看到首页是这样的。
我的天,Webview里面的内容上天了,这显然不是我们期望的沉浸式状态栏的样子。
设置Webview的状态栏颜色
上面这种情况,在html5plus应用开发中,可以通过设置Webview的状态栏样式,模拟出一个状态条来填充状态栏部分的空间。
终端支持
- Android5及以上系统支持
- iOS7.0及以上系统支持
首页
无论是launchwebview还是secondwebview,都是在manifest.json中配置相关的参数信息。
manifest.json->plus->launchwebview
"launchwebview": {
"statusbar": {
"background": "#FF3333"
}
}
此时,再次运行,我们看到的效果是这样的。
Webview的内容出现在了正常的位置,这才是正确的样子。
普通Webview
普通的Webview,可以在创建的时候,设置statusbar的background来填充状态栏颜色。
webview = plus.webview.create('test.html', 'test', {
statusbar: {
background: '#1478ee'
}
});
有titleNView的Webview
如果Webview配置了titleNView,创建Webview时,会取titleNView下的backgroundColor值,填充状态栏颜色。
webview = plus.webview.create('test.html', 'test', {
titleNView: {
titleText: '测试页',
backgroundColor: '#fb6f18'
}
});
效果是这样的
为了区分开来,这里特地使用了不同的颜色。
Webview窗口的系统状态栏区域样式
窗口标题栏控件样式
前景色
调整状态栏颜色后,有时会出现系统状态栏部分的文字不清楚的情况。这时候,需要调调整前景色。
前景色的设置,受到的限制更多,只有“dark”和“light”两个选项。
同时前景色处理在终端支持方面
- Android5只有小米和魅族支持,Android6及以上所有安卓支持
- iOS7及以上支持
manifest.json配置
manifest.json->plus->statusbar
"statusbar": {
"style": "dark"
}
调用API
也可以通过调用相关API进行设置调整,设置系统状态栏样式。
plus.navigator.setStatusBarStyle('dark');
注意事项
- 黑白灰等以及接近黑白灰的颜色不支持设置为状态栏前景色,防止与前景色一致导致状态栏文字看不到。
更多
更多关于状态栏的设置,参考状态栏大全-状态栏透明(沉浸式)、变色及全屏的区别等相关文章。
附件中提供了一个最简的沉浸式状态栏示例工程,下载后真机运行即可体验。

【讲座】html5+ App开发之 Android 平台离线集成 5+ SDK
html5+ App开发过程中有时候会遇到一些功能,单纯通过 html5 或者 5+ 规范无法满足要求,比如自定义相机这种定制性较强的功能,这时候不得不借助第三方库或者原生插件来完成,本次讲座主要面向的就是需要定制性开发的朋友。
本次讲座的主要内容:
- 简单介绍 Android 开发环境
- html5+ SDK 两种集成方式实例讲解
- 浅析 Android 原生 和 JavaScript 交互原理
- Native.js 实例讲解
- html5+ 插件开发实例详解
本节课时长80分钟左右。
面向人群:
- 想学习原生开发的前端开发朋友
- 想快速集成 5+ sdk 的原生开发朋友
往期系列讲座:
> 限时特价票:html5+ App开发之 Android 平台离线集成 5+ SDK
上次参与讲座的可以私聊我获取半价券。
html5+ App开发过程中有时候会遇到一些功能,单纯通过 html5 或者 5+ 规范无法满足要求,比如自定义相机这种定制性较强的功能,这时候不得不借助第三方库或者原生插件来完成,本次讲座主要面向的就是需要定制性开发的朋友。
本次讲座的主要内容:
- 简单介绍 Android 开发环境
- html5+ SDK 两种集成方式实例讲解
- 浅析 Android 原生 和 JavaScript 交互原理
- Native.js 实例讲解
- html5+ 插件开发实例详解
本节课时长80分钟左右。
面向人群:
- 想学习原生开发的前端开发朋友
- 想快速集成 5+ sdk 的原生开发朋友
往期系列讲座:
> 限时特价票:html5+ App开发之 Android 平台离线集成 5+ SDK
上次参与讲座的可以私聊我获取半价券。
收起阅读 »
小米推送ios内MiSDKRun设置问题
最近在调试小米推送。发现ios版一直不好使。咨询了小米的工作人员,被告知是MiSDKRun的配置不对,
测试环境应为debug,正式环境应为online。但是云打包貌似一直都是online。导致测试环境下无法接收到
推送消息。请问这个如何解决?
最近在调试小米推送。发现ios版一直不好使。咨询了小米的工作人员,被告知是MiSDKRun的配置不对,
测试环境应为debug,正式环境应为online。但是云打包貌似一直都是online。导致测试环境下无法接收到
推送消息。请问这个如何解决?

h5+ 地图API 视频教程已经发布
h5+ 地图API 视频教程已经发布,观看地址
http://www.hcoder.net/course/index/cate/4
更多视频教程:
mui、h5+、app项目实战教程,请点击
http://www.hcoder.net/course/index/cate/4
h5+ 地图API 视频教程已经发布,观看地址
http://www.hcoder.net/course/index/cate/4
更多视频教程:
mui、h5+、app项目实战教程,请点击
http://www.hcoder.net/course/index/cate/4

技术交流群,旨在互相解决问题。反灌水,反乱聊。
QQ群号:194178496
欢迎开发中遇到的问题和乐意为别人提供帮助的请加群!
点击链接加入群【HBuilder+MUI技术交流群】:https://jq.qq.com/?_wv=1027&k=4BF2dhv
QQ群号:194178496
欢迎开发中遇到的问题和乐意为别人提供帮助的请加群!
点击链接加入群【HBuilder+MUI技术交流群】:https://jq.qq.com/?_wv=1027&k=4BF2dhv