
分享一个利用融云开发伪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云端打包后才能生效
收起阅读 »
授权登录插件配置
此文档将不再维护,请参考新文档:https://uniapp.dcloud.io/tutorial/app-oauth
云端打包登录鉴权功能需要到第三方开发平台申请应用后获取相关配置参数,目前支持的平台包括:
- 微信:微信开放平台
- QQ:腾讯开放平台
- 微博:新浪微博开放平台
- 苹果登录:iOS 苹果授权登录(Sign in with Apple)教程
- Google登录:HBuilderX3.2.7+版本支持
- Facebook登录:HBuilder3.2.7+版本支持
配置参数需要提交云端打包后才能生效,如果需要真机运行生效请使用自定义基座
HBuilderX中配置登录鉴权
从微信/QQ/微博开放平台申请获取配置参数后,需在HBuilderX中配置并提交云端打包才能生效。
老版本HBuilder配置界面有差异,逻辑是一样的,建议更新使用HBuilderX
配置使用登录鉴权模块
打开应用的manifest.json文件,在“App模块配置”项中勾选“OAuth(登录鉴权)”:
配置登录鉴权参数
微信
在manifest.json文件“App模块配置”项的“OAuth(登录鉴权)”下,勾选“微信登录”项,并输入从微信开放平台申请的参数:
- appid:微信开放平台申请应用的AppID值;
- appSecret:微信开放平台申请应用的AppSecret值;
- UniversalLinks:iOS平台通用链接,必须与微信开放平台配置的一致,参考iOS平台微信SDK配置通用链接(Universal Links)。
在manifest.json文件“App模块配置”项的“OAuth(登录鉴权)”下,勾选“QQ登录”项,并输入从腾讯QQ开放平台申请的参数:
- appid:腾讯QQ开放平台申请应用的AppID值。
微博
在manifest.json文件“App模块配置”项的“OAuth(登录鉴权)”下,勾选“新浪微博登录”项,并输入从新浪微博开放平台申请的参数:
- appkey: "新浪微博平台应用appkey";
- appsecret: "新浪微博平台应用appsecret";
- redirect_uri: "新浪微博平台应用授权回调页地址"。
配置完成后Ctrl+S保存提交App云端打包生效。
<a id="secret"></a>
配置参数安全性问题
在HBuilder|HBuilderX中配置的参数云端打包后会保存在apk/ipa中,对于安全性要求高的开发者可能担心存在参数泄露的风险,可以采取以下方式处理。
-
JS代码中动态传参数
如登录服务AuthService的请求授权认证方法authorize,可以通过第三个参数options动态传入appid、appSecret等。
这些参数可以加密保存到js代码中或连网从服务器获取(避免保存在本地引起泄露风险,当然需要考虑网络传输过程的安全问题)。 -
通过服务器完成授权认证
根据OAuth规范,实际客户端授权只是为了获取授权临时票据(code),这时候可以仅在客户端配置appid参数调用登录服务AuthService的请求授权认证方法authorize获取临时票据,将票据提交到服务器完成后续的操作。
这种情况授权的参数(如appsecret)仅保存服务器即可,安全性更高。
在服务器的对接流程需要根据各开放平台的规范要求进行处理,如微信参考:授权后接口调用。
这时在HBuilder|HBuilderX中可以填入任意值提交云端打包
此文档将不再维护,请参考新文档:https://uniapp.dcloud.io/tutorial/app-oauth
云端打包登录鉴权功能需要到第三方开发平台申请应用后获取相关配置参数,目前支持的平台包括:
- 微信:微信开放平台
- QQ:腾讯开放平台
- 微博:新浪微博开放平台
- 苹果登录:iOS 苹果授权登录(Sign in with Apple)教程
- Google登录:HBuilderX3.2.7+版本支持
- Facebook登录:HBuilder3.2.7+版本支持
配置参数需要提交云端打包后才能生效,如果需要真机运行生效请使用自定义基座
HBuilderX中配置登录鉴权
从微信/QQ/微博开放平台申请获取配置参数后,需在HBuilderX中配置并提交云端打包才能生效。
老版本HBuilder配置界面有差异,逻辑是一样的,建议更新使用HBuilderX
配置使用登录鉴权模块
打开应用的manifest.json文件,在“App模块配置”项中勾选“OAuth(登录鉴权)”:
配置登录鉴权参数
微信
在manifest.json文件“App模块配置”项的“OAuth(登录鉴权)”下,勾选“微信登录”项,并输入从微信开放平台申请的参数:
- appid:微信开放平台申请应用的AppID值;
- appSecret:微信开放平台申请应用的AppSecret值;
- UniversalLinks:iOS平台通用链接,必须与微信开放平台配置的一致,参考iOS平台微信SDK配置通用链接(Universal Links)。
在manifest.json文件“App模块配置”项的“OAuth(登录鉴权)”下,勾选“QQ登录”项,并输入从腾讯QQ开放平台申请的参数:
- appid:腾讯QQ开放平台申请应用的AppID值。
微博
在manifest.json文件“App模块配置”项的“OAuth(登录鉴权)”下,勾选“新浪微博登录”项,并输入从新浪微博开放平台申请的参数:
- appkey: "新浪微博平台应用appkey";
- appsecret: "新浪微博平台应用appsecret";
- redirect_uri: "新浪微博平台应用授权回调页地址"。
配置完成后Ctrl+S保存提交App云端打包生效。
<a id="secret"></a>
配置参数安全性问题
在HBuilder|HBuilderX中配置的参数云端打包后会保存在apk/ipa中,对于安全性要求高的开发者可能担心存在参数泄露的风险,可以采取以下方式处理。
-
JS代码中动态传参数
如登录服务AuthService的请求授权认证方法authorize,可以通过第三个参数options动态传入appid、appSecret等。
这些参数可以加密保存到js代码中或连网从服务器获取(避免保存在本地引起泄露风险,当然需要考虑网络传输过程的安全问题)。 -
通过服务器完成授权认证
根据OAuth规范,实际客户端授权只是为了获取授权临时票据(code),这时候可以仅在客户端配置appid参数调用登录服务AuthService的请求授权认证方法authorize获取临时票据,将票据提交到服务器完成后续的操作。
这种情况授权的参数(如appsecret)仅保存服务器即可,安全性更高。
在服务器的对接流程需要根据各开放平台的规范要求进行处理,如微信参考:授权后接口调用。
这时在HBuilder|HBuilderX中可以填入任意值提交云端打包
收起阅读 »
Android证书的生成和指纹获取
可能iOS下各种证书的繁杂,所以官方只给出了iOS系统下证书的生成说明,Android证书的生成的确很简单,我简单说明一下:
1、安装JDK
2、在cmd下,进入到JDK的bin目录,输入:
keytool -genkey -alias yourapp -keyalg RSA -validity 20000 -keystore yourapp.keystore
说明:yourapp就是证书的别名,20000是证书的有效天数,yourapp.keystore就是生成的证书名字。
3、一路根据指示设置密码,组织等,注意密码是不会显示或者以***代替,但是其实已经输入了。确认后选择Y,生成的证书会在bin目录下。
4、获取证书的指纹,输入:
keytool -list -v -keystore "D:\Program Files\Java\jdk1.8.0_40\bin\yourapp.keystore" -alias yourapp
路径请使用自己安装JDK的路径代替。
这样就可以获取MD5、SHA1的证书指纹。
的确很简单,但是的确有人不会,譬如说我以前就不会,囧。
列出来,以供大家查阅。
可能iOS下各种证书的繁杂,所以官方只给出了iOS系统下证书的生成说明,Android证书的生成的确很简单,我简单说明一下:
1、安装JDK
2、在cmd下,进入到JDK的bin目录,输入:
keytool -genkey -alias yourapp -keyalg RSA -validity 20000 -keystore yourapp.keystore
说明:yourapp就是证书的别名,20000是证书的有效天数,yourapp.keystore就是生成的证书名字。
3、一路根据指示设置密码,组织等,注意密码是不会显示或者以***代替,但是其实已经输入了。确认后选择Y,生成的证书会在bin目录下。
4、获取证书的指纹,输入:
keytool -list -v -keystore "D:\Program Files\Java\jdk1.8.0_40\bin\yourapp.keystore" -alias yourapp
路径请使用自己安装JDK的路径代替。
这样就可以获取MD5、SHA1的证书指纹。
的确很简单,但是的确有人不会,譬如说我以前就不会,囧。
列出来,以供大家查阅。

