深入理解高度。获取屏幕、webview、软键盘高度
如果没有手动调整过webview的高度的话,默认情况下,屏幕的高度=顶部状态栏的高度+webview的高度。
如果软键盘打开,则屏幕的高度=顶部状态栏的高度+webview的高度+软键盘的高度。
HTML5 规范目前没有提供状态栏高度和软键盘高度的直接的查询方法,不过有了Native.js,我们还是能查询到所有这些高度的数值。
背景知识介绍:
手机屏幕有真实的物理分辨率,比如小米note的高度是1920px。
但在网页里,一个10px的字体,并不会小的看不清,因为webview提供了逻辑分辨率的概念。
如果不在meta里设置,默认下小米note的放大系数scale是3,就是会放大3倍显示。
也就是对于HTML而言,小米note的高度是1920/3=640px。
如果网页是全屏的,没有顶部状态栏,那么一个640px高的div将撑满屏幕高度。
获取屏幕、顶部状态栏和软键盘的高度
1. 屏幕的高度
获取屏幕的高度很简单,HTML自带了screen.height,直接就可以得到屏幕的整体高度,单位是px,物理分辨率值(不是HTML的逻辑分辨率)。
HTML5Plus里提供了plus.screen.resolutionHeight方法,也是屏幕高度,但这个值是逻辑分辨率的高度。参考[http://html5plus.org/doc/zh_cn/device.html#plus.screen](http://html5plus.org/doc/zh_cn/device.html#plus.screen)
screen.height = plus.screen.resolutionHeight*plus.screen.scale
2. 获取webview高度
虽然webview的getStyle可以得到webview的高度,但开发者如果不手动设置的话,默认值是100%,这个值对本文探讨的问题没有意义。我们需要px的物理高度。
在Android里通过如下js代码可以得到webview的高度:plus.android.invoke(plus.android.currentWebview(),"getHeight")
这是一段Native.js代码,由于Android的webview原生对象就有getHeight()方法,所以就可以直接这样调用。
当然webview原生对象还有很多方法属性都可以调,具体参阅[Native.js入门](http://ask.dcloud.net.cn/docs/#http://ask.dcloud.net.cn/article/88)
图1(此图中,物理分辨率是蓝色字体,逻辑分辨率是黑色字体)
对于iOS,就没必要使用Native.js这么复杂的技术了,iOS的屏幕高度是固定的几个,直接判断屏幕高度和设备型号就可以了。
3. 获取状态栏高度
顶部状态栏比较复杂,如果webview设了全屏,那么状态栏高度就为0。
如果设置了沉浸式状态栏,状态栏透明了,虽然状态栏存在理论高度,但webview高度是全屏的。
plus.navigator.getStatusbarHeight(),这个api专门获取状态栏高度。
4. 获取输入法高度
当弹出输入法时,在Android上,webview的高度会自动减少,留出空间给软键盘。
那么输入法高度=屏幕高度-状态栏高度-webview高度
在小米note上,默认输入法高度是 863px。
图2(此图中,物理分辨率是蓝色字体,逻辑分辨率是黑色字体)
5. 流应用任务栏高度
在流应用环境中,比如iOS流应用,会有一个流应用任务栏,这个任务栏的出现会挤压webview的高度。
如果开发者要设置一个元素居底,请使用bottom方法,而不是通过屏幕高度-webview高度设top,以避免出现流应用任务栏时高度错位。
说说HTML里的几个高度
其实HTML自己也提供了很多高度相关的api,除了screen.height还有document.body.clientHeight,document.body.scrollHeight,感觉也很容易混淆。
document.body.clientHeight是网页内容区的有效高度,符合盒模型。document.body.scrollHeight是网页可滚动区域的高度。
如图2所示:
- 此时document.body.clientHeight小于webview高度,而document.body.scrollHeight等于webview高度,即document.body.scrollHeight=plus.android.invoke(plus.android.currentWebview(),"getHeight") 。
- 如果数字不是1-5,而是1-10,那么document.body.clientHeight会继续增加,但内容不够多,不会滚动,document.body.scrollHeight不变,仍是webview高度。
- 如果网页内容非常多,数字到了40,那document.body.clientHeight会继续增加,并且只要发生了滚动,则document.body.clientHeight=document.body.scrollHeight
其他相关知识。
- 监控软键盘是否弹出,Android上可以使用HTML自带的resize事件。
但是要判断横竖屏,因为横竖屏切换也会造成resize,所以高度变而宽度不变,就是软键盘弹出或消失了。 - 强制弹出键盘,也有Native.js示例,http://ask.dcloud.net.cn/question/2324
- hello uni-app和hello mui都有聊天示例模板,有底部输入框跟随键盘顶起的示例,可以参考下。
如果没有手动调整过webview的高度的话,默认情况下,屏幕的高度=顶部状态栏的高度+webview的高度。
如果软键盘打开,则屏幕的高度=顶部状态栏的高度+webview的高度+软键盘的高度。
HTML5 规范目前没有提供状态栏高度和软键盘高度的直接的查询方法,不过有了Native.js,我们还是能查询到所有这些高度的数值。
背景知识介绍:
手机屏幕有真实的物理分辨率,比如小米note的高度是1920px。
但在网页里,一个10px的字体,并不会小的看不清,因为webview提供了逻辑分辨率的概念。
如果不在meta里设置,默认下小米note的放大系数scale是3,就是会放大3倍显示。
也就是对于HTML而言,小米note的高度是1920/3=640px。
如果网页是全屏的,没有顶部状态栏,那么一个640px高的div将撑满屏幕高度。
获取屏幕、顶部状态栏和软键盘的高度
1. 屏幕的高度
获取屏幕的高度很简单,HTML自带了screen.height,直接就可以得到屏幕的整体高度,单位是px,物理分辨率值(不是HTML的逻辑分辨率)。
HTML5Plus里提供了plus.screen.resolutionHeight方法,也是屏幕高度,但这个值是逻辑分辨率的高度。参考[http://html5plus.org/doc/zh_cn/device.html#plus.screen](http://html5plus.org/doc/zh_cn/device.html#plus.screen)
screen.height = plus.screen.resolutionHeight*plus.screen.scale
2. 获取webview高度
虽然webview的getStyle可以得到webview的高度,但开发者如果不手动设置的话,默认值是100%,这个值对本文探讨的问题没有意义。我们需要px的物理高度。
在Android里通过如下js代码可以得到webview的高度:plus.android.invoke(plus.android.currentWebview(),"getHeight")
这是一段Native.js代码,由于Android的webview原生对象就有getHeight()方法,所以就可以直接这样调用。
当然webview原生对象还有很多方法属性都可以调,具体参阅[Native.js入门](http://ask.dcloud.net.cn/docs/#http://ask.dcloud.net.cn/article/88)
图1(此图中,物理分辨率是蓝色字体,逻辑分辨率是黑色字体)
对于iOS,就没必要使用Native.js这么复杂的技术了,iOS的屏幕高度是固定的几个,直接判断屏幕高度和设备型号就可以了。
3. 获取状态栏高度
顶部状态栏比较复杂,如果webview设了全屏,那么状态栏高度就为0。
如果设置了沉浸式状态栏,状态栏透明了,虽然状态栏存在理论高度,但webview高度是全屏的。
plus.navigator.getStatusbarHeight(),这个api专门获取状态栏高度。
4. 获取输入法高度
当弹出输入法时,在Android上,webview的高度会自动减少,留出空间给软键盘。
那么输入法高度=屏幕高度-状态栏高度-webview高度
在小米note上,默认输入法高度是 863px。
图2(此图中,物理分辨率是蓝色字体,逻辑分辨率是黑色字体)
5. 流应用任务栏高度
在流应用环境中,比如iOS流应用,会有一个流应用任务栏,这个任务栏的出现会挤压webview的高度。
如果开发者要设置一个元素居底,请使用bottom方法,而不是通过屏幕高度-webview高度设top,以避免出现流应用任务栏时高度错位。
说说HTML里的几个高度
其实HTML自己也提供了很多高度相关的api,除了screen.height还有document.body.clientHeight,document.body.scrollHeight,感觉也很容易混淆。
document.body.clientHeight是网页内容区的有效高度,符合盒模型。document.body.scrollHeight是网页可滚动区域的高度。
如图2所示:
- 此时document.body.clientHeight小于webview高度,而document.body.scrollHeight等于webview高度,即document.body.scrollHeight=plus.android.invoke(plus.android.currentWebview(),"getHeight") 。
- 如果数字不是1-5,而是1-10,那么document.body.clientHeight会继续增加,但内容不够多,不会滚动,document.body.scrollHeight不变,仍是webview高度。
- 如果网页内容非常多,数字到了40,那document.body.clientHeight会继续增加,并且只要发生了滚动,则document.body.clientHeight=document.body.scrollHeight
其他相关知识。
- 监控软键盘是否弹出,Android上可以使用HTML自带的resize事件。
但是要判断横竖屏,因为横竖屏切换也会造成resize,所以高度变而宽度不变,就是软键盘弹出或消失了。 - 强制弹出键盘,也有Native.js示例,http://ask.dcloud.net.cn/question/2324
- hello uni-app和hello mui都有聊天示例模板,有底部输入框跟随键盘顶起的示例,可以参考下。
手动检测版本更新的代码
官方提供了自动检测更新的代码,我这里稍微更改了一下,可以手动检测更新。
不过建议使用App资源在线升级更新
我这里也提供了自动+手动的App资源在线升级
<ul class="mui-table-view" style="margin-top: 25px;">
<li class="mui-table-view-cell">
<a id="onlineupgrade">
在线升级 <span class="mui-pull-right" id="supgrade" style="color: red;display: none;">
New
</span>
</a>
</li>
</ul>
mui.plusReady(function() {
var supgrade = document.getElementById("supgrade");
//有新版本显示提示
mui.initUpdate(function() {
supgrade.style.display = "inline-block";
});
document.getElementById("onlineupgrade").addEventListener('tap', function() {
if (supgrade.style.display != "none") {
mui.initUpdate();
}
});
});
(function($) {
//正式上线了请更改app更新地址
var server = "http://www.dcloud.io/helloh5/update.json", //获取升级描述文件服务器地址
localDir = "update",
localFile = "update.json", //本地保存升级描述目录和文件名
keyUpdate = "updateCheck", //取消升级键名
keyAbort = "updateAbort", //忽略版本键名
checkInterval = 3600000, //升级检查间隔,单位为ms,1小时为60*60*1000=3600000, 如果每次启动需要检查设置值为0
dir = null;
/**
* 准备升级操作
* 创建升级文件保存目录
* @param{Function} 有回调函数是手动检查折升级
*/
$.initUpdate = function(callback) {
// 打开doc根目录
plus.io.requestFileSystem(plus.io.PRIVATE_DOC, function(fs) {
fs.root.getDirectory(localDir, {
create: true
}, function(entry) {
dir = entry;
checkUpdate(callback);
}, function(e) {
console.log("准备升级操作,打开update目录失败:" + e.message);
});
}, function(e) {
console.log("准备升级操作,打开doc目录失败:" + e.message);
});
}
/**
* 检测程序升级
* @author liuyf 2015-04-27
* @description 手动检测是否升级 是否已过期等标记在手动下无效
*
* @param{Function} 有回调函数是手动检查折升级
* */
function checkUpdate(callback) {
// 判断升级检测是否过期
var lastcheck = plus.storage.getItem(keyUpdate);
if (lastcheck) {
// 取消已过期,删除取消标记
plus.storage.removeItem(keyUpdate);
}
// 读取本地升级文件]
dir.getFile(localFile, {
create: false
}, function(fentry) {
fentry.file(function(file) {
var reader = new plus.io.FileReader();
reader.onloadend = function(e) {
//检测升级的时候不删除本地升级文件
if (!callback) {
fentry.remove();
}
var data = null;
try {
data = JSON.parse(e.target.result);
} catch (e) {
console.log("读取本地升级文件,数据格式错误!");
}
checkUpdateData(data, callback);
}
reader.readAsText(file);
}, function(e) {
console.log("读取本地升级文件,获取文件对象失败:" + e.message);
fentry.remove();
});
}, function(e) {
// 失败表示文件不存在,从服务器获取升级数据
getUpdateData();
});
}
/**
* 检查升级数据
* @author liuyf 2015-04-27
* @description 手动升级 是否存在版本号等逻辑应舍弃
* @param{Function} 有回调函数是手动检查升级
*/
function checkUpdateData(j, callback) {
//当前客户端版本号
var curVer = plus.runtime.version,
inf = j[plus.os.name];
if (inf) {
var srvVer = inf.version;
// 判断是否需要升级
if (compareVersion(curVer, srvVer)) {
if (!callback) {
// 提示用户是否升级
plus.ui.confirm(inf.note, function(i) {
if (0 == i)
plus.runtime.openURL(inf.url);
else {
plus.storage.setItem(keyUpdate, (new Date()).getTime().toString());
}
}, inf.title, ["立即更新", "取 消"]);
} else {
callback();
}
}
}
}
/**
* 从服务器获取升级数据,并存储到本地;
*/
function getUpdateData() {
mui.getJSON(server, {}, function(data) {
//appid一致,才将服务器上的版本数据保存到本地
if (data.appid == plus.runtime.appid) {
// 保存到本地文件中
dir.getFile(localFile, {
create: true
}, function(fentry) {;
fentry.createWriter(function(writer) {
writer.onerror = function() {
console.log("获取升级数据,保存文件失败!");
}
writer.write(data);
}, function(e) {
console.log("获取升级数据,创建写文件对象失败:" + e.message);
});
}, function(e) {
console.log("获取升级数据,打开保存文件失败:" + e.message);
});
}
});
}
/**
* 比较版本大小,如果新版本nv大于旧版本ov则返回true,否则返回false
* @param {String} ov
* @param {String} nv
* @return {Boolean}
*/
function compareVersion(ov, nv) {
if (!ov || !nv || ov == "" || nv == "") {
return false;
}
var b = false,
ova = ov.split(".", 4),
nva = nv.split(".", 4);
for (var i = 0; i < ova.length && i < nva.length; i++) {
var so = ova[i],
no = parseInt(so),
sn = nva[i],
nn = parseInt(sn);
if (nn > no || sn.length > so.length) {
return true;
} else if (nn < no) {
return false;
}
}
if (nva.length > ova.length && 0 == nv.indexOf(ov)) {
return true;
}
}
})(mui);
官方提供了自动检测更新的代码,我这里稍微更改了一下,可以手动检测更新。
不过建议使用App资源在线升级更新
我这里也提供了自动+手动的App资源在线升级
<ul class="mui-table-view" style="margin-top: 25px;">
<li class="mui-table-view-cell">
<a id="onlineupgrade">
在线升级 <span class="mui-pull-right" id="supgrade" style="color: red;display: none;">
New
</span>
</a>
</li>
</ul>
mui.plusReady(function() {
var supgrade = document.getElementById("supgrade");
//有新版本显示提示
mui.initUpdate(function() {
supgrade.style.display = "inline-block";
});
document.getElementById("onlineupgrade").addEventListener('tap', function() {
if (supgrade.style.display != "none") {
mui.initUpdate();
}
});
});
(function($) {
//正式上线了请更改app更新地址
var server = "http://www.dcloud.io/helloh5/update.json", //获取升级描述文件服务器地址
localDir = "update",
localFile = "update.json", //本地保存升级描述目录和文件名
keyUpdate = "updateCheck", //取消升级键名
keyAbort = "updateAbort", //忽略版本键名
checkInterval = 3600000, //升级检查间隔,单位为ms,1小时为60*60*1000=3600000, 如果每次启动需要检查设置值为0
dir = null;
/**
* 准备升级操作
* 创建升级文件保存目录
* @param{Function} 有回调函数是手动检查折升级
*/
$.initUpdate = function(callback) {
// 打开doc根目录
plus.io.requestFileSystem(plus.io.PRIVATE_DOC, function(fs) {
fs.root.getDirectory(localDir, {
create: true
}, function(entry) {
dir = entry;
checkUpdate(callback);
}, function(e) {
console.log("准备升级操作,打开update目录失败:" + e.message);
});
}, function(e) {
console.log("准备升级操作,打开doc目录失败:" + e.message);
});
}
/**
* 检测程序升级
* @author liuyf 2015-04-27
* @description 手动检测是否升级 是否已过期等标记在手动下无效
*
* @param{Function} 有回调函数是手动检查折升级
* */
function checkUpdate(callback) {
// 判断升级检测是否过期
var lastcheck = plus.storage.getItem(keyUpdate);
if (lastcheck) {
// 取消已过期,删除取消标记
plus.storage.removeItem(keyUpdate);
}
// 读取本地升级文件]
dir.getFile(localFile, {
create: false
}, function(fentry) {
fentry.file(function(file) {
var reader = new plus.io.FileReader();
reader.onloadend = function(e) {
//检测升级的时候不删除本地升级文件
if (!callback) {
fentry.remove();
}
var data = null;
try {
data = JSON.parse(e.target.result);
} catch (e) {
console.log("读取本地升级文件,数据格式错误!");
}
checkUpdateData(data, callback);
}
reader.readAsText(file);
}, function(e) {
console.log("读取本地升级文件,获取文件对象失败:" + e.message);
fentry.remove();
});
}, function(e) {
// 失败表示文件不存在,从服务器获取升级数据
getUpdateData();
});
}
/**
* 检查升级数据
* @author liuyf 2015-04-27
* @description 手动升级 是否存在版本号等逻辑应舍弃
* @param{Function} 有回调函数是手动检查升级
*/
function checkUpdateData(j, callback) {
//当前客户端版本号
var curVer = plus.runtime.version,
inf = j[plus.os.name];
if (inf) {
var srvVer = inf.version;
// 判断是否需要升级
if (compareVersion(curVer, srvVer)) {
if (!callback) {
// 提示用户是否升级
plus.ui.confirm(inf.note, function(i) {
if (0 == i)
plus.runtime.openURL(inf.url);
else {
plus.storage.setItem(keyUpdate, (new Date()).getTime().toString());
}
}, inf.title, ["立即更新", "取 消"]);
} else {
callback();
}
}
}
}
/**
* 从服务器获取升级数据,并存储到本地;
*/
function getUpdateData() {
mui.getJSON(server, {}, function(data) {
//appid一致,才将服务器上的版本数据保存到本地
if (data.appid == plus.runtime.appid) {
// 保存到本地文件中
dir.getFile(localFile, {
create: true
}, function(fentry) {;
fentry.createWriter(function(writer) {
writer.onerror = function() {
console.log("获取升级数据,保存文件失败!");
}
writer.write(data);
}, function(e) {
console.log("获取升级数据,创建写文件对象失败:" + e.message);
});
}, function(e) {
console.log("获取升级数据,打开保存文件失败:" + e.message);
});
}
});
}
/**
* 比较版本大小,如果新版本nv大于旧版本ov则返回true,否则返回false
* @param {String} ov
* @param {String} nv
* @return {Boolean}
*/
function compareVersion(ov, nv) {
if (!ov || !nv || ov == "" || nv == "") {
return false;
}
var b = false,
ova = ov.split(".", 4),
nva = nv.split(".", 4);
for (var i = 0; i < ova.length && i < nva.length; i++) {
var so = ova[i],
no = parseInt(so),
sn = nva[i],
nn = parseInt(sn);
if (nn > no || sn.length > so.length) {
return true;
} else if (nn < no) {
return false;
}
}
if (nva.length > ova.length && 0 == nv.indexOf(ov)) {
return true;
}
}
})(mui);
收起阅读 »
IOS离线打包的那些梗,搞定了真高兴!
这几天要发布新版本,打开Hbuilder准备在线打包,然后上次发布的悲剧重演,试过所有的p12证书之后,还是报错。
于是拿脑袋砸墙5分钟--------疼!
想起来前几天好不容易下到的xcode6.3(之前测试过离线,demo怎么跑都跑不出来,后来找个高手才发现,我的xcode是4.2的,离线打包必须要xcode 5.0,然后下了起码10个xcode的img,才找到个好用的),不如离线打包吧!
就这么愉快的决定了,于是就有了下面的各种梗:
> 我的操作步骤是这样的:
1.下载最新的官方包 HTML 5+ SDK 更新日志
- 拷贝官方的项目出来
- 按照官方的要求修改必要的参数 (附官方的说明:iOS离线打包秘籍)
- 联真机测试 (我的以前弄的,请百度之)
- 打包测试 (菜单位置Product->Archive)
- 提交审核
1.manifest.json的各种错误
一定要检查json的格式是否正确,我碰到的情况是用apple自己带的编辑器(不是xcode)编辑了之后,居然json就破了
这是xcode报错的内容截图没保留,大概的内容是有关键字(jsonvalue)的,要是你碰到这个问题,请检查manifest.json。
必须要对的地方如下图:
要是你改错了,哪怕是各双引号,那就是这样的:
友情提示:
1.拷贝demo里的过来用,再改改,因为云端打包需要的参数都在里面,但离线打包的时候是不需要的
- 直接用xcode改,防止编码问题,双引号一直不对的话,请用ctrl+C和ctrl+V
- 网上有json检查工具,看填写的是否完整
4.要是你的入口文件不是index.html,请别忘记修改哦
2. xcode闪退(这个奇葩问题请翻到最后)
真是奇葩~
真是奇葩~~
真是奇葩~~~
真是奇葩~~~~
真是奇葩~
3. input点选出来的是英文菜单
想象中的菜单是这样的:
现实是这样的:
解决的办法是这样的:
需要添加一行对中文的支持,就是图中用红框框出来的部分
4. 头部的样式设置错误
想象中的样子是这样的:
现实是这样的:
解决的办法是这样的:
需要添加一行,就是图中用红框框出来的部分,注意选成no
官方的状态栏设置的介绍navigator用于管理浏览器运行环境信息
5. 精简文件大小
好不容易Archive过了,才发现文件真大啊,居然又30多MB
就不停的去除库,对IOS的开发不了解,我看着不爽就删除,不敢瞎说,只知道库还是很大的
6. 64位支持
看到这个界面的时候我好高兴啊,但是苹果叔叔却不停的跳提示
解决的办法是这样的,看到红框的部分了嘛?改成no就行了
7. PUSH的问题
这个问题,是在一路绿灯之后,好高兴啊,去developer center写新版本特点了,然后...
苹果给我发了一封邮件,内容如下:
于是又开始了一路的排除bug
解决的办法是把这部分的代码注释掉
百度地图的部分也可以注释掉(要是你没用到的话)
-----------------------------奇葩故事分隔线-----------------------------
终于到最后的了,我们讲讲奇葩的故事
事情发生在1号房APP真机调试正常之后,当时的状况如下:
1号房APP:真机运行正常,打包时候xcode闪退
Hello5Demo:真机运行正常,打包正常
新建空项目:真机运行正常,打包正常
先是修改xcode里的参数无数,一点结果也没有,xcode一如既往的闪退,我那个胸闷啊。
不过大神就是大神,很淡定的说:“我们再找个人问问”,于是又出现一位美女大神,也很淡定,发布APP无数,看了几个参数之后,淡淡的说:“把你写的东西都删了~~”
当时,我说就听到这话的当时,我的下巴掉下来了,心里默念“那我不都白写了,我的青春啊~~”
“再把你写的一部分一部分的加进去~~”
好吧,我承认淡定有时候要人命啊!
接下来就是纯体力的活动:
开始清空Pandora/apps/yihaofang/www/目录下的所有文件
打包正常....
加入根目录下的独立文件
打包正常....
加入css目录
打包正常....
加入js目录
打包正常....
加入img目录
打包正常....
加入html目录
xcode闪退!
原因找到了,但是,但是这个目录没法用话,我的APP还是白写了
继续革命啊
清空html目录
xcode闪退!
到这里的时候,我看到大神们也露出了惊讶的表情,难道一个目录的名称会导致xcode闪退!
大神冷静了一下说:“改各文件夹名看看”
改目录名为htmla
打包正常....
在htmla中加入所有文件
打包正常....
幸福的泪花在我眼眶中打转(请原谅我的文艺青年本质),这就行啦?!
于是我作出了一个重要的决定
把htmla改成html
打包正常....
这就是这朵奇葩,详细原因不知道,只知道是奇葩~~
-----------------------------奇葩故事分隔线-----------------------------
其他会遇到的问题
各种证书的问题,因为之前发布过一次,这次发布就没有这么多的问题,隐约记得当时也是鸡毛一地
重点看下面这篇文章,仔细操作就能过
iOS证书(.p12)和描述文件(.mobileprovision)申请
最后,特别感谢几位朋友,排名不分先后
Hbuiler:有了这么方便的工具,web工程师转做APP也是妥妥的
两位大神:没有你们,也许很久之后1号房才能上线
Hbuilder热心回答我问题的 DCloud-App-Array(colour), DCloud_iOS_XTY(我)
这几天要发布新版本,打开Hbuilder准备在线打包,然后上次发布的悲剧重演,试过所有的p12证书之后,还是报错。
于是拿脑袋砸墙5分钟--------疼!
想起来前几天好不容易下到的xcode6.3(之前测试过离线,demo怎么跑都跑不出来,后来找个高手才发现,我的xcode是4.2的,离线打包必须要xcode 5.0,然后下了起码10个xcode的img,才找到个好用的),不如离线打包吧!
就这么愉快的决定了,于是就有了下面的各种梗:
> 我的操作步骤是这样的:
1.下载最新的官方包 HTML 5+ SDK 更新日志
- 拷贝官方的项目出来
- 按照官方的要求修改必要的参数 (附官方的说明:iOS离线打包秘籍)
- 联真机测试 (我的以前弄的,请百度之)
- 打包测试 (菜单位置Product->Archive)
- 提交审核
1.manifest.json的各种错误
一定要检查json的格式是否正确,我碰到的情况是用apple自己带的编辑器(不是xcode)编辑了之后,居然json就破了
这是xcode报错的内容截图没保留,大概的内容是有关键字(jsonvalue)的,要是你碰到这个问题,请检查manifest.json。
必须要对的地方如下图:
要是你改错了,哪怕是各双引号,那就是这样的:
友情提示:
1.拷贝demo里的过来用,再改改,因为云端打包需要的参数都在里面,但离线打包的时候是不需要的
- 直接用xcode改,防止编码问题,双引号一直不对的话,请用ctrl+C和ctrl+V
- 网上有json检查工具,看填写的是否完整
4.要是你的入口文件不是index.html,请别忘记修改哦
2. xcode闪退(这个奇葩问题请翻到最后)
真是奇葩~
真是奇葩~~
真是奇葩~~~
真是奇葩~~~~
真是奇葩~
3. input点选出来的是英文菜单
想象中的菜单是这样的:
现实是这样的:
解决的办法是这样的:
需要添加一行对中文的支持,就是图中用红框框出来的部分
4. 头部的样式设置错误
想象中的样子是这样的:
现实是这样的:
解决的办法是这样的:
需要添加一行,就是图中用红框框出来的部分,注意选成no
官方的状态栏设置的介绍navigator用于管理浏览器运行环境信息
5. 精简文件大小
好不容易Archive过了,才发现文件真大啊,居然又30多MB
就不停的去除库,对IOS的开发不了解,我看着不爽就删除,不敢瞎说,只知道库还是很大的
6. 64位支持
看到这个界面的时候我好高兴啊,但是苹果叔叔却不停的跳提示
解决的办法是这样的,看到红框的部分了嘛?改成no就行了
7. PUSH的问题
这个问题,是在一路绿灯之后,好高兴啊,去developer center写新版本特点了,然后...
苹果给我发了一封邮件,内容如下:
于是又开始了一路的排除bug
解决的办法是把这部分的代码注释掉
百度地图的部分也可以注释掉(要是你没用到的话)
-----------------------------奇葩故事分隔线-----------------------------
终于到最后的了,我们讲讲奇葩的故事
事情发生在1号房APP真机调试正常之后,当时的状况如下:
1号房APP:真机运行正常,打包时候xcode闪退
Hello5Demo:真机运行正常,打包正常
新建空项目:真机运行正常,打包正常
先是修改xcode里的参数无数,一点结果也没有,xcode一如既往的闪退,我那个胸闷啊。
不过大神就是大神,很淡定的说:“我们再找个人问问”,于是又出现一位美女大神,也很淡定,发布APP无数,看了几个参数之后,淡淡的说:“把你写的东西都删了~~”
当时,我说就听到这话的当时,我的下巴掉下来了,心里默念“那我不都白写了,我的青春啊~~”
“再把你写的一部分一部分的加进去~~”
好吧,我承认淡定有时候要人命啊!
接下来就是纯体力的活动:
开始清空Pandora/apps/yihaofang/www/目录下的所有文件
打包正常....
加入根目录下的独立文件
打包正常....
加入css目录
打包正常....
加入js目录
打包正常....
加入img目录
打包正常....
加入html目录
xcode闪退!
原因找到了,但是,但是这个目录没法用话,我的APP还是白写了
继续革命啊
清空html目录
xcode闪退!
到这里的时候,我看到大神们也露出了惊讶的表情,难道一个目录的名称会导致xcode闪退!
大神冷静了一下说:“改各文件夹名看看”
改目录名为htmla
打包正常....
在htmla中加入所有文件
打包正常....
幸福的泪花在我眼眶中打转(请原谅我的文艺青年本质),这就行啦?!
于是我作出了一个重要的决定
把htmla改成html
打包正常....
这就是这朵奇葩,详细原因不知道,只知道是奇葩~~
-----------------------------奇葩故事分隔线-----------------------------
其他会遇到的问题
各种证书的问题,因为之前发布过一次,这次发布就没有这么多的问题,隐约记得当时也是鸡毛一地
重点看下面这篇文章,仔细操作就能过
iOS证书(.p12)和描述文件(.mobileprovision)申请
最后,特别感谢几位朋友,排名不分先后
Hbuiler:有了这么方便的工具,web工程师转做APP也是妥妥的
两位大神:没有你们,也许很久之后1号房才能上线
Hbuilder热心回答我问题的 DCloud-App-Array(colour), DCloud_iOS_XTY(我)
分享图片压缩上传demo,可以选择一张或多张图片也可以拍摄照片
2016-08-05更新:
下方的代码是比较OLD的了,是通过js进行图片的剪切 旋转 再生成,效率较低。
后来又整合了一个利用native.js本地接口的压缩代码 ,链接在这
。页面中有详细的说明,需要的童鞋们可以参考以下。
代码整合了
1.多串君
- 伟子
两个人的demo,
其中resize原来的filereader在5 中更改为plus.io.FileReader()方不报错。
如有错误,请不吝批评指正。
更新日志:
2015-05-09 1020450921@qq.com
1.修复了ios下无法获取图片宽高的问题:
ios下不在img.onload中是获取不到文件对象的。
-
新增了最大宽度(高度)的判别,按比例压缩。
具体代码如下:<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <title></title> <link href="http://ask.dcloud.net.cn/../../../css/mui.min.css" rel="stylesheet" /> <style type="text/css"> body { background-color: #efeff4; } .mui-content {} .mui-content a { color: #8F8F94; } .mui-content a.active { color: #007aff; } .mui-title { font-family: simhei; } .btn_1 { position: absolute; bottom: 100px; left: 10px; right: 10px; } .btn_2 { position: absolute; bottom: 20px; left: 10px; right: 10px; } .mui-btn-block { width: 90%; margin: 0 auto; } body { overflow: hidden; } .showimg { margin: 20px 10px auto 10px; text-align: center; } </style> </head> <body> <header class="mui-bar mui-bar-nav"> <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a> <h1 class="mui-title">上传身份证照片</h1> <a class="mui-pull-right mui-icon mui-icon-upload" onclick="imgupgrade()"></a> </header> <!-- 作者:1020450921@qq.com 时间:2015-04-24 描述:参考 http://ask.dcloud.net.cn/question/2089 --> <div class="mui-content"> <div class="showimg"> </div> <button type="button" class="mui-btn mui-btn-primary mui-btn-block btn_1" onclick="galleryImgs()">从相册中选择图片</button> <br> <button type="button" class="mui-btn mui-btn-success mui-btn-block btn_2" onclick="cameraimages()">拍照</button> </div> </body> <script src="../../../js/mui.min.js"></script> <script src="../../../js/binaryajax.js" type="text/javascript" charset="utf-8"></script> <script src="../../../js/exif.js" type="text/javascript" charset="utf-8"></script> <script src="../../../js/canvasResize.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> mui.init(); mui.plusReady(function() {}) //上传单张图片 function galleryImg() { //每次拍摄或选择图片前清空数组对象 f1.splice(0, f1.length); document.getElementsByClassName("showimg")[0].innerHTML = null; // 从相册中选择图片 mui.toast("从相册中选择一张图片"); plus.gallery.pick(function(path) { showImg(path); }, function(e) { mui.toast("取消选择图片"); }, { filter: "image", multiple: false }); } function galleryImgs() { //每次拍摄或选择图片前清空数组对象 f1.splice(0, f1.length); document.getElementsByClassName("showimg")[0].innerHTML = null; // 从相册中选择图片 mui.toast("从相册中选择不超过两张图片"); plus.gallery.pick(function(e) { if (e.files.length != 2) { mui.toast('请选择身份证正面和背面照片共两张'); return false; } for (var i in e.files) { showImg(e.files[i]); } }, function(e) { mui.toast("取消选择图片"); }, { filter: "image", multiple: true }); } // 拍照添加文件 function cameraimages() { //每次拍摄或选择图片前清空数组对象 f1.splice(0, f1.length); document.getElementsByClassName("showimg")[0].innerHTML = null; var cmr = plus.camera.getCamera(); cmr.captureImage(function(p) { plus.io.resolveLocalFileSystemURL(p, function(entry) { var localurl = entry.toLocalURL(); //把拍照的目录路径,变成本地url路径,例如file:///........之类的。 showImg(localurl); }); }, function(e) { mui.toast("很抱歉,获取失败 " e); }); } // 全局数组对象,添加文件,用于压缩上传使用 var f1 = new Array();
function showImg(url) {
// 兼容以“file:”开头的情况
if (0 != url.toString().indexOf("file://")) {
url = "file://" + url;
}
var div = document.getElementsByClassName("showimg")[0];
var img = new Image();
img.src = url; // 传过来的图片路径在这里用。
img.onclick = function() {
plus.runtime.openFile(url);
};
img.onload = function() {
var tmph = img.height;
var tmpw = img.width;
var isHengTu = tmpw > tmph;
var max = Math.max(tmpw, tmph);
var min = Math.min(tmpw, tmph);
var bili = min / max;
if (max > 1200) {
max = 1200;
min = Math.floor(bili * max);
}
tmph = isHengTu ? min : max;
tmpw = isHengTu ? max : min;
img.style.border = "1px solid rgb(200,199,204)";
img.style.margin = "10px";
img.style.width = "150px";
img.style.height = "150px";
img.onload = null;
plus.io.resolveLocalFileSystemURL(url, function(entry) {
entry.file(function(file) {
console.log(file.size + '--' + file.name);
canvasResize(file, {
width: tmpw,
height: tmph,
crop: false,
quality: 50, //压缩质量
rotate: 0,
callback: function(data, width, height) {
f1.push(data);
img.src = data;
div.appendChild(img);
plus.nativeUI.closeWaiting();
}
});
});
},
function(e) {
plus.nativeUI.closeWaiting();
console.log(e.message);
});
};
};
function imgupgrade() {
mui.toast('后台联调时启用上传功能');
return;
var wt = plus.nativeUI.showWaiting();
var url = '后台地址';
var dataType = 'json';
//发送数据
var data = {
files1: f1 //base64数据
};
mui.post(url, data, success, dataType);
}
//成功响应的回调函数
var success = function(response) {
plus.nativeUI.closeWaiting();
if (response != null) {
alert("上传成功");
}
}
</script>
</html>
伟子的js代码我放在了附件中。
2016-08-05更新:
下方的代码是比较OLD的了,是通过js进行图片的剪切 旋转 再生成,效率较低。
后来又整合了一个利用native.js本地接口的压缩代码 ,链接在这
。页面中有详细的说明,需要的童鞋们可以参考以下。
代码整合了
1.多串君
- 伟子
两个人的demo,
其中resize原来的filereader在5 中更改为plus.io.FileReader()方不报错。
如有错误,请不吝批评指正。
更新日志:
2015-05-09 1020450921@qq.com
1.修复了ios下无法获取图片宽高的问题:
ios下不在img.onload中是获取不到文件对象的。
-
新增了最大宽度(高度)的判别,按比例压缩。
具体代码如下:<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <title></title> <link href="http://ask.dcloud.net.cn/../../../css/mui.min.css" rel="stylesheet" /> <style type="text/css"> body { background-color: #efeff4; } .mui-content {} .mui-content a { color: #8F8F94; } .mui-content a.active { color: #007aff; } .mui-title { font-family: simhei; } .btn_1 { position: absolute; bottom: 100px; left: 10px; right: 10px; } .btn_2 { position: absolute; bottom: 20px; left: 10px; right: 10px; } .mui-btn-block { width: 90%; margin: 0 auto; } body { overflow: hidden; } .showimg { margin: 20px 10px auto 10px; text-align: center; } </style> </head> <body> <header class="mui-bar mui-bar-nav"> <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a> <h1 class="mui-title">上传身份证照片</h1> <a class="mui-pull-right mui-icon mui-icon-upload" onclick="imgupgrade()"></a> </header> <!-- 作者:1020450921@qq.com 时间:2015-04-24 描述:参考 http://ask.dcloud.net.cn/question/2089 --> <div class="mui-content"> <div class="showimg"> </div> <button type="button" class="mui-btn mui-btn-primary mui-btn-block btn_1" onclick="galleryImgs()">从相册中选择图片</button> <br> <button type="button" class="mui-btn mui-btn-success mui-btn-block btn_2" onclick="cameraimages()">拍照</button> </div> </body> <script src="../../../js/mui.min.js"></script> <script src="../../../js/binaryajax.js" type="text/javascript" charset="utf-8"></script> <script src="../../../js/exif.js" type="text/javascript" charset="utf-8"></script> <script src="../../../js/canvasResize.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> mui.init(); mui.plusReady(function() {}) //上传单张图片 function galleryImg() { //每次拍摄或选择图片前清空数组对象 f1.splice(0, f1.length); document.getElementsByClassName("showimg")[0].innerHTML = null; // 从相册中选择图片 mui.toast("从相册中选择一张图片"); plus.gallery.pick(function(path) { showImg(path); }, function(e) { mui.toast("取消选择图片"); }, { filter: "image", multiple: false }); } function galleryImgs() { //每次拍摄或选择图片前清空数组对象 f1.splice(0, f1.length); document.getElementsByClassName("showimg")[0].innerHTML = null; // 从相册中选择图片 mui.toast("从相册中选择不超过两张图片"); plus.gallery.pick(function(e) { if (e.files.length != 2) { mui.toast('请选择身份证正面和背面照片共两张'); return false; } for (var i in e.files) { showImg(e.files[i]); } }, function(e) { mui.toast("取消选择图片"); }, { filter: "image", multiple: true }); } // 拍照添加文件 function cameraimages() { //每次拍摄或选择图片前清空数组对象 f1.splice(0, f1.length); document.getElementsByClassName("showimg")[0].innerHTML = null; var cmr = plus.camera.getCamera(); cmr.captureImage(function(p) { plus.io.resolveLocalFileSystemURL(p, function(entry) { var localurl = entry.toLocalURL(); //把拍照的目录路径,变成本地url路径,例如file:///........之类的。 showImg(localurl); }); }, function(e) { mui.toast("很抱歉,获取失败 " e); }); } // 全局数组对象,添加文件,用于压缩上传使用 var f1 = new Array();
function showImg(url) {
// 兼容以“file:”开头的情况
if (0 != url.toString().indexOf("file://")) {
url = "file://" + url;
}
var div = document.getElementsByClassName("showimg")[0];
var img = new Image();
img.src = url; // 传过来的图片路径在这里用。
img.onclick = function() {
plus.runtime.openFile(url);
};
img.onload = function() {
var tmph = img.height;
var tmpw = img.width;
var isHengTu = tmpw > tmph;
var max = Math.max(tmpw, tmph);
var min = Math.min(tmpw, tmph);
var bili = min / max;
if (max > 1200) {
max = 1200;
min = Math.floor(bili * max);
}
tmph = isHengTu ? min : max;
tmpw = isHengTu ? max : min;
img.style.border = "1px solid rgb(200,199,204)";
img.style.margin = "10px";
img.style.width = "150px";
img.style.height = "150px";
img.onload = null;
plus.io.resolveLocalFileSystemURL(url, function(entry) {
entry.file(function(file) {
console.log(file.size + '--' + file.name);
canvasResize(file, {
width: tmpw,
height: tmph,
crop: false,
quality: 50, //压缩质量
rotate: 0,
callback: function(data, width, height) {
f1.push(data);
img.src = data;
div.appendChild(img);
plus.nativeUI.closeWaiting();
}
});
});
},
function(e) {
plus.nativeUI.closeWaiting();
console.log(e.message);
});
};
};
function imgupgrade() {
mui.toast('后台联调时启用上传功能');
return;
var wt = plus.nativeUI.showWaiting();
var url = '后台地址';
var dataType = 'json';
//发送数据
var data = {
files1: f1 //base64数据
};
mui.post(url, data, success, dataType);
}
//成功响应的回调函数
var success = function(response) {
plus.nativeUI.closeWaiting();
if (response != null) {
alert("上传成功");
}
}
</script>
</html>
伟子的js代码我放在了附件中。
收起阅读 »
App资源在线差量升级更新
官方已发布APP升级中心,支持原生APP整包升级和wgt资源包升级。详见
5+应用可使用以下方式进行升级
本文重点介绍5+应用资源差量升级,相对App资源独立升级,因为只需要下载更新的资源文件,所以升级包体积更小,升级速度会更快。
注意:需HBuilder5.7.0以上版本才有此功能
生成移动App资源差量升级包
顾名思义,差量升级包是针对某个历史版本到新版本的差量,所以对于升级服务器来讲需要保留所有历史版本,并且分别生成每个历史版本到新版本的差量升级包。
目前HBuilder IDE并未提供版本控制系统,所以需要按照以下规则手动生成差量升级包:
- 差量升级包文件格式为wgtu
在HTML5+移动App支持的差量升级包文件后缀名必须是wgtu,它是标准的zip压缩文件,可以使用任何zip工具对wgtu目录进行压缩,并修改文件后缀名称为wgtu。 - wgtu目录结构
名称 | 类型 | 说明 |
---|---|---|
www | 目录 | 保存需要更新的数据文件,其下的目录结构与应用的目录结构一致,其下必须包含manifest.json文件,否则认为更新文件非法,不执行升级操作 |
update.xml | 文件 | 保存应用升级操作的配置信息,参考update.xml文件格式 |
- update.xml文件格式(注意:文件名必须全部小写)
update.xml为标准的xml文件格式,定义wgtu文件升级配置信息,其文件格式如下:
元素名称 | 父元素 | 类型 | 描述 | 必须 |
---|---|---|---|---|
wgtu | 根节点 | |||
appid | wgtu | 属性 | 升级应用的appid | 是 |
basis | wgtu | 节点 | 基础应用信息 | 是 |
version | basis | 属性 | 基础应用的版本信息(旧版本的版本号) | 是 |
remove | wgtu | 节点 | 升级需要删除的文件/目录列表 | 否 |
item | remove | 节点 | 需要删除的项 | 否 |
path | item | 属性 | 需要删除的项的路径,相对于应用根目录,如“image/a.png”表示image目录下的a.png文件。如果是目录则以‘/’结尾,如“res/img/”表示res目录下的img目录,删除时包括下的所有内容。 | 否 |
- wgtu示例
应用升级前的版本1.0,其应用目录结构为:
应用升级后的版本1.1,其应用目录结构为:
差量升级包wgtu文件的目录结构为(不包括最外层wgtu目录):
应用升级更新文件manifest.json、index.html,新增detail.html、image/new.png,删除image/icon5.png。
其中update.xml中的内容为:<?xml version="1.0" encoding="utf-8" standalone="no"?> <wgtu appid="TEST" > <basis version="1.0" /> <remove> <item path="image/icon5.png" /> </remove> </wgtu>
应用中差量更新资源
下载wgtu文件并更新:
var url='http://demo.dcloud.net.cn/helloh5/update/HelloH5.wgtu';
plus.nativeUI.showWaiting("升级中...");
var dtask = plus.downloader.createDownload( url, {method:"GET"}, function(d,status){
if ( status == 200 ) {
console.log( "Download wgtu success: " + d.filename );
plus.runtime.install(d.filename,{},function(){
plus.nativeUI.closeWaiting();
plus.nativeUI.alert("Update wgtu success, restart now!",function(){
plus.runtime.restart();
});
},function(e){
plus.nativeUI.closeWaiting();
alert("Update wgtu failed: "+e.message);
});
} else {
plus.nativeUI.closeWaiting();
alert( "Download wgtu failed: " + status );
}
} );
dtask.addEventListener('statechanged',function(d,status){
console.log("statechanged: "+d.state);
});
dtask.start();
注意
- wgtu包中的update.xml文件名称必须全部小写,并且和www在同一级目录中
- wgtu包中www目录下必须包含manifest.json文件,并且里面不能包含注释(HBuilder中默认带注释,需要手动删除所有注释)
- update.xml中的appid值是应用的AppID(如“H5F6AE111”),不是程序的包名(如“io.dcloud.H5F6AE1111”)
- wgtu包必须是使用zip格式压缩的文件(不能使用如rar等其它压缩格式)
App store应用更新说明
应用资源更新肯定是违反apple政策的,但目前看起来它也不管。你在官网案例那里下载Appstore版本的那些app,大多启动后都会提示更新,反正也都没下架。如果你不是很大的公司,apple不会理睬你。如果你是大公司,建议不要做整体更新,每次更新几个页面,也不要提示更新后需要重启,这样会安全点。
感谢 @大裤子 反馈建议更新补充文档,wgtu更新经验分享:http://ask.dcloud.net.cn/question/6353
官方已发布APP升级中心,支持原生APP整包升级和wgt资源包升级。详见
5+应用可使用以下方式进行升级
本文重点介绍5+应用资源差量升级,相对App资源独立升级,因为只需要下载更新的资源文件,所以升级包体积更小,升级速度会更快。
注意:需HBuilder5.7.0以上版本才有此功能
生成移动App资源差量升级包
顾名思义,差量升级包是针对某个历史版本到新版本的差量,所以对于升级服务器来讲需要保留所有历史版本,并且分别生成每个历史版本到新版本的差量升级包。
目前HBuilder IDE并未提供版本控制系统,所以需要按照以下规则手动生成差量升级包:
- 差量升级包文件格式为wgtu
在HTML5+移动App支持的差量升级包文件后缀名必须是wgtu,它是标准的zip压缩文件,可以使用任何zip工具对wgtu目录进行压缩,并修改文件后缀名称为wgtu。 - wgtu目录结构
名称 | 类型 | 说明 |
---|---|---|
www | 目录 | 保存需要更新的数据文件,其下的目录结构与应用的目录结构一致,其下必须包含manifest.json文件,否则认为更新文件非法,不执行升级操作 |
update.xml | 文件 | 保存应用升级操作的配置信息,参考update.xml文件格式 |
- update.xml文件格式(注意:文件名必须全部小写)
update.xml为标准的xml文件格式,定义wgtu文件升级配置信息,其文件格式如下:
元素名称 | 父元素 | 类型 | 描述 | 必须 |
---|---|---|---|---|
wgtu | 根节点 | |||
appid | wgtu | 属性 | 升级应用的appid | 是 |
basis | wgtu | 节点 | 基础应用信息 | 是 |
version | basis | 属性 | 基础应用的版本信息(旧版本的版本号) | 是 |
remove | wgtu | 节点 | 升级需要删除的文件/目录列表 | 否 |
item | remove | 节点 | 需要删除的项 | 否 |
path | item | 属性 | 需要删除的项的路径,相对于应用根目录,如“image/a.png”表示image目录下的a.png文件。如果是目录则以‘/’结尾,如“res/img/”表示res目录下的img目录,删除时包括下的所有内容。 | 否 |
- wgtu示例
应用升级前的版本1.0,其应用目录结构为:
应用升级后的版本1.1,其应用目录结构为:
差量升级包wgtu文件的目录结构为(不包括最外层wgtu目录):
应用升级更新文件manifest.json、index.html,新增detail.html、image/new.png,删除image/icon5.png。
其中update.xml中的内容为:<?xml version="1.0" encoding="utf-8" standalone="no"?> <wgtu appid="TEST" > <basis version="1.0" /> <remove> <item path="image/icon5.png" /> </remove> </wgtu>
应用中差量更新资源
下载wgtu文件并更新:
var url='http://demo.dcloud.net.cn/helloh5/update/HelloH5.wgtu';
plus.nativeUI.showWaiting("升级中...");
var dtask = plus.downloader.createDownload( url, {method:"GET"}, function(d,status){
if ( status == 200 ) {
console.log( "Download wgtu success: " + d.filename );
plus.runtime.install(d.filename,{},function(){
plus.nativeUI.closeWaiting();
plus.nativeUI.alert("Update wgtu success, restart now!",function(){
plus.runtime.restart();
});
},function(e){
plus.nativeUI.closeWaiting();
alert("Update wgtu failed: "+e.message);
});
} else {
plus.nativeUI.closeWaiting();
alert( "Download wgtu failed: " + status );
}
} );
dtask.addEventListener('statechanged',function(d,status){
console.log("statechanged: "+d.state);
});
dtask.start();
注意
- wgtu包中的update.xml文件名称必须全部小写,并且和www在同一级目录中
- wgtu包中www目录下必须包含manifest.json文件,并且里面不能包含注释(HBuilder中默认带注释,需要手动删除所有注释)
- update.xml中的appid值是应用的AppID(如“H5F6AE111”),不是程序的包名(如“io.dcloud.H5F6AE1111”)
- wgtu包必须是使用zip格式压缩的文件(不能使用如rar等其它压缩格式)
App store应用更新说明
应用资源更新肯定是违反apple政策的,但目前看起来它也不管。你在官网案例那里下载Appstore版本的那些app,大多启动后都会提示更新,反正也都没下架。如果你不是很大的公司,apple不会理睬你。如果你是大公司,建议不要做整体更新,每次更新几个页面,也不要提示更新后需要重启,这样会安全点。
感谢 @大裤子 反馈建议更新补充文档,wgtu更新经验分享:http://ask.dcloud.net.cn/question/6353
收起阅读 »segment分段选择页面根据传入的querystring 激活相应的分段
具体需求描述是这样的:父页面有两个链接需要导向segment页并根据传入的querystring激活不同的段落。
使用原始的url带参的方式,没有使用自定义扩展参数的方式,不过建议使用自定义扩展参数的方式。
mui.openwindow({
extras:{
'x_id':'10'//自定义扩展参数,可以用来处理页面间传值
}
})
在B页面这样去获取
var self=plus.webview.currentWebview();
var id=self.x_id;
具体的实现代码如下:
父页面html,打开链接依然使用mui.openwindow.
<ul>
<li><a class="mui-inline" href="http://ask.dcloud.net.cn/myxx.html?model=item1mobile" style="color:rgb(0, 0, 0) ;">商家XX</a></li>
<li>20</li>
</ul>
<ul>
<li ><a class="mui-inline" href="http://ask.dcloud.net.cn/myxx.html?model=item2mobile" style="color:rgb(0, 0, 0) ;">个人XX</a></li>
<li>10</li>
</ul>
子页面:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<title>我的XX</title>
<link href="http://ask.dcloud.net.cn/../css/mui.min.css" rel="stylesheet" />
<style type="text/css">
.mui-segmented-control .mui-control-item {
line-height: 28px;
}
.mui-title {
font-family: simhei;
}
.gray_color {
color: #8f8f94;
font-size: 0.8em;
}
</style>
</head>
<body>
<header class="mui-bar mui-bar-nav">
<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
<h1 class="mui-title">我的XX</h1>
</header>
<div class="mui-content">
<div class="mui-content-padded tab_title" style="margin: 0.65em 3em;">
<div id="segmentedControl" class="mui-segmented-control">
<a class="mui-control-item mui-active" href="#item1mobile">
商家XX
</a>
<a class="mui-control-item" href="#item2mobile">
个人XX
</a>
</div>
</div>
<div id="item1mobile" class="mui-control-content mui-active">
<ul class="mui-table-view">
<li class="mui-table-view-cell mui-media">
<a href="javascript:void(0);">
<img class="mui-media-object mui-pull-left" src="../images/content.jpg">
<div class="mui-media-body">
3月19日XXXXX<span class='mui-pull-right gray_color'>3月18号</span>
<p class='mui-ellipsis'>您在日历XXX中设定的XXX即将到来</p>
</div>
</a>
</li>
<li class="mui-table-view-cell mui-media">
<a href="javascript:void(0);">
<img class="mui-media-object mui-pull-left" src="../images/content.jpg">
<div class="mui-media-body">
3月15日XXX<span class='mui-pull-right gray_color'>3月14号</span>
<p class='mui-ellipsis'>您在XXX即将到来</p>
</div>
</a>
</li>
<li class="mui-table-view-cell mui-media">
<a href="javascript:void(0);">
<img class="mui-media-object mui-pull-left" src="../images/content.jpg">
<div class="mui-media-body">
3月11日XXX<span class='mui-pull-right gray_color'>3月10号</span>
<p class='mui-ellipsis'>您在日历XXX即将到来</p>
</div>
</a>
</li>
</ul>
</div>
<div id="item2mobile" class="mui-control-content">
<ul class="mui-table-view">
<li class="mui-table-view-cell mui-media">
<a href="javascript:void(0);">
<img class="mui-media-object mui-pull-left" src="../images/content.jpg">
<div class="mui-media-body">
订单消息<span class='mui-pull-right gray_color'>4月12号</span>
<p class='mui-ellipsis'>xxxxxxxx</p>
</div>
</a>
</li>
<li class="mui-table-view-cell mui-media">
<a href="javascript:void(0);">
<img class="mui-media-object mui-pull-left" src="../images/content.jpg">
<div class="mui-media-body">
订单消息<span class='mui-pull-right gray_color'>4月10号</span>
<p class='mui-ellipsis'>xxxxxxx</p>
</div>
</a>
</li>
</ul>
</div>
</div>
<script src="../js/mui.min.js"></script>
<script type="text/javascript" charset="utf-8">
mui.init();
mui.plusReady(function() {
//我使用plus,有时候会报undefined错误
console.log(plus.webview.currentWebview().id)
})
mui.ready(function() {
var urlx = this.document.URL.toString();
var modelx = "item1mobile";
if (urlx.lastIndexOf('?') != -1) {
modelx = urlx.substr(urlx.lastIndexOf('?') 1, urlx.length).split('=')[1];
}
var ar = document.getElementById("segmentedControl").querySelectorAll(".mui-control-item");
var reg = new RegExp('(\\s|^)' 'mui-active' '(\\s|$)');
var item1 = document.getElementById("item1mobile");
var item2 = document.getElementById("item2mobile");
//重置
ar[0].className = ar[0].className.replace(reg, '');
ar[1].className = ar[1].className.replace(reg, '');
item1.className = item1.className.replace(reg, '');
item1.className = item2.className.replace(reg, '');
if (modelx == "item1mobile") {
ar[0].className = " mui-active";
ar[1].className = ar[1].className.replace(reg, '');
item1.className = " mui-active";
item2.className = item2.className.replace(reg, '');
} else {
ar[0].className = ar[0].className.replace(reg, '');
ar[1].className = " mui-active";
item2.className = " mui-active";
item1.className = item1.className.replace(reg, '');
}
});
</script>
</body>
</html>
具体需求描述是这样的:父页面有两个链接需要导向segment页并根据传入的querystring激活不同的段落。
使用原始的url带参的方式,没有使用自定义扩展参数的方式,不过建议使用自定义扩展参数的方式。
mui.openwindow({
extras:{
'x_id':'10'//自定义扩展参数,可以用来处理页面间传值
}
})
在B页面这样去获取
var self=plus.webview.currentWebview();
var id=self.x_id;
具体的实现代码如下:
父页面html,打开链接依然使用mui.openwindow.
<ul>
<li><a class="mui-inline" href="http://ask.dcloud.net.cn/myxx.html?model=item1mobile" style="color:rgb(0, 0, 0) ;">商家XX</a></li>
<li>20</li>
</ul>
<ul>
<li ><a class="mui-inline" href="http://ask.dcloud.net.cn/myxx.html?model=item2mobile" style="color:rgb(0, 0, 0) ;">个人XX</a></li>
<li>10</li>
</ul>
子页面:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<title>我的XX</title>
<link href="http://ask.dcloud.net.cn/../css/mui.min.css" rel="stylesheet" />
<style type="text/css">
.mui-segmented-control .mui-control-item {
line-height: 28px;
}
.mui-title {
font-family: simhei;
}
.gray_color {
color: #8f8f94;
font-size: 0.8em;
}
</style>
</head>
<body>
<header class="mui-bar mui-bar-nav">
<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
<h1 class="mui-title">我的XX</h1>
</header>
<div class="mui-content">
<div class="mui-content-padded tab_title" style="margin: 0.65em 3em;">
<div id="segmentedControl" class="mui-segmented-control">
<a class="mui-control-item mui-active" href="#item1mobile">
商家XX
</a>
<a class="mui-control-item" href="#item2mobile">
个人XX
</a>
</div>
</div>
<div id="item1mobile" class="mui-control-content mui-active">
<ul class="mui-table-view">
<li class="mui-table-view-cell mui-media">
<a href="javascript:void(0);">
<img class="mui-media-object mui-pull-left" src="../images/content.jpg">
<div class="mui-media-body">
3月19日XXXXX<span class='mui-pull-right gray_color'>3月18号</span>
<p class='mui-ellipsis'>您在日历XXX中设定的XXX即将到来</p>
</div>
</a>
</li>
<li class="mui-table-view-cell mui-media">
<a href="javascript:void(0);">
<img class="mui-media-object mui-pull-left" src="../images/content.jpg">
<div class="mui-media-body">
3月15日XXX<span class='mui-pull-right gray_color'>3月14号</span>
<p class='mui-ellipsis'>您在XXX即将到来</p>
</div>
</a>
</li>
<li class="mui-table-view-cell mui-media">
<a href="javascript:void(0);">
<img class="mui-media-object mui-pull-left" src="../images/content.jpg">
<div class="mui-media-body">
3月11日XXX<span class='mui-pull-right gray_color'>3月10号</span>
<p class='mui-ellipsis'>您在日历XXX即将到来</p>
</div>
</a>
</li>
</ul>
</div>
<div id="item2mobile" class="mui-control-content">
<ul class="mui-table-view">
<li class="mui-table-view-cell mui-media">
<a href="javascript:void(0);">
<img class="mui-media-object mui-pull-left" src="../images/content.jpg">
<div class="mui-media-body">
订单消息<span class='mui-pull-right gray_color'>4月12号</span>
<p class='mui-ellipsis'>xxxxxxxx</p>
</div>
</a>
</li>
<li class="mui-table-view-cell mui-media">
<a href="javascript:void(0);">
<img class="mui-media-object mui-pull-left" src="../images/content.jpg">
<div class="mui-media-body">
订单消息<span class='mui-pull-right gray_color'>4月10号</span>
<p class='mui-ellipsis'>xxxxxxx</p>
</div>
</a>
</li>
</ul>
</div>
</div>
<script src="../js/mui.min.js"></script>
<script type="text/javascript" charset="utf-8">
mui.init();
mui.plusReady(function() {
//我使用plus,有时候会报undefined错误
console.log(plus.webview.currentWebview().id)
})
mui.ready(function() {
var urlx = this.document.URL.toString();
var modelx = "item1mobile";
if (urlx.lastIndexOf('?') != -1) {
modelx = urlx.substr(urlx.lastIndexOf('?') 1, urlx.length).split('=')[1];
}
var ar = document.getElementById("segmentedControl").querySelectorAll(".mui-control-item");
var reg = new RegExp('(\\s|^)' 'mui-active' '(\\s|$)');
var item1 = document.getElementById("item1mobile");
var item2 = document.getElementById("item2mobile");
//重置
ar[0].className = ar[0].className.replace(reg, '');
ar[1].className = ar[1].className.replace(reg, '');
item1.className = item1.className.replace(reg, '');
item1.className = item2.className.replace(reg, '');
if (modelx == "item1mobile") {
ar[0].className = " mui-active";
ar[1].className = ar[1].className.replace(reg, '');
item1.className = " mui-active";
item2.className = item2.className.replace(reg, '');
} else {
ar[0].className = ar[0].className.replace(reg, '');
ar[1].className = " mui-active";
item2.className = " mui-active";
item1.className = item1.className.replace(reg, '');
}
});
</script>
</body>
</html>
收起阅读 »
C#配置支付宝信息
参考支付宝官方的demo配置的方法,亲测可行.
public object GetPayInfo(string _amount)//_amount:付款金额
{
string orderInfo = OrderInfo(_amount);
// 对订单做RSA 签名
string sign = RSAFromPkcs8.sign(orderInfo, Config.Private_key, Config.Input_charset); //支付宝提供的Config.cs
//仅需对sign做URL编码
sign = HttpUtility.UrlEncode(sign, Encoding.UTF8);
string payInfo = orderInfo + "&sign=\"" + sign + "\"&"
+ getSignType();
return payInfo;
}
public string OrderInfo(string price)
{
Dictionary<string, string> payinfo = new Dictionary<string, string>();
payinfo.Add("service", "\"mobile.securitypay.pay\"");
payinfo.Add("partner", "\"" + Config.Partner + "\"");
payinfo.Add("seller_id", "\"" + Config.SELLER + "\"");
payinfo.Add("out_trade_no", "\"" + DateTime.Now.ToString("yyyyMMddHHmmssfff")+ "\"");
payinfo.Add("subject", "\"****\"");
payinfo.Add("body", "\"*****\"");
payinfo.Add("total_fee", "\"" + price.ToString() + "\"");
payinfo.Add("notify_url", "\"***********"");
payinfo.Add("payment_type", "\"1\"");
payinfo.Add("_input_charset", "\"UTF-8\"");
payinfo.Add("it_b_pay", "\"30m\"");
return Core.CreateLinkString(payinfo);
}
public String getSignType()
{
return "sign_type=\"RSA\"";
}
HB中的方法
function topay(rate) {
if (w) {
return;
}
//检查是否请求订单中
w = plus.nativeUI.showWaiting();
mui.getJSON("http://************/api/Pay", {
_amount: rate
}, function(data) {
w.close();
w = null;
if (data) {
plus.payment.request(pays["alipay"], data, function(result) {
plus.nativeUI.alert("支付成功:感谢您的支持。", function() {
mui.back();
}, "****");
}, function(error) {
if (error.code == 62001) {
mui.toast("您放弃支付.");
mui.back();
} else {
plus.nativeUI.alert("支付失败", null, "请前往会员中心再次对该订单尝试支付,支付失败:" + error.code);
}
});
}
});
}
参考支付宝官方的demo配置的方法,亲测可行.
public object GetPayInfo(string _amount)//_amount:付款金额
{
string orderInfo = OrderInfo(_amount);
// 对订单做RSA 签名
string sign = RSAFromPkcs8.sign(orderInfo, Config.Private_key, Config.Input_charset); //支付宝提供的Config.cs
//仅需对sign做URL编码
sign = HttpUtility.UrlEncode(sign, Encoding.UTF8);
string payInfo = orderInfo + "&sign=\"" + sign + "\"&"
+ getSignType();
return payInfo;
}
public string OrderInfo(string price)
{
Dictionary<string, string> payinfo = new Dictionary<string, string>();
payinfo.Add("service", "\"mobile.securitypay.pay\"");
payinfo.Add("partner", "\"" + Config.Partner + "\"");
payinfo.Add("seller_id", "\"" + Config.SELLER + "\"");
payinfo.Add("out_trade_no", "\"" + DateTime.Now.ToString("yyyyMMddHHmmssfff")+ "\"");
payinfo.Add("subject", "\"****\"");
payinfo.Add("body", "\"*****\"");
payinfo.Add("total_fee", "\"" + price.ToString() + "\"");
payinfo.Add("notify_url", "\"***********"");
payinfo.Add("payment_type", "\"1\"");
payinfo.Add("_input_charset", "\"UTF-8\"");
payinfo.Add("it_b_pay", "\"30m\"");
return Core.CreateLinkString(payinfo);
}
public String getSignType()
{
return "sign_type=\"RSA\"";
}
HB中的方法
function topay(rate) {
if (w) {
return;
}
//检查是否请求订单中
w = plus.nativeUI.showWaiting();
mui.getJSON("http://************/api/Pay", {
_amount: rate
}, function(data) {
w.close();
w = null;
if (data) {
plus.payment.request(pays["alipay"], data, function(result) {
plus.nativeUI.alert("支付成功:感谢您的支持。", function() {
mui.back();
}, "****");
}, function(error) {
if (error.code == 62001) {
mui.toast("您放弃支付.");
mui.back();
} else {
plus.nativeUI.alert("支付失败", null, "请前往会员中心再次对该订单尝试支付,支付失败:" + error.code);
}
});
}
});
}
收起阅读 »
这几天对HBuilder使用的感想
HBuilder,刚推出来过后不久,我就开始尝鲜,使用的时候让我感觉很是惊艳,自动提示的让我很满足,什么link 、script、meta标签,刷刷就出来了,这个必须点赞。后来继续尝试WEB 、APP开发,发现能上传自动帮我打包,感觉很轻松,因为我尝试过配置Phongap的环境,一个android,就有些麻烦,更何况还有IOS的。
接着,我用HBuilder来编写PC端的网页,也把这个工具推荐给了我同事们使用,大家都觉得挺好的。
过了一段时间,今年,领导让给写一个APP,我说用原生来开发吧(我这时已经会Android开发了),领导say no,用HTML 5开发,要兼容IOS、Android、WP、微信(微信是一个超级APP,可以算是一个小平台了),另外我给你安排一个专家,帮你做好微信支付、二维码扫描等等功能,我说不用,我知道一个工具,并集成了UI、还有很多开发好的功能,领导很满意,让我先开发一个DEMO出来。
清明节3天假,我第一天就写的差不多了,迅速写好了DEMO大部分工作,也在我的IOS手机上调试完毕,完成度大概80%,遗留了一些优化的工作(不要小瞧这部分),很顺利,很开心。于是在我的Android(小米2s)机器上,我第二天继续调试,我擦!,怎么各种各样的情况出现了,首先领导很重视的二维码扫描功能,我在这部Android机上调了半天,硬是识别不出来,后来是我手不小心抖了下,才发现只有小抖几下才识别的出来。这个我在问答部分也提了,得到官方回复,他们的小米2上是正常的,我不知道会不会与“S”有关系,在二维码扫描方面,扫描速度还是有点不行~~
接着我继续界面的跳转,我比较郁闷,原生Anroid有singleTop、singleTask模式的,我就想让webview重复利用下,例如 首页,我肯定不想让它重复打开吧。查下API,找到了plus.webview.show 方法,不行,IOS的显示不出来,找到了webview.show方法(我想这个两个方法实现逻辑是一样的),结果IOS都无法显示出来,Android到挺正常的,继续查openClosed,IOS的还是不好使,偶那个神哪!在问答区,我发出了提问,官方回复使用zindex,OK,果然能显示出来!但是接着我再首页打开新的页面,mygod,出了个圈圈,就不见踪影,明白了,新打开的界面没有继承打开者zindex属性,得,我只好重新下openWindow方法,将currentWebview,的zindex赋值给要打开的页面,结果还是不行,我想明白了我要取的是topWebview的zindex, 只好查API,没有获取topWebView的方法,得,将show()方法也重写下,并自己记录下topWebview,IOS正常了,达到我要的效果了。
在Android上来试试,Y的,Android倒不干了,使用webview.getStyle.zindex,发现,额,程序刚开始运行的时候得到undifined,而IOS得到的是0。算了算了,给重写的函数加两个判断分支,如果是android,执行原来的方法。呼,脑子终于清静了。
四天的时间,其实还算是挺快的。但是挺有点烦人的。webview的show方法,官方说下一版修复。恩,我也觉得应该将这方面的栈结果顺序维护好。我使用过plus.webview.all()函数,好像如果用show方法,在被show的之后的webview 貌似没被清空。
时间紧迫,我还没对这些缺陷做准确的验证,再提出来。
因此,希望官方提供一个专门的缺陷报告填写页过来,问答的方式 不是感觉很严谨呵。另外,mui的js源码也阅读了下,额,API不够用,当然可以自己写,但是官方的理解肯定深刻一些。5+部分的可否给一些原理图神马的。HB整体的发展路线图、蓝图,也能给一个,让大家也讨论下,让小伙伴们都参加进来,给予意见吧。
我还是挺喜欢HB的,但是这些或多或少的问题能得到解决。
HBuilder,刚推出来过后不久,我就开始尝鲜,使用的时候让我感觉很是惊艳,自动提示的让我很满足,什么link 、script、meta标签,刷刷就出来了,这个必须点赞。后来继续尝试WEB 、APP开发,发现能上传自动帮我打包,感觉很轻松,因为我尝试过配置Phongap的环境,一个android,就有些麻烦,更何况还有IOS的。
接着,我用HBuilder来编写PC端的网页,也把这个工具推荐给了我同事们使用,大家都觉得挺好的。
过了一段时间,今年,领导让给写一个APP,我说用原生来开发吧(我这时已经会Android开发了),领导say no,用HTML 5开发,要兼容IOS、Android、WP、微信(微信是一个超级APP,可以算是一个小平台了),另外我给你安排一个专家,帮你做好微信支付、二维码扫描等等功能,我说不用,我知道一个工具,并集成了UI、还有很多开发好的功能,领导很满意,让我先开发一个DEMO出来。
清明节3天假,我第一天就写的差不多了,迅速写好了DEMO大部分工作,也在我的IOS手机上调试完毕,完成度大概80%,遗留了一些优化的工作(不要小瞧这部分),很顺利,很开心。于是在我的Android(小米2s)机器上,我第二天继续调试,我擦!,怎么各种各样的情况出现了,首先领导很重视的二维码扫描功能,我在这部Android机上调了半天,硬是识别不出来,后来是我手不小心抖了下,才发现只有小抖几下才识别的出来。这个我在问答部分也提了,得到官方回复,他们的小米2上是正常的,我不知道会不会与“S”有关系,在二维码扫描方面,扫描速度还是有点不行~~
接着我继续界面的跳转,我比较郁闷,原生Anroid有singleTop、singleTask模式的,我就想让webview重复利用下,例如 首页,我肯定不想让它重复打开吧。查下API,找到了plus.webview.show 方法,不行,IOS的显示不出来,找到了webview.show方法(我想这个两个方法实现逻辑是一样的),结果IOS都无法显示出来,Android到挺正常的,继续查openClosed,IOS的还是不好使,偶那个神哪!在问答区,我发出了提问,官方回复使用zindex,OK,果然能显示出来!但是接着我再首页打开新的页面,mygod,出了个圈圈,就不见踪影,明白了,新打开的界面没有继承打开者zindex属性,得,我只好重新下openWindow方法,将currentWebview,的zindex赋值给要打开的页面,结果还是不行,我想明白了我要取的是topWebview的zindex, 只好查API,没有获取topWebView的方法,得,将show()方法也重写下,并自己记录下topWebview,IOS正常了,达到我要的效果了。
在Android上来试试,Y的,Android倒不干了,使用webview.getStyle.zindex,发现,额,程序刚开始运行的时候得到undifined,而IOS得到的是0。算了算了,给重写的函数加两个判断分支,如果是android,执行原来的方法。呼,脑子终于清静了。
四天的时间,其实还算是挺快的。但是挺有点烦人的。webview的show方法,官方说下一版修复。恩,我也觉得应该将这方面的栈结果顺序维护好。我使用过plus.webview.all()函数,好像如果用show方法,在被show的之后的webview 貌似没被清空。
时间紧迫,我还没对这些缺陷做准确的验证,再提出来。
因此,希望官方提供一个专门的缺陷报告填写页过来,问答的方式 不是感觉很严谨呵。另外,mui的js源码也阅读了下,额,API不够用,当然可以自己写,但是官方的理解肯定深刻一些。5+部分的可否给一些原理图神马的。HB整体的发展路线图、蓝图,也能给一个,让大家也讨论下,让小伙伴们都参加进来,给予意见吧。
我还是挺喜欢HB的,但是这些或多或少的问题能得到解决。
分享一个利用融云开发伪IM的思路
在移动互联时代,IM基本都是必备品了,可惜的是DCloud在短期内貌似还没有加入IM系统的可能,最近研究了一下三方的IM模块,在这里和大家分享一个利用融云Web达成伪IM的思路。
1、为什么选择Web版本?
融云在Web版本外还提供iOS和Android版本的SDK,但是需要一定的原生开发能力来进行支持,虽然都是封装好的API接口,开发起来不是很难,但是在我的系统里,暂时是不需要除了文字外的其余多余的功能,为了一些莫名的功能,耗费时间和精力去研究SDK,实在是有些得不偿失,等以后有时间或者需求的时候再去研究,所以需要添加很多IM组件的小伙伴可以绕道了。
2、Web版本的优缺点?
优点:集成简单,只需要简单的几步,就可以集成一套IM系统在APP中。
(1)引入Web版本SDK
http://res.websdk.rong.io/RongIMClient{-版本号}-min.js
(2)初始化web sdk
RongIMClient.init("appkey");
(3)设置链接状态监听器
RongIMClient.setConnectionStatusListener({
onChanged: function (status) {}
});
(4)链接融云服务器
RongIMClient.connect("token", {
onSuccess: function (userid) { },
onError: function (x) { }
});
(5)设置消息监听器
RongIMClient.getInstance().setOnReceiveMessageListener({
onReceived: function (message) { }
});
(6)得到RongIMClient实例对象,设置私人会话类型
var ins = RongIMClient.getInstance();
var contype = RongIMClient.ConversationType.PRIVATE;
(7)发送
ins.sendMessage(contype, "targetId", RongIMClient.TextMessage.obtain("发送消息内容"), null, {
onSuccess: function () { },
onError: function (data) { }
});
缺点:无法PUSH,这个实在要吐槽一下,其实融云支持PUSH,但是我和他们交涉良久,他们总是认为Web版本不需要PUSH这样的东西,其实就是简单的在他们服务器上注册用户的deviceToken,就为了注册个deviceToken,就要分别集成iOS和Android两套SDK,实在是麻烦。我让他们加,未果;让他们把SDK再底层的API开放给我,我自己增加注册,也是未果。
3、为什么是伪IM?
这个和无法PUSH是相关的,因为无法PUSH,所以只有用户在打开APP的情况下才能收到对方发送的消息,无法忍受,那么如何解决呢?
方案如下:
(1)在用户退出APP,或者pause的时候,主动断开和融云服务器的连接。
(2)每次发出IM消息的时候,消息要通知到自己的服务器,自己的服务器去融云的服务器获取用户在线状态
(a)如果用户在线,不处理
(b)如果用户不在线,利用个推的PUSH提醒用户打开APP
(3)用户进入APP的时候,重连融云服务器,接受消息。
这样,就可以做到一个简单的IM系统了。服务器端就不在这里详细描述啦^_^
PS1:融云的Web版本其实不止是文字,还有其余的一些,譬如客服,图片等等,集成起来还是比较简单的,当然,如果你的需求比较高,那么还是集成iOS或者Android版本的SDK要更好些。
PS2:还有一些三方IM,我没有仔细的研究过,但是应该大同小异。
在移动互联时代,IM基本都是必备品了,可惜的是DCloud在短期内貌似还没有加入IM系统的可能,最近研究了一下三方的IM模块,在这里和大家分享一个利用融云Web达成伪IM的思路。
1、为什么选择Web版本?
融云在Web版本外还提供iOS和Android版本的SDK,但是需要一定的原生开发能力来进行支持,虽然都是封装好的API接口,开发起来不是很难,但是在我的系统里,暂时是不需要除了文字外的其余多余的功能,为了一些莫名的功能,耗费时间和精力去研究SDK,实在是有些得不偿失,等以后有时间或者需求的时候再去研究,所以需要添加很多IM组件的小伙伴可以绕道了。
2、Web版本的优缺点?
优点:集成简单,只需要简单的几步,就可以集成一套IM系统在APP中。
(1)引入Web版本SDK
http://res.websdk.rong.io/RongIMClient{-版本号}-min.js
(2)初始化web sdk
RongIMClient.init("appkey");
(3)设置链接状态监听器
RongIMClient.setConnectionStatusListener({
onChanged: function (status) {}
});
(4)链接融云服务器
RongIMClient.connect("token", {
onSuccess: function (userid) { },
onError: function (x) { }
});
(5)设置消息监听器
RongIMClient.getInstance().setOnReceiveMessageListener({
onReceived: function (message) { }
});
(6)得到RongIMClient实例对象,设置私人会话类型
var ins = RongIMClient.getInstance();
var contype = RongIMClient.ConversationType.PRIVATE;
(7)发送
ins.sendMessage(contype, "targetId", RongIMClient.TextMessage.obtain("发送消息内容"), null, {
onSuccess: function () { },
onError: function (data) { }
});
缺点:无法PUSH,这个实在要吐槽一下,其实融云支持PUSH,但是我和他们交涉良久,他们总是认为Web版本不需要PUSH这样的东西,其实就是简单的在他们服务器上注册用户的deviceToken,就为了注册个deviceToken,就要分别集成iOS和Android两套SDK,实在是麻烦。我让他们加,未果;让他们把SDK再底层的API开放给我,我自己增加注册,也是未果。
3、为什么是伪IM?
这个和无法PUSH是相关的,因为无法PUSH,所以只有用户在打开APP的情况下才能收到对方发送的消息,无法忍受,那么如何解决呢?
方案如下:
(1)在用户退出APP,或者pause的时候,主动断开和融云服务器的连接。
(2)每次发出IM消息的时候,消息要通知到自己的服务器,自己的服务器去融云的服务器获取用户在线状态
(a)如果用户在线,不处理
(b)如果用户不在线,利用个推的PUSH提醒用户打开APP
(3)用户进入APP的时候,重连融云服务器,接受消息。
这样,就可以做到一个简单的IM系统了。服务器端就不在这里详细描述啦^_^
PS1:融云的Web版本其实不止是文字,还有其余的一些,譬如客服,图片等等,集成起来还是比较简单的,当然,如果你的需求比较高,那么还是集成iOS或者Android版本的SDK要更好些。
PS2:还有一些三方IM,我没有仔细的研究过,但是应该大同小异。
点击小图浏览大图,双击缩放,赶快拿走
功能实现:
点击小图浏览大图,双击放大,滑动切换,上滑或下滑关闭,双指放大或缩小(到了最小是自动关闭),预加载缓冲。
调用的第三方插件,好处就是没有借用jquery,而且滑动,缩放效果超流畅
最底下有效果图和所需文件下载
1,引入CSS及JS
<link rel="stylesheet prefetch" href="../plugin/touchPhotos/photoswipe.css">
<link rel="stylesheet prefetch" href="../plugin/touchPhotos/default-skin.css">
<script src="../plugin/touchPhotos/stopExecutionOnTimeout.js"></script>
<script src="../plugin/touchPhotos/photoswipe.min.js"></script>
<script src="../plugin/touchPhotos/photoswipe-ui-default.min.js"></script>
2,小图片容器(这里要获取原图地址及图片尺寸),我是通过JSON来获取的,就写个JSON的写法了
//容器 my-simple-gallery,获取图片的宽度和高度
data += '<div class="my-simple-gallery">';
for(var k=0; k<data.photos.length; k++){
data += '<figure itemprop="associatedMedia" itemscope=""><a href="'+data.photos[k].big+'" data-size="'+data.photos[k].size+'"><img src="'+data.photos[k].small+'" ></a></figure>';
}
data += '</div>';
自己讲data插入到ID里面了哈
然后就指定浏览图片的那个容器了
initPhotoSwipeFromDOM('.my-simple-gallery');
3.下面的代码写的有点恶心,直接拿去放到模板里面
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true" style="">
<div class="pswpbg"></div>
<div class="pswpscroll-wrap">
<div class="pswpcontainer" style="transform: translate3d(0px, 0px, 0px);">
<div class="pswpitem" style="display: block; transform: translate3d(-1792px, 0px, 0px);"><div class="pswpzoom-wrap" style="transform: translate3d(288px, 56px, 0px) scale(1);"></div></div>
<div class="pswpitem" style="transform: translate3d(0px, 0px, 0px);"><div class="pswpzoom-wrap" style="transform: translate3d(473px, 66.8125px, 0px) scale(0.146484375);"></div></div>
<div class="pswpitem" style="display: block; transform: translate3d(1792px, 0px, 0px);"><div class="pswpzoom-wrap" style="transform: translate3d(447px, 44px, 0px) scale(0.6904296875);"></div></div>
</div>
<div class="pswpui pswpui--fit pswpui--hidden">
<div class="pswptop-bar">
<div class="pswpcounter">4 / 4</div>
<button class="pswpbutton pswpbutton--close" title="Close (Esc)"></button>
<div class="pswppreloader">
<div class="pswppreloadericn">
<div class="pswppreloadercut">
<div class="pswppreloaderdonut"></div>
</div>
</div>
</div>
</div>
<div class="pswpcaption">
<div class="pswpcaptioncenter"></div>
</div>
</div>
</div>
</div>
4.处理大图的,直接拿去
var initPhotoSwipeFromDOM = function (gallerySelector) {
var parseThumbnailElements = function (el) {
var thumbElements = el.childNodes, numNodes = thumbElements.length, items = [], figureEl, childElements, linkEl, size, item;
for (var i = 0; i < numNodes; i++) {
if (window.CP.shouldStopExecution(1)) {
break;
}
figureEl = thumbElements[i];
if (figureEl.nodeType !== 1) {
continue;
}
linkEl = figureEl.children[0];
size = linkEl.getAttribute('data-size').split('x');
item = {
src: linkEl.getAttribute('href'),
w: parseInt(size[0], 10),
h: parseInt(size[1], 10)
};
if (figureEl.children.length > 1) {
item.title = figureEl.children[1].innerHTML;
}
if (linkEl.children.length > 0) {
item.msrc = linkEl.children[0].getAttribute('src');
}
item.el = figureEl;
items.push(item);
}
window.CP.exitedLoop(1);
return items;
};
var closest = function closest(el, fn) {
return el && (fn(el) ? el : closest(el.parentNode, fn));
};
var onThumbnailsClick = function (e) {
e = e || window.event;
e.preventDefault ? e.preventDefault() : e.returnValue = false;
var eTarget = e.target || e.srcElement;
var clickedListItem = closest(eTarget, function (el) {
return el.tagName && el.tagName.toUpperCase() === 'FIGURE';
});
if (!clickedListItem) {
return;
}
var clickedGallery = clickedListItem.parentNode, childNodes = clickedListItem.parentNode.childNodes, numChildNodes = childNodes.length, nodeIndex = 0, index;
for (var i = 0; i < numChildNodes; i++) {
if (window.CP.shouldStopExecution(2)) {
break;
}
if (childNodes[i].nodeType !== 1) {
continue;
}
if (childNodes[i] === clickedListItem) {
index = nodeIndex;
break;
}
nodeIndex++;
}
window.CP.exitedLoop(2);
if (index >= 0) {
openPhotoSwipe(index, clickedGallery);
}
return false;
};
var photoswipeParseHash = function () {
var hash = window.location.hash.substring(1), params = {};
if (hash.length < 5) {
return params;
}
var vars = hash.split('&');
for (var i = 0; i < vars.length; i++) {
if (window.CP.shouldStopExecution(3)) {
break;
}
if (!vars[i]) {
continue;
}
var pair = vars[i].split('=');
if (pair.length < 2) {
continue;
}
params[pair[0]] = pair[1];
}
window.CP.exitedLoop(3);
if (params.gid) {
params.gid = parseInt(params.gid, 10);
}
if (!params.hasOwnProperty('pid')) {
return params;
}
params.pid = parseInt(params.pid, 10);
return params;
};
var openPhotoSwipe = function (index, galleryElement, disableAnimation) {
var pswpElement = document.querySelectorAll('.pswp')[0], gallery, options, items;
items = parseThumbnailElements(galleryElement);
options = {
index: index,
galleryUID: galleryElement.getAttribute('data-pswp-uid'),
getThumbBoundsFn: function (index) {
var thumbnail = items[index].el.getElementsByTagName('img')[0], pageYScroll = window.pageYOffset || document.documentElement.scrollTop, rect = thumbnail.getBoundingClientRect();
return {
x: rect.left,
y: rect.top + pageYScroll,
w: rect.width
};
},
history: false,
focus: false
};
if (disableAnimation) {
options.showAnimationDuration = 0;
}
gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, options);
gallery.init();
};
var galleryElements = document.querySelectorAll(gallerySelector);
for (var i = 0, l = galleryElements.length; i < l; i++) {
if (window.CP.shouldStopExecution(4)) {
break;
}
galleryElements[i].setAttribute('data-pswp-uid', i + 1);
galleryElements[i].onclick = onThumbnailsClick;
}
window.CP.exitedLoop(4);
var hashData = photoswipeParseHash();
if (hashData.pid > 0 && hashData.gid > 0) {
openPhotoSwipe(hashData.pid - 1, galleryElements[hashData.gid - 1], true);
}
};
功能实现:
点击小图浏览大图,双击放大,滑动切换,上滑或下滑关闭,双指放大或缩小(到了最小是自动关闭),预加载缓冲。
调用的第三方插件,好处就是没有借用jquery,而且滑动,缩放效果超流畅
最底下有效果图和所需文件下载
1,引入CSS及JS
<link rel="stylesheet prefetch" href="../plugin/touchPhotos/photoswipe.css">
<link rel="stylesheet prefetch" href="../plugin/touchPhotos/default-skin.css">
<script src="../plugin/touchPhotos/stopExecutionOnTimeout.js"></script>
<script src="../plugin/touchPhotos/photoswipe.min.js"></script>
<script src="../plugin/touchPhotos/photoswipe-ui-default.min.js"></script>
2,小图片容器(这里要获取原图地址及图片尺寸),我是通过JSON来获取的,就写个JSON的写法了
//容器 my-simple-gallery,获取图片的宽度和高度
data += '<div class="my-simple-gallery">';
for(var k=0; k<data.photos.length; k++){
data += '<figure itemprop="associatedMedia" itemscope=""><a href="'+data.photos[k].big+'" data-size="'+data.photos[k].size+'"><img src="'+data.photos[k].small+'" ></a></figure>';
}
data += '</div>';
自己讲data插入到ID里面了哈
然后就指定浏览图片的那个容器了
initPhotoSwipeFromDOM('.my-simple-gallery');
3.下面的代码写的有点恶心,直接拿去放到模板里面
<div class="pswp" tabindex="-1" role="dialog" aria-hidden="true" style="">
<div class="pswpbg"></div>
<div class="pswpscroll-wrap">
<div class="pswpcontainer" style="transform: translate3d(0px, 0px, 0px);">
<div class="pswpitem" style="display: block; transform: translate3d(-1792px, 0px, 0px);"><div class="pswpzoom-wrap" style="transform: translate3d(288px, 56px, 0px) scale(1);"></div></div>
<div class="pswpitem" style="transform: translate3d(0px, 0px, 0px);"><div class="pswpzoom-wrap" style="transform: translate3d(473px, 66.8125px, 0px) scale(0.146484375);"></div></div>
<div class="pswpitem" style="display: block; transform: translate3d(1792px, 0px, 0px);"><div class="pswpzoom-wrap" style="transform: translate3d(447px, 44px, 0px) scale(0.6904296875);"></div></div>
</div>
<div class="pswpui pswpui--fit pswpui--hidden">
<div class="pswptop-bar">
<div class="pswpcounter">4 / 4</div>
<button class="pswpbutton pswpbutton--close" title="Close (Esc)"></button>
<div class="pswppreloader">
<div class="pswppreloadericn">
<div class="pswppreloadercut">
<div class="pswppreloaderdonut"></div>
</div>
</div>
</div>
</div>
<div class="pswpcaption">
<div class="pswpcaptioncenter"></div>
</div>
</div>
</div>
</div>
4.处理大图的,直接拿去
var initPhotoSwipeFromDOM = function (gallerySelector) {
var parseThumbnailElements = function (el) {
var thumbElements = el.childNodes, numNodes = thumbElements.length, items = [], figureEl, childElements, linkEl, size, item;
for (var i = 0; i < numNodes; i++) {
if (window.CP.shouldStopExecution(1)) {
break;
}
figureEl = thumbElements[i];
if (figureEl.nodeType !== 1) {
continue;
}
linkEl = figureEl.children[0];
size = linkEl.getAttribute('data-size').split('x');
item = {
src: linkEl.getAttribute('href'),
w: parseInt(size[0], 10),
h: parseInt(size[1], 10)
};
if (figureEl.children.length > 1) {
item.title = figureEl.children[1].innerHTML;
}
if (linkEl.children.length > 0) {
item.msrc = linkEl.children[0].getAttribute('src');
}
item.el = figureEl;
items.push(item);
}
window.CP.exitedLoop(1);
return items;
};
var closest = function closest(el, fn) {
return el && (fn(el) ? el : closest(el.parentNode, fn));
};
var onThumbnailsClick = function (e) {
e = e || window.event;
e.preventDefault ? e.preventDefault() : e.returnValue = false;
var eTarget = e.target || e.srcElement;
var clickedListItem = closest(eTarget, function (el) {
return el.tagName && el.tagName.toUpperCase() === 'FIGURE';
});
if (!clickedListItem) {
return;
}
var clickedGallery = clickedListItem.parentNode, childNodes = clickedListItem.parentNode.childNodes, numChildNodes = childNodes.length, nodeIndex = 0, index;
for (var i = 0; i < numChildNodes; i++) {
if (window.CP.shouldStopExecution(2)) {
break;
}
if (childNodes[i].nodeType !== 1) {
continue;
}
if (childNodes[i] === clickedListItem) {
index = nodeIndex;
break;
}
nodeIndex++;
}
window.CP.exitedLoop(2);
if (index >= 0) {
openPhotoSwipe(index, clickedGallery);
}
return false;
};
var photoswipeParseHash = function () {
var hash = window.location.hash.substring(1), params = {};
if (hash.length < 5) {
return params;
}
var vars = hash.split('&');
for (var i = 0; i < vars.length; i++) {
if (window.CP.shouldStopExecution(3)) {
break;
}
if (!vars[i]) {
continue;
}
var pair = vars[i].split('=');
if (pair.length < 2) {
continue;
}
params[pair[0]] = pair[1];
}
window.CP.exitedLoop(3);
if (params.gid) {
params.gid = parseInt(params.gid, 10);
}
if (!params.hasOwnProperty('pid')) {
return params;
}
params.pid = parseInt(params.pid, 10);
return params;
};
var openPhotoSwipe = function (index, galleryElement, disableAnimation) {
var pswpElement = document.querySelectorAll('.pswp')[0], gallery, options, items;
items = parseThumbnailElements(galleryElement);
options = {
index: index,
galleryUID: galleryElement.getAttribute('data-pswp-uid'),
getThumbBoundsFn: function (index) {
var thumbnail = items[index].el.getElementsByTagName('img')[0], pageYScroll = window.pageYOffset || document.documentElement.scrollTop, rect = thumbnail.getBoundingClientRect();
return {
x: rect.left,
y: rect.top + pageYScroll,
w: rect.width
};
},
history: false,
focus: false
};
if (disableAnimation) {
options.showAnimationDuration = 0;
}
gallery = new PhotoSwipe(pswpElement, PhotoSwipeUI_Default, items, options);
gallery.init();
};
var galleryElements = document.querySelectorAll(gallerySelector);
for (var i = 0, l = galleryElements.length; i < l; i++) {
if (window.CP.shouldStopExecution(4)) {
break;
}
galleryElements[i].setAttribute('data-pswp-uid', i + 1);
galleryElements[i].onclick = onThumbnailsClick;
}
window.CP.exitedLoop(4);
var hashData = photoswipeParseHash();
if (hashData.pid > 0 && hashData.gid > 0) {
openPhotoSwipe(hashData.pid - 1, galleryElements[hashData.gid - 1], true);
}
};
Android平台API等级配置 - minSdkVersion&targetSdkVersion
此文档将不再维护,请参考新文档:https://uniapp.dcloud.io/tutorial/app-android-minsdkversion
<a id="minsdkversion"></a>
minSdkVersion
minSdkVersion用于指定应用兼容的最低Android版本(API等级)。
如果APP某些功能无法支持低版本Android系统的设备,可以配置minSdkVersion确保APP只能安装到指定Android版本以上的设备。HBuilder|HBuilderX中可在manifest.json中进行配置。
⚠️注意: minSdkVersion升级时只能增加不能降低。minSdkVersion高的apk无法被minSdkVersion低的apk覆盖安装需要注意!!
可视化界面配置
打开项目的manifest.json文件,在 "App常用其它设置" 项中 "Android设置" 下的 minSdkVersion编辑框中输入要支持的最低Android版本号:
源码视图配置
打开项目的manifest.json文件,切换到 "源码视图"
- 5+APP项目
在plus->distribute->google节点下添加“minSdkVersion”字段,并配置要支持的最低Android版本号:"plus": { "distribute": { "google":{ "minSdkVersion": 22 } } }
- uni-app项目
在"app-plus"->distribute->android节点下添加“minSdkVersion”字段,并配置要支持的最低Android版本号:"app-plus": { "distribute": { "android":{ "minSdkVersion": 22 } } }
Number类型,整数值,应用要求的最低系统版本,必须大于等于19(Android4.4)小于等于23(android 6.0),默认值为19
示例中设置值为22表示应用只能安装在Android5.1及以上设备。
<a id="targetsdkversion"></a>
targetSdkVersion
HBuilder3.2.13版本开始targetSdkVersion默认值由26调整为28
注意:某些uni原生插件可能没有适配好targetSdkVersion为28会引起部分功能异常,碰到这类情况请联系插件开发者进行适配
⚠️注意: targetSdkVersion升级时只能增加不能降低。targetSdkVersion高的apk无法被targetSdkVersion低的apk覆盖安装需要注意!!
targetSdkVersion用于指定应用适配的Android版本(API等级)。
在Android系统中设置低版本的targetSdkVersion会使APP兼容模式运行,也就可能无法用到新系统的特性,甚至在兼容模式下运行可能存在安全漏洞等问题。
随着Android系统的升级,一些应用市场会要求设置较高的targetSdkVersion才可以提交。HBuilder|HBuilderX中可在manifest.json中进行配置。
可视化界面配置
打开项目的manifest.json文件,在 "App常用其它设置" 项中 "Android设置" 下的 targetSdkVersion编辑框中输入要支持的最低Android版本号:
源码视图配置
打开项目的manifest.json文件,切换到 "源码视图"
- 5+APP项目
在plus->distribute->google节点下添加“targetSdkVersion”字段:"plus": { "distribute": { "google":{ "targetSdkVersion": 26 } } }
- uni-app项目
在"app-plus"->distribute->android节点下添加“targetSdkVersion”字段:"app-plus": { "distribute": { "android":{ "targetSdkVersion": 26 } } }
Number类型,整数值,云端打包默认的targetSdkVersion值为26
- 5+App项目:最小值为19,最大值29
- uni-app项目:最小值为26,最大值29
HBuilderX2.8.3及以下版本targetSdkVersion最大值支持28
HBuilderX2.8.4+版本targetSdkVersion最大值支持29
Android版本列表
API等级与Android版本对应列表如下:
API等级 | Android版本号 |
---|---|
14 | Android4.0 |
15 | Android4.0.3 |
16 | Android4.1.2 |
17 | Android4.2.2 |
18 | Android4.3.1 |
19 | Android4.4.2 |
20 | Android4.4W.2 |
21 | Android5.0.1 |
22 | Android5.1 |
24 | Android7.0 |
25 | Android7.1.1 |
26 | Android8.0 |
27 | Android8.1 |
28 | Android9.0 |
29 | Android10.0(Android Q) |
30 | Android11.0 |
配置完成保存提交App云端打包后才能生效
此文档将不再维护,请参考新文档:https://uniapp.dcloud.io/tutorial/app-android-minsdkversion
<a id="minsdkversion"></a>
minSdkVersion
minSdkVersion用于指定应用兼容的最低Android版本(API等级)。
如果APP某些功能无法支持低版本Android系统的设备,可以配置minSdkVersion确保APP只能安装到指定Android版本以上的设备。HBuilder|HBuilderX中可在manifest.json中进行配置。
⚠️注意: minSdkVersion升级时只能增加不能降低。minSdkVersion高的apk无法被minSdkVersion低的apk覆盖安装需要注意!!
可视化界面配置
打开项目的manifest.json文件,在 "App常用其它设置" 项中 "Android设置" 下的 minSdkVersion编辑框中输入要支持的最低Android版本号:
源码视图配置
打开项目的manifest.json文件,切换到 "源码视图"
- 5+APP项目
在plus->distribute->google节点下添加“minSdkVersion”字段,并配置要支持的最低Android版本号:"plus": { "distribute": { "google":{ "minSdkVersion": 22 } } }
- uni-app项目
在"app-plus"->distribute->android节点下添加“minSdkVersion”字段,并配置要支持的最低Android版本号:"app-plus": { "distribute": { "android":{ "minSdkVersion": 22 } } }
Number类型,整数值,应用要求的最低系统版本,必须大于等于19(Android4.4)小于等于23(android 6.0),默认值为19
示例中设置值为22表示应用只能安装在Android5.1及以上设备。
<a id="targetsdkversion"></a>
targetSdkVersion
HBuilder3.2.13版本开始targetSdkVersion默认值由26调整为28
注意:某些uni原生插件可能没有适配好targetSdkVersion为28会引起部分功能异常,碰到这类情况请联系插件开发者进行适配
⚠️注意: targetSdkVersion升级时只能增加不能降低。targetSdkVersion高的apk无法被targetSdkVersion低的apk覆盖安装需要注意!!
targetSdkVersion用于指定应用适配的Android版本(API等级)。
在Android系统中设置低版本的targetSdkVersion会使APP兼容模式运行,也就可能无法用到新系统的特性,甚至在兼容模式下运行可能存在安全漏洞等问题。
随着Android系统的升级,一些应用市场会要求设置较高的targetSdkVersion才可以提交。HBuilder|HBuilderX中可在manifest.json中进行配置。
可视化界面配置
打开项目的manifest.json文件,在 "App常用其它设置" 项中 "Android设置" 下的 targetSdkVersion编辑框中输入要支持的最低Android版本号:
源码视图配置
打开项目的manifest.json文件,切换到 "源码视图"
- 5+APP项目
在plus->distribute->google节点下添加“targetSdkVersion”字段:"plus": { "distribute": { "google":{ "targetSdkVersion": 26 } } }
- uni-app项目
在"app-plus"->distribute->android节点下添加“targetSdkVersion”字段:"app-plus": { "distribute": { "android":{ "targetSdkVersion": 26 } } }
Number类型,整数值,云端打包默认的targetSdkVersion值为26
- 5+App项目:最小值为19,最大值29
- uni-app项目:最小值为26,最大值29
HBuilderX2.8.3及以下版本targetSdkVersion最大值支持28
HBuilderX2.8.4+版本targetSdkVersion最大值支持29
Android版本列表
API等级与Android版本对应列表如下:
API等级 | Android版本号 |
---|---|
14 | Android4.0 |
15 | Android4.0.3 |
16 | Android4.1.2 |
17 | Android4.2.2 |
18 | Android4.3.1 |
19 | Android4.4.2 |
20 | Android4.4W.2 |
21 | Android5.0.1 |
22 | Android5.1 |
24 | Android7.0 |
25 | Android7.1.1 |
26 | Android8.0 |
27 | Android8.1 |
28 | Android9.0 |
29 | Android10.0(Android Q) |
30 | Android11.0 |
配置完成保存提交App云端打包后才能生效
收起阅读 »