【分享】头像类裁剪上传
夜深了,分享个头像类的裁剪上传,代码写的比较啰嗦。
虽然说用jquery渲染力差,没办法,还是用了。然后借助cropper来完成裁剪
<script src="plugin/avatar/js/jquery.min.js"></script>
<script src="plugin/avatar/js/cropper.js"></script>
<link href="plugin/avatar/css/cropper.css" rel="stylesheet">
照片容器
<a href="#picture"><div id="changeAvatar"></div></a>
裁剪照片的容器(样式就自己写了)
<div id="showEdit">
<header class="mui-bar mui-bar-nav mui-nav-bg" style="background:none; position:absolute; top:0;">
<a class="mui-icon iconfont icon-roundclosefill mui-pull-left" style="color:#ffffff; font-size:36px;" onclick="closeEdit();"></a>
<a class="mui-icon iconfont icon-roundcheckfill mui-pull-right" style="color:#ffffff; font-size:36px;" onclick="confirm();"></a>
</header>
<div id="report"></div>
</div>
下面就很简单了,调用下相机和相册选择图片。然后加入cropper的几行代码就搞定了
//拍照
function getImage() {
var cmr = plus.camera.getCamera();
cmr.captureImage( function (p) {
plus.io.resolveLocalFileSystemURL( p, function ( entry ) {
var localurl = entry.toLocalURL();//
$("#report").html('<img src="'+localurl+'">');
cutImg();
mui('#picture').popover('toggle');
});
});
}
//相册选取
function galleryImgs(){
plus.gallery.pick( function(e){
$("#report").html('<img src="'+e.files[0]+'">');
cutImg();
mui('#picture').popover('toggle');
}, function ( e ) {
//outSet( "取消选择图片" );
},{filter:"image",multiple:true});
}
照片裁剪类:
function cutImg(){
$("#showEdit").fadeIn();
var $image = $('#report > img');
$image.cropper({
aspectRatio: 1 / 1,
autoCropArea: 0.8
});
}
确定裁剪并上传(base64)
function confirm(){
$("#showEdit").fadeOut();
var $image = $('#report > img');
var dataURL = $image.cropper("getDataURL");
$("#changeAvatar").html('<img src="'+dataURL+'" />');
}
function postAvatar() {
var $image = $('#report > img');
var dataURL = $image.cropper("getDataURL");
var data = {
base64: dataURL
};
$.post(url,data,function(data){
//这里就自己写了哈
});
};
效果
有高手可以补充下的谢谢啦哈
夜深了,分享个头像类的裁剪上传,代码写的比较啰嗦。
虽然说用jquery渲染力差,没办法,还是用了。然后借助cropper来完成裁剪
<script src="plugin/avatar/js/jquery.min.js"></script>
<script src="plugin/avatar/js/cropper.js"></script>
<link href="plugin/avatar/css/cropper.css" rel="stylesheet">
照片容器
<a href="#picture"><div id="changeAvatar"></div></a>
裁剪照片的容器(样式就自己写了)
<div id="showEdit">
<header class="mui-bar mui-bar-nav mui-nav-bg" style="background:none; position:absolute; top:0;">
<a class="mui-icon iconfont icon-roundclosefill mui-pull-left" style="color:#ffffff; font-size:36px;" onclick="closeEdit();"></a>
<a class="mui-icon iconfont icon-roundcheckfill mui-pull-right" style="color:#ffffff; font-size:36px;" onclick="confirm();"></a>
</header>
<div id="report"></div>
</div>
下面就很简单了,调用下相机和相册选择图片。然后加入cropper的几行代码就搞定了
//拍照
function getImage() {
var cmr = plus.camera.getCamera();
cmr.captureImage( function (p) {
plus.io.resolveLocalFileSystemURL( p, function ( entry ) {
var localurl = entry.toLocalURL();//
$("#report").html('<img src="'+localurl+'">');
cutImg();
mui('#picture').popover('toggle');
});
});
}
//相册选取
function galleryImgs(){
plus.gallery.pick( function(e){
$("#report").html('<img src="'+e.files[0]+'">');
cutImg();
mui('#picture').popover('toggle');
}, function ( e ) {
//outSet( "取消选择图片" );
},{filter:"image",multiple:true});
}
照片裁剪类:
function cutImg(){
$("#showEdit").fadeIn();
var $image = $('#report > img');
$image.cropper({
aspectRatio: 1 / 1,
autoCropArea: 0.8
});
}
确定裁剪并上传(base64)
function confirm(){
$("#showEdit").fadeOut();
var $image = $('#report > img');
var dataURL = $image.cropper("getDataURL");
$("#changeAvatar").html('<img src="'+dataURL+'" />');
}
function postAvatar() {
var $image = $('#report > img');
var dataURL = $image.cropper("getDataURL");
var data = {
base64: dataURL
};
$.post(url,data,function(data){
//这里就自己写了哈
});
};
效果
有高手可以补充下的谢谢啦哈
收起阅读 »
夜深了,分享个思路,解决图片“表面”上秒传效果
接触5+有一个月了,也是接触APP开发有一个月了。今天先分享个思路出来,供大家参考。
图片上传传统解决方案:
1.选择图片
- 可能还要经过canvas的压缩处理
- 传到服务器返回图片url,或者点击发布同时上传
这样的流程无疑会造成发布时间慢,用户体验性差。
解决思路,现在还是个思路,明天开始敲
1.选择图片 - 返回图片本地地址
- 点击发布写入本地存储(websql),列表页就调用这个本地数据了
- 写个js在后台开始慢慢处理数据与服务器的同步
表面上用户会以为自己发布的图片已经秒速发布成功,还能在列表页看到刚刚发布的内容。实际还在后面慢慢的同步。
有高手可以帮忙完善下思路或者敲两下代码的感激不尽~
接触5+有一个月了,也是接触APP开发有一个月了。今天先分享个思路出来,供大家参考。
图片上传传统解决方案:
1.选择图片
- 可能还要经过canvas的压缩处理
- 传到服务器返回图片url,或者点击发布同时上传
这样的流程无疑会造成发布时间慢,用户体验性差。
解决思路,现在还是个思路,明天开始敲
1.选择图片 - 返回图片本地地址
- 点击发布写入本地存储(websql),列表页就调用这个本地数据了
- 写个js在后台开始慢慢处理数据与服务器的同步
表面上用户会以为自己发布的图片已经秒速发布成功,还能在列表页看到刚刚发布的内容。实际还在后面慢慢的同步。
有高手可以帮忙完善下思路或者敲两下代码的感激不尽~

分享一个检测iOS是否允许使用相机的Native.js
var AVCaptureDevice = plus.ios.importClass("AVCaptureDevice");
var Status = AVCaptureDevice.authorizationStatusForMediaType("vide");
if (3 != Status) {
var btnArray = ['确定'];
mui.confirm(' ','请在设置中允许使用相机',btnArray,function(e) {});
}
注意此功能在iOS7.0以上设备才支持!
var AVCaptureDevice = plus.ios.importClass("AVCaptureDevice");
var Status = AVCaptureDevice.authorizationStatusForMediaType("vide");
if (3 != Status) {
var btnArray = ['确定'];
mui.confirm(' ','请在设置中允许使用相机',btnArray,function(e) {});
}
注意此功能在iOS7.0以上设备才支持!
收起阅读 »
Native.js可以获取安卓的短信内容吗
想做个验证的功能,客户编辑了内容发到我的手机上,然后获取发过来的短信类容,POST到服务器然后进行绑定操作,不要问为啥不用短信接口给客户发送验证码,特么的太穷了,耗不起啊!
想做个验证的功能,客户编辑了内容发到我的手机上,然后获取发过来的短信类容,POST到服务器然后进行绑定操作,不要问为啥不用短信接口给客户发送验证码,特么的太穷了,耗不起啊!

关于使用中过程中的jQuery代码提示建议
真心很喜欢hbuilder,现在在努力的从sublime往hbuilder转;但是很多时候觉得hbuilder的很多地方还有待完善;不得不得在使用的过程中再打开sublime;例如jQuery的代码提示;
同样是打hover、click、post;回车,生成的代码如下;
hbuilder:
$(fucntion(){
$('#test').hover();
$('#test').click();
$.post()
})
sublime:
$(function(){
$('#test').hover(function() {
/ Stuff to do when the mouse enters the element /
}, function() {
/ Stuff to do when the mouse leaves the element /
});
$('#test').click(function(event) {
/ Act on the event /
});
$.post('/path/to/file', param1: 'value1', function(data, textStatus, xhr) {
/optional stuff to do after success /
});
})
还有就是直接打开远程服务器端的文件;hbuilder不能使用less和jQuery提示;
渴望hbuilder的代码提示功能可以加强;
愿hbuilder越来越完善;
真心很喜欢hbuilder,现在在努力的从sublime往hbuilder转;但是很多时候觉得hbuilder的很多地方还有待完善;不得不得在使用的过程中再打开sublime;例如jQuery的代码提示;
同样是打hover、click、post;回车,生成的代码如下;
hbuilder:
$(fucntion(){
$('#test').hover();
$('#test').click();
$.post()
})
sublime:
$(function(){
$('#test').hover(function() {
/ Stuff to do when the mouse enters the element /
}, function() {
/ Stuff to do when the mouse leaves the element /
});
$('#test').click(function(event) {
/ Act on the event /
});
$.post('/path/to/file', param1: 'value1', function(data, textStatus, xhr) {
/optional stuff to do after success /
});
})
还有就是直接打开远程服务器端的文件;hbuilder不能使用less和jQuery提示;
渴望hbuilder的代码提示功能可以加强;
愿hbuilder越来越完善;

MUI+HTML5+集成 IOS Weibo APP例子
最近闲得蛋痛,无意间发现了这么个东东,看看觉得还行,于是乎准备写个demo来玩玩,这一写不要紧,看到官方的文档,立马蛋都碎了~这~~
想查个API真是翻山越岭,走遍大江南北,什么百度、Google、360翻了个底朝天,就连个简单的demo都没人放出来,真是心碎了,好吧!事以至此还是看官方的组件例子吧,这个CSS啊,JS啊看得是头晕眼花,而且还不带任何关联的,这要写个成型的东东出来逼得死人哇。好在我这个人是个打不死的小强,我就不信了爷还征服不了你,立马菊花一紧,饭也不用吃了,水也不要喝了,烟也不要抽了,盯着个HBuilder码呀码呀码,码到一半都想砸电脑了,低头一看,得了还是别砸了,这尼玛是公司的Mac!认命吧,接着码,突然灵光一闪,醍醐灌顶,我艹,原来是这么回事哇,原来晚上才是IT男的天下,这句话真是一点都没错呀。好吧,经过一天的努力终于有个demo的样子了,收工了,打豆豆!!!!
最近闲得蛋痛,无意间发现了这么个东东,看看觉得还行,于是乎准备写个demo来玩玩,这一写不要紧,看到官方的文档,立马蛋都碎了~这~~
想查个API真是翻山越岭,走遍大江南北,什么百度、Google、360翻了个底朝天,就连个简单的demo都没人放出来,真是心碎了,好吧!事以至此还是看官方的组件例子吧,这个CSS啊,JS啊看得是头晕眼花,而且还不带任何关联的,这要写个成型的东东出来逼得死人哇。好在我这个人是个打不死的小强,我就不信了爷还征服不了你,立马菊花一紧,饭也不用吃了,水也不要喝了,烟也不要抽了,盯着个HBuilder码呀码呀码,码到一半都想砸电脑了,低头一看,得了还是别砸了,这尼玛是公司的Mac!认命吧,接着码,突然灵光一闪,醍醐灌顶,我艹,原来是这么回事哇,原来晚上才是IT男的天下,这句话真是一点都没错呀。好吧,经过一天的努力终于有个demo的样子了,收工了,打豆豆!!!!