DCloud喜获CSDN、html5梦工厂两项大奖
2015开发工具及服务年度大奖评选,是全球最大的中文 IT 社区 CSDN 针对开发工具及服务的评选大奖,目的在于推动开发服务及工具质量的整体提升。国内领先的H5平台DCloud,凭借在流应用技术方面的仔细钻研和突破,从200多家参选企业中脱颖而出,获得最具含金量的「最佳技术创新奖」。
“娜喊杯””由中国最大HTML5社区——HTML5梦工场举办,目的在于为快速推进HTML5在国内的发展和应用,整合行业优质资源促进上下游发展,打造健康的HTML5生态圈,DCloud凭借HBuilder完整的语法提示和dai码输入法、dai码块、大幅提升HTML、js、css的开发效率等诸多优势,一举获得 “娜喊杯”「2015 年度最佳 H5 开发者工具奖」。
DCloud是领先的HTML5平台厂商,W3C会员,HTML5中国产业联盟的发起单位,在HTML5领域陆续推出开发工具HBuilder、手机端强化引擎5 runtime、跨平台的轻巧框架mui,流应用等四款产品。其中前三款工具帮助开发者快速的开发出优质的HTML5应用,如今已有几十万开发者使用DCloud的产品开发了几十万App。
DCloud再接再厉又于2015年10月份发布国内首款流应用引擎。流应用的推出,让最终用户也感受到HTML5的好处,让不差钱的大型互联网公司也开始开发HTML5应用,它是一款基于HTM5 的技术的增强型js引擎,首先对H5进行了大幅强化,让js可以调用40万原生能力,并且大幅提升了H5的性能体验。经过强化过后,使得基于HTML5 开发的流应用可以达到原生应用的标准。
过去业内也有HTML5强化引擎或hybrid方案,比如cordova,但其在功能和性能上还无法达到原生应用的水准。HTML5 做到了这一点,利用js的动态语言特点,把手机端App的安装包拆解,流式下载到手机端。类似流媒体边看边下一样,再辅以特殊的压缩解码技术,使得流应用可以在5秒内完成App的下载-安装-启动全过程。
随着流应用引擎的发布,DCloud提供了从开发到发行的整体HTML5平台服务,一边服务开发商、一边服务手机用户,围绕HTML5建立一个更新颖的平台系统。
DCloud,一直以用户体验至上为原则
移动互联网一直是用户体验至上的原则,原来App强调使用体验好。经过几年的发展,在手机上使用App的过程已经不卡顿了,此时用户体验的痛点转移了,从使用App的体验不佳转移到获取、更新App的体验不佳了。现在获取传统原生App的过程,依然让用户等待过久。
所有的不爽,总会有人来改变它。流应用可以在5秒内完成App的下载-安装-启动,可以无感知差量更新(一般一款流应用的版本更新消耗的流量只有十几K)。把安卓原生应用导致的手机会变卡,变得更费电等大多数问题流应用都解决了,如此一来,很可能出现用户卸载原生App改用流应用的局面。
在2015开发工具及服务年度大奖评选的获奖理由里,专家们这样点评DCloud的流应用,“流应用引擎是一款基于HTM5 的技术的增强型js引擎,它首先对H5进行了大幅强化,让js可以调用40万原生能力,并且大幅提升了H5的性能体验,最让人期待的是这个流应用引擎是否可以颠覆原生App一统江山的局面。”
2015开发工具及服务年度大奖评选,是全球最大的中文 IT 社区 CSDN 针对开发工具及服务的评选大奖,目的在于推动开发服务及工具质量的整体提升。国内领先的H5平台DCloud,凭借在流应用技术方面的仔细钻研和突破,从200多家参选企业中脱颖而出,获得最具含金量的「最佳技术创新奖」。
“娜喊杯””由中国最大HTML5社区——HTML5梦工场举办,目的在于为快速推进HTML5在国内的发展和应用,整合行业优质资源促进上下游发展,打造健康的HTML5生态圈,DCloud凭借HBuilder完整的语法提示和dai码输入法、dai码块、大幅提升HTML、js、css的开发效率等诸多优势,一举获得 “娜喊杯”「2015 年度最佳 H5 开发者工具奖」。
DCloud是领先的HTML5平台厂商,W3C会员,HTML5中国产业联盟的发起单位,在HTML5领域陆续推出开发工具HBuilder、手机端强化引擎5 runtime、跨平台的轻巧框架mui,流应用等四款产品。其中前三款工具帮助开发者快速的开发出优质的HTML5应用,如今已有几十万开发者使用DCloud的产品开发了几十万App。
DCloud再接再厉又于2015年10月份发布国内首款流应用引擎。流应用的推出,让最终用户也感受到HTML5的好处,让不差钱的大型互联网公司也开始开发HTML5应用,它是一款基于HTM5 的技术的增强型js引擎,首先对H5进行了大幅强化,让js可以调用40万原生能力,并且大幅提升了H5的性能体验。经过强化过后,使得基于HTML5 开发的流应用可以达到原生应用的标准。
过去业内也有HTML5强化引擎或hybrid方案,比如cordova,但其在功能和性能上还无法达到原生应用的水准。HTML5 做到了这一点,利用js的动态语言特点,把手机端App的安装包拆解,流式下载到手机端。类似流媒体边看边下一样,再辅以特殊的压缩解码技术,使得流应用可以在5秒内完成App的下载-安装-启动全过程。
随着流应用引擎的发布,DCloud提供了从开发到发行的整体HTML5平台服务,一边服务开发商、一边服务手机用户,围绕HTML5建立一个更新颖的平台系统。
DCloud,一直以用户体验至上为原则
移动互联网一直是用户体验至上的原则,原来App强调使用体验好。经过几年的发展,在手机上使用App的过程已经不卡顿了,此时用户体验的痛点转移了,从使用App的体验不佳转移到获取、更新App的体验不佳了。现在获取传统原生App的过程,依然让用户等待过久。
所有的不爽,总会有人来改变它。流应用可以在5秒内完成App的下载-安装-启动,可以无感知差量更新(一般一款流应用的版本更新消耗的流量只有十几K)。把安卓原生应用导致的手机会变卡,变得更费电等大多数问题流应用都解决了,如此一来,很可能出现用户卸载原生App改用流应用的局面。
在2015开发工具及服务年度大奖评选的获奖理由里,专家们这样点评DCloud的流应用,“流应用引擎是一款基于HTM5 的技术的增强型js引擎,它首先对H5进行了大幅强化,让js可以调用40万原生能力,并且大幅提升了H5的性能体验,最让人期待的是这个流应用引擎是否可以颠覆原生App一统江山的局面。”
收起阅读 »使用Native.js实现打开页面默认弹出软键盘
先来体验下这个神奇的功能(兼容iOS和Android):
此功能需要在模块权限中配置Native.js
var nativeWebview, imm, InputMethodManager;
var initNativeObjects = function() {
if (mui.os.android) {
var main = plus.android.runtimeMainActivity();
var Context = plus.android.importClass("android.content.Context");
InputMethodManager = plus.android.importClass("android.view.inputmethod.InputMethodManager");
imm = main.getSystemService(Context.INPUT_METHOD_SERVICE);
} else {
nativeWebview = plus.webview.currentWebview().nativeInstanceObject();
}
};
var showSoftInput = function() {
if (mui.os.android) {
imm.toggleSoftInput(0, InputMethodManager.SHOW_FORCED);
} else {
nativeWebview.plusCallMethod({
"setKeyboardDisplayRequiresUserAction": false
});
}
setTimeout(function() {
//此处可写具体逻辑设置获取焦点的input
var inputElem = document.querySelector('input');
inputElem.focus();
}, 200);
};
mui.plusReady(function() {
initNativeObjects();
showSoftInput();
});
补充更新:
调用plus.webview.create()创建新的webview,会导致当前webview失去焦点,因此可能出现键盘闪一下又消失的情况。
解决方案:将创建webview的代码放到显示键盘之前(initNativeObjects方法之前)。
另外,为了强制当前webview获得焦点,可像如下方式修改showSoftInput方法:
var showSoftInput = function() {
var nativeWebview = plus.webview.currentWebview().nativeInstanceObject();
if (mui.os.android) {
//强制当前webview获得焦点
plus.android.importClass(nativeWebview);
nativeWebview.requestFocus();
imm.toggleSoftInput(0, InputMethodManager.SHOW_FORCED);
} else {
nativeWebview.plusCallMethod({
"setKeyboardDisplayRequiresUserAction": false
});
}
setTimeout(function() {
//此处可写具体逻辑设置获取焦点的input
var inputElem = document.querySelector('input');
inputElem.focus();
}, 200);
};
先来体验下这个神奇的功能(兼容iOS和Android):
此功能需要在模块权限中配置Native.js
var nativeWebview, imm, InputMethodManager;
var initNativeObjects = function() {
if (mui.os.android) {
var main = plus.android.runtimeMainActivity();
var Context = plus.android.importClass("android.content.Context");
InputMethodManager = plus.android.importClass("android.view.inputmethod.InputMethodManager");
imm = main.getSystemService(Context.INPUT_METHOD_SERVICE);
} else {
nativeWebview = plus.webview.currentWebview().nativeInstanceObject();
}
};
var showSoftInput = function() {
if (mui.os.android) {
imm.toggleSoftInput(0, InputMethodManager.SHOW_FORCED);
} else {
nativeWebview.plusCallMethod({
"setKeyboardDisplayRequiresUserAction": false
});
}
setTimeout(function() {
//此处可写具体逻辑设置获取焦点的input
var inputElem = document.querySelector('input');
inputElem.focus();
}, 200);
};
mui.plusReady(function() {
initNativeObjects();
showSoftInput();
});
补充更新:
调用plus.webview.create()创建新的webview,会导致当前webview失去焦点,因此可能出现键盘闪一下又消失的情况。
解决方案:将创建webview的代码放到显示键盘之前(initNativeObjects方法之前)。
另外,为了强制当前webview获得焦点,可像如下方式修改showSoftInput方法:
var showSoftInput = function() {
var nativeWebview = plus.webview.currentWebview().nativeInstanceObject();
if (mui.os.android) {
//强制当前webview获得焦点
plus.android.importClass(nativeWebview);
nativeWebview.requestFocus();
imm.toggleSoftInput(0, InputMethodManager.SHOW_FORCED);
} else {
nativeWebview.plusCallMethod({
"setKeyboardDisplayRequiresUserAction": false
});
}
setTimeout(function() {
//此处可写具体逻辑设置获取焦点的input
var inputElem = document.querySelector('input');
inputElem.focus();
}, 200);
};
收起阅读 »
h5+实时定位问题 setInterval中只执行一次getCurrentPosition
参考问题:http://ask.dcloud.net.cn/question/10078
刚开始学习html5+App 要实现定时发送位置信息功能,看了官方示例如下,确实简单明了
官网示例
<!DOCTYPE html>
<html>****
<head>
<meta charset="utf-8">
<title>Geolocation Example</title>
<script type="text/javascript" >
// 扩展API加载完毕后调用onPlusReady回调函数
document.addEventListener( "plusready", onPlusReady, false );
// 扩展API加载完毕,现在可以正常调用扩展API
function onPlusReady() {
plus.geolocation.getCurrentPosition( function ( p ) {
alert( "Geolocation\nLatitude:" + p.coords.latitude + "\nLongitude:" + p.coords.longitude + "\nAltitude:" + p.coords.altitude );
}, function ( e ) {
alert( "Geolocation error: " + e.message );
} );
}
</script>
</head>
<body >
</body>
</html>
不过问题来了,现在要自动隔段时间就发送一下位置了,采用上面的方法getPosBaidu,加上下面这句,期间可能遇到其他问题plus未定义啊什么的,不过不是关键
setInterval('getPosBaidu()', 10000);
调试结果:
执行了一次getPosBaidu
然后就over了
原因可能是阻塞啊 什么的,具体我也不太懂了,就觉得跟setInterval有关
怎么办?
看了一堆dcloud定位相关问题的贴子,特别混乱。
总结下可能的方法,
1、自己开发5+sdk插件替换getCurrentPosition
2、Native.js 调用本地替换getCurrentPosition
3、换掉setInterval 用别的方法定时触发
4、曲线利用 plus.geolocation.watchPosition
结果当然是1,2太复杂,还没动。
4 方法,看了半天,watchPosition 这个方法也没太懂,继续setInterval 只用plus.geolocation.watchPosition 直接取代getCurrentPosition :开始监视触发一次,然后清楚监视就是一次啦,不过依然没用。
3方法目前算是成功了。
有其他方法的同学,请告知,毕竟这个办法也太曲线了。
html5+ 官方需要继续改进,要加快进度。 控制变量法:自己做到最好,历史才能选择究竟谁才是未来的主流,希望html5+应用火起来。
End。。。
参考问题:http://ask.dcloud.net.cn/question/10078
刚开始学习html5+App 要实现定时发送位置信息功能,看了官方示例如下,确实简单明了
官网示例
<!DOCTYPE html>
<html>****
<head>
<meta charset="utf-8">
<title>Geolocation Example</title>
<script type="text/javascript" >
// 扩展API加载完毕后调用onPlusReady回调函数
document.addEventListener( "plusready", onPlusReady, false );
// 扩展API加载完毕,现在可以正常调用扩展API
function onPlusReady() {
plus.geolocation.getCurrentPosition( function ( p ) {
alert( "Geolocation\nLatitude:" + p.coords.latitude + "\nLongitude:" + p.coords.longitude + "\nAltitude:" + p.coords.altitude );
}, function ( e ) {
alert( "Geolocation error: " + e.message );
} );
}
</script>
</head>
<body >
</body>
</html>
不过问题来了,现在要自动隔段时间就发送一下位置了,采用上面的方法getPosBaidu,加上下面这句,期间可能遇到其他问题plus未定义啊什么的,不过不是关键
setInterval('getPosBaidu()', 10000);
调试结果:
执行了一次getPosBaidu
然后就over了
原因可能是阻塞啊 什么的,具体我也不太懂了,就觉得跟setInterval有关
怎么办?
看了一堆dcloud定位相关问题的贴子,特别混乱。
总结下可能的方法,
1、自己开发5+sdk插件替换getCurrentPosition
2、Native.js 调用本地替换getCurrentPosition
3、换掉setInterval 用别的方法定时触发
4、曲线利用 plus.geolocation.watchPosition
结果当然是1,2太复杂,还没动。
4 方法,看了半天,watchPosition 这个方法也没太懂,继续setInterval 只用plus.geolocation.watchPosition 直接取代getCurrentPosition :开始监视触发一次,然后清楚监视就是一次啦,不过依然没用。
3方法目前算是成功了。
有其他方法的同学,请告知,毕竟这个办法也太曲线了。
html5+ 官方需要继续改进,要加快进度。 控制变量法:自己做到最好,历史才能选择究竟谁才是未来的主流,希望html5+应用火起来。
End。。。
【分享】图片下载本地缓存时间戳显示图片方法
参考文章
http://ask.dcloud.net.cn/article/256
http://ask.dcloud.net.cn/article/397
说明:为了方便,里面使用的图片来源是从上面文章中的源码项目获取的.
说明:参考了上面文章中的思路,然后自己重新写了一个较为完整的图片本地缓存显示工具.
功能
1.第一次显示图片时下载到本地,然后之后如果本地存在缓存(根据url),则显示本地缓存的图片
- 基于plus的storage,对每一个图片的缓存进行控制管理,增加时间戳参数,来控制本地缓存的有效时间
- 将所有的下载任务放入队列中,进行统一下载管理,增加最大并发请求数,防止一次性请求过多损耗性能
- 加入了图片缓存池机制,对于同一个下载路径的图片不会多次下载,而是填入缓存池,下载后统一回调
- 修改了图片本地路径的获取方法,摆脱第三方依赖
- 重构了代码,并入开发框架中
使用方法
* 外部API:注意,需要显示的图片需要有data-img-localcache这个属性(用来放目标url),dom需要是原生对象
* 清除所有图片缓存:MobileFrame.ImageUtil.ImageLoaderFactory.clearLoaderImgsCache(successCb,errorCb)
* 采取本地缓存显示所有图片:MobileFrame.ImageUtil.ImageLoaderFactory.setAllNetImgsWithLocalCache();
* 清除某一张图片的本地缓存:MobileFrame.ImageUtil.ImageLoaderFactory.clearNetUrlImgCache(src);
* 显示某一张图片:MobileFrame.ImageUtil.ImageLoaderFactory.setImgWidthLocalCache(dom,src);
源码
/**
* @description 移动开发框架
* @author dailc dailc
* @version 1.0
* @time 2016-01-11 16:57:57
* 功能模块:只依赖于plus系统
* @see http://ask.dcloud.net.cn/people/%E6%92%92%E7%BD%91%E8%A6%81%E8%A7%81%E9%B1%BC
* 图片本地缓存模块********************************
* 1.本地缓存显示图片
* 2.增加storage,增加每一个本地缓存的有效时间戳
* 3.增加自定义设置方法,可以根据不同需求,对参数进行修改
* 4.采用下载队列进行下载管理,增加最大并发请求数,防止一次性请求过多损耗性能
* 5.修改了图片本地路径的获取方法,摆脱第三方依赖
* 6.重构代码
* 外部API:注意,需要显示的图片需要有data-img-localcache这个属性(用来放目标url)
* 清除所有图片缓存:MobileFrame.ImageUtil.ImageLoaderFactory.clearLoaderImgsCache(successCb,errorCb)
* 采取本地缓存显示所有图片:MobileFrame.ImageUtil.ImageLoaderFactory.setAllNetImgsWithLocalCache();
* 清除某一张图片的本地缓存:MobileFrame.ImageUtil.ImageLoaderFactory.clearNetUrlImgCache(src);
* 显示某一张图片:MobileFrame.ImageUtil.ImageLoaderFactory.setImgWidthLocalCache(dom,src);
* 图片本地缓存模块完毕********************************
*/
(function(global) {
/**
* 定义全局函数对象 define出来的
*/
var mapping = {};
/**
* 缓存,正在用到的对象,函数中return 出来的,这样就不需要重复执行函数
*/
var cache = {};
/**
* @description 模块定义
* @param {String} id id
* @param {Function} func 对应的函数对象
*/
global.define = function(id, func) {
mapping[id] = func;
};
/**
* @description 生成模块对象,并采用本地缓存
* @param {String} id
*/
global.require = function(id) {
if (!/\.js$/.test(id)) {
id += '.js';
}
if (cache[id]) {
return cache[id];
} else {
return cache[id] = mapping[id]({});
}
};
/**
* @description 配置全局工具类以及一些需要用到的全局函数
*/
(function() {
global.MobileFrame = {};
/**
* 空函数
*/
MobileFrame.noop = function() {};
/**
* @description each遍历操作
* @param {type} elements
* @param {type} callback
* @returns {global}
*/
MobileFrame.each = function(elements, callback, hasOwnProperty) {
if (!elements) {
return this;
}
if (typeof elements.length === 'number') {
[].every.call(elements, function(el, idx) {
return callback.call(el, idx, el) !== false;
});
} else {
for (var key in elements) {
if (hasOwnProperty) {
if (elements.hasOwnProperty(key)) {
if (callback.call(elements[key], key, elements[key]) === false) return elements;
}
} else {
if (callback.call(elements[key], key, elements[key]) === false) return elements;
}
}
}
return global;
};
/**
* @description plusReady
* @param {Function} callback
* @returns {global} 返回的是global
*/
MobileFrame.plusReady = function(callback) {
if (window.plus) {
setTimeout(function() { //解决callback与plusready事件的执行时机问题(典型案例:showWaiting,closeWaiting)
callback();
}, 0);
} else {
document.addEventListener("plusready", function() {
callback();
}, false);
}
return global;
};
/**
* @description 得到相对路径对应的key,这个key可以使缓存池的或者是本地缓存键值
* 主要作用是去除非法字符
* @param {String} relativePath
*/
MobileFrame.getRelativePathKey = function(relativePath) {
var finalKey =
// relativePath.replace('\/', '').replace('.', '').replace('\/', '')
// .replace('_download', '').replace('jpg', '');
relativePath.replace(/[&\|\\\*^%$#@\-]/g, "");
return finalKey;
};
/**
* @description 更改url类型,去除cache,因为cache会造成一些困扰
* @param {String} url 传入的url
*/
MobileFrame.changImgUrlTypeNoCache = function(url) {
url = url || '';
if (url.indexOf('?') != -1) {
url += '&timeRandKey=' + Math.random();
} else {
url += '?timeRandKey=' + Math.random();
}
return url;
};
/**
* @description 删除指定路径的文件
* @param {String} relativePath 绝对路径或相对路径例如: _downloads/imgs/test.jpg
* @param {Function} successCallback 删除成功回调
* @param {Function} errorCallback 失败回调
*/
MobileFrame.delFile = function(relativePath, successCallback, errorCallback) {
if (!relativePath) {
return;
}
MobileFrame.plusReady(function() {
plus.io.resolveLocalFileSystemURL(relativePath, function(entry) {
entry.remove(function(entry) {
if (successCallback && typeof(successCallback) == 'function') {
successCallback(true);
}
}, function(e) {
if (errorCallback && typeof(errorCallback) == 'function') {
errorCallback('删除文件失败!');
}
});
}, function() {
if (errorCallback && typeof(errorCallback) == 'function') {
errorCallback('打开文件路径失败!');
}
});
});
};
/**
* @description 判断网络状态
*/
function GetNetWorkState() {
var NetStateStr = '未知';
var types = {};
types[plus.networkinfo.CONNECTION_UNKNOW] = "未知";
types[plus.networkinfo.CONNECTION_NONE] = "未连接网络";
types[plus.networkinfo.CONNECTION_ETHERNET] = "有线网络";
types[plus.networkinfo.CONNECTION_WIFI] = "WiFi网络";
types[plus.networkinfo.CONNECTION_CELL2G] = "2G蜂窝网络";
types[plus.networkinfo.CONNECTION_CELL3G] = "3G蜂窝网络";
types[plus.networkinfo.CONNECTION_CELL4G] = "4G蜂窝网络";
NetStateStr = types[plus.networkinfo.getCurrentType()];
return NetStateStr;
};
/**
* @description 判断是否有网络
*/
MobileFrame.IsNetWorkCanUse = function() {
var IsCanUseNetWork = false;
if (GetNetWorkState() == '未知' || GetNetWorkState() == '未连接网络') {
IsCanUseNetWork = false;
} else {
IsCanUseNetWork = true;
}
return IsCanUseNetWork;
};
})();
/**
* @description 定义模块功能-图片工具类
*/
define('scripts/Core/ImageUtil.js', function(exports) {
/**
* @description 图片加载工厂,包含图片加载方法
* 例如:时间戳控制缓存,下载队列批次下载,默认图片,下载loading图片,下载失败图片
* 注意相对路径在Android:/sdcard/Android/data/io.dcloud.HBuilder/.HBuilder/...
* iOS:/Library/Pandora/...
*/
(function(ImgLoaderFactory) {
/**
* 默认的options
*/
var defaultSettingOptions = {
//默认的图片缓存目录-存到应用的downloads/imgs下
'imgStoragePath': "_downloads/imgs/",
//默认图片的基座路径
'defaultImgBase': '../../img/mobileFrame/',
//loading图片的名称
'loadingImgName': 'img_loading.jpg',
//error图片名称
'errorImgName': 'img_error.jpg',
//图片缓存的时间戳,毫秒单位,默认为1天
'imgsTimeStamp': 1000 * 60 * 60 * 24 * 1,
//同时最多的downloader 并发下载数目,默认为3个
'concurrentDownloadCount': 3,
//单个下载任务最大的请求时间,防止一些机型上无法触发错误回调,单位毫秒,默认10秒
'maxTimeSingleDownloadTaskSpend': 1000 * 10
};
//默认的下载图片临时变量
var defaultLoadingImg = defaultSettingOptions['defaultImgBase'] + defaultSettingOptions['loadingImgName'];
//默认的显示图片临时变量
var defaultImg = defaultSettingOptions['defaultImgBase'] + defaultSettingOptions['errorImgName'];
/**
* 图片缓存的session头部
*/
var imageSessionKey_header = 'imageSessionKey_util_imgs_';
/**
* 图片缓存的session的管理者
*/
var imageSessionManagerKey = 'imageSessionKey_util_Manager';
/**
* 图片缓存池,用来解决多张图片并发请求问题
* 默认是空的,当有多张图片是同一个请求时,缓存池子中会有数据
* 格式 {'url1':[dom1,dom2]}
*/
var requestImgsPool = {};
/**
* 并发下载任务,包括下载队列,处理最大并发数下载
*/
var concurrentDownloadTask = {
//任务池-还没有下载的任务
Queue: [],
//当前正在下载的任务数量
CurrentTaskCount: 0
};
/**
* 当前的任务队列,包含任务的名称,以及时间戳-用来控制最大的超时时间,防止不能正常触发回调
* 包含:
* taskObj,timeBegin
* 格式:{url1:{task1,time1}}
*/
var currentDownloadTasks = {};
/**
* @description 将对应的图片缓存键值添加进入图片缓存管理中
* @param {String} key 图片路径对应的key
*/
function addImageSessionKeyToManager(key) {
//获取管理者
var manager = plus.storage.getItem(imageSessionManagerKey);
if (manager == null) {
//如果以前的缓存为空,生成缓存
manager = [];
} else {
try {
manager = JSON.parse(manager);
} catch (e) {}
}
if (manager.indexOf(key) == -1) {
manager.push(key);
}
plus.storage.setItem(imageSessionManagerKey, JSON.stringify(manager));
};
/**
* @description 从缓存管理中移除相应的图片缓存
* @param {String} key 图片路径对应的key
*/
function removeImageSessionKeyFromManager(key) {
//获取管理者
var manager = plus.storage.getItem(imageSessionManagerKey);
if (manager == null) {
//这时候肯定没有离线缓存
return;
}
try {
manager = JSON.parse(manager);
} catch (e) {}
var index = -1;
for (var i = 0; i < manager.length || 0; i++) {
if (manager[i] == key) {
index = i;
}
}
if (index != -1) {
//删除对应的index位置
manager.splice(index, 1);
//重新存储
plus.storage.setItem(imageSessionManagerKey, JSON.stringify(manager));
}
};
/**
* 设置图片缓存key
* @param {String} url
* @param {JSON} value 存进去的是图片相关的所有属性,包括时间戳,本地路径等
*/
function setImageSessionItem(url, value) {
if (url == null) {
return;
}
//然后添加进入缓存管理者中
addImageSessionKeyToManager(url);
url = imageSessionKey_header + MobileFrame.getRelativePathKey(url);
value = (value != null) ? value : '';
value = (typeof(value) == 'string') ? value : JSON.stringify(value);
plus.storage.setItem(url, value);
};
/**
* 获取图片缓存key
* @param {String} url
* @return {JSON} item 返回的是一个json对象,包括图片相关的所有属性,包括时间戳,本地路径等
* @example 包含属性:time localPath
*/
function getImageSessionItem(url) {
if (url == null) {
return null;
}
//去除非法字符
url = imageSessionKey_header + MobileFrame.getRelativePathKey(url);
var item = plus.storage.getItem(url);
try {
if (item != null) {
item = JSON.parse(item);
}
} catch (e) {}
return item;
};
/**
* 移除图片缓存key
* @param {String} url
*/
function removeImageSessionItem(url) {
if (url == null) {
return null;
}
removeImageSessionKeyFromManager(url);
//去除非法字符
url = imageSessionKey_header + MobileFrame.getRelativePathKey(url);
var items = plus.storage.removeItem(url);
};
/**
* @description 设置图片的src地址,根据一个url,为所有需要的图片赋值
* @param {HTMLElement} $img 图片的dom,这里为原生dom对象
* @param {String} srcUrl 图片的路径
* @param {String} relativePath 相对路径,用来判断缓存池的
*/
function setImgSrc($img, srcUrl, relativePath) {
if (!srcUrl) {
return;
}
setImgSrcByDom($img, srcUrl);
//如果缓存池子中存在图片
if (!relativePath) {
return;
}
var relativePathKey = MobileFrame.getRelativePathKey(relativePath);
if (requestImgsPool && requestImgsPool[relativePathKey]) {
var imgsData = requestImgsPool[relativePathKey];
//如果是数组
if (Array.isArray(imgsData)) {
for (var i = 0; i < imgsData.length; i++) {
setImgSrcByDom(imgsData[i], srcUrl);
}
} else {
//单条数据--单个dom对象
setImgSrcByDom(imgsData, srcUrl);
}
if (srcUrl != defaultLoadingImg) {
//如果不是loading图片就清空
//清空图片池子中的该条键值
requestImgsPool[relativePathKey] = null;
}
}
};
/**
* @description 设置图片的src地址,一个dom和一个 src一一对应
* @param {HTMLElement} $img 图片的dom,原生dom对象
* @param {String} srcUrl 图片的路径
*/
function setImgSrcByDom($img, srcUrl) {
if (!$img || !($img instanceof HTMLElement)) {
//console.log('该dom不是原生对象,url:' + srcUrl);
return;
}
//暂时去除loading图片的独特样式
// if (srcUrl == defaultLoadingImg) {
// //默认的loading图片,修改css
// $img.style.maxWidth = '30px';
// $img.style.maxHeight = '30px';
// } else {
// //恢复普通高度
// $img.style.maxWidth = '100%';
// $img.style.maxHeight = '100%';
// }
srcUrl = MobileFrame.changImgUrlTypeNoCache(srcUrl);
$img.setAttribute('src', srcUrl);
};
/**
* @description 移除所有的图片缓存键
*/
function clearAllImageSessionKey() {
MobileFrame.plusReady(function() {
var manager = plus.storage.getItem(imageSessionManagerKey);
if (manager == null) {
//这时候肯定没有离线缓存
return;
}
try {
manager = JSON.parse(manager);
} catch (e) {}
if (Array.isArray(manager)) {
for (var i = 0; i < manager.length; i++) {
removeImageSessionItem(manager[i]);
}
}
});
};
/**
* @description 设置图片加载工具的一些基本参数
* @param {JSON} options 参数
* @example 参数没传代表使用默认值,包括:
* imgStoragePath,string型,图片的默认路径
* defaultImgBase,string型,默认图片的基座路径
*/
ImgLoaderFactory.setOptions = function(options) {
if (!options) {
return;
}
//设置参数
for (var key in defaultSettingOptions) {
//如果设置的是有效的
if (options[key] != null) {
defaultSettingOptions[key] = options[key];
}
}
//默认的下载图片临时变量
defaultLoadingImg = defaultSettingOptions['defaultImgBase'] + defaultSettingOptions['loadingImgName'];
//默认的显示图片临时变量
defaultImg = defaultSettingOptions['defaultImgBase'] + defaultSettingOptions['errorImgName'];
};
/**
* @description 清除图片加载工厂的所有图片缓存
* @param {Function} successCallback 成功回调
* @param {Function} errorCallback 失败回调
*/
ImgLoaderFactory.clearLoaderImgsCache = function(successCallback, errorCallback) {
MobileFrame.plusReady(function() {
//遍历目录文件夹下的所有文件,然后删除
var tmpUrl = plus.io.convertLocalFileSystemURL(defaultSettingOptions['imgStoragePath']);
//需要手动加上 file://
tmpUrl = 'file://' + tmpUrl;
//同时清除所有的缓存键值
clearAllImageSessionKey();
plus.io.resolveLocalFileSystemURL(tmpUrl, function(entry) {
entry.removeRecursively(function() {
if (successCallback && typeof(successCallback) == 'function') {
successCallback('清除图片缓存成功!');
}
}, function() {
if (errorCallback && typeof(errorCallback) == 'function') {
errorCallback('清除图片缓存失败!');
}
});
}, function(e) {
if (errorCallback && typeof(errorCallback) == 'function') {
errorCallback('打开图片缓存目录失败!');
}
});
});
};
/**
* @description 从一个网络URL中,获取本地图片缓存相对路径
* @param {String} loadUrl 图片的网络路径,如果为null,则返回一个null
* @return {String} 返回路径或者是 ***outOfTime***表示缓存时间戳过去
* @example 获取相对路径可以有很多种方法
* 比如可以用md5将url加密,或者其它字符串操作等等
* 我这里也是根据项目而进行自适应的
*/
ImgLoaderFactory.getRelativePathFromLoadUrl = function(loadUrl) {
if (loadUrl == null) return null;
//如果loadUrl以../开头,代表是相对路径,采用本地相对路径,这里就直接返回本地路径,这样就是直接赋值了
if (loadUrl.substring(0, 5).indexOf('../') != -1) {
return loadUrl;
}
//图片缓存如果存在,判断是否过期,默认为''
var isOutOfTimeHeader = '';
//如果存在本地缓存,并且没有过期,采用本地缓存中的图片
var loacalImgSessionItem = getImageSessionItem(loadUrl);
if (loacalImgSessionItem != null) {
//判断是否过期 time localPath
if (loacalImgSessionItem.time) {
loacalImgSessionItem.time = parseInt(loacalImgSessionItem.time, 10);
if ((new Date()).valueOf() - loacalImgSessionItem.time > defaultSettingOptions['imgsTimeStamp']) {
//console.log('当前缓存已经过期')
//返回一个特殊字符,代表过期
isOutOfTimeHeader = '***outOfTime***';
} else {
//console.log('缓存未过期');
}
}
if (loacalImgSessionItem.localPath) {
return loacalImgSessionItem.localPath;
}
}
//获取图片后缀,如果没有获取到后缀,默认是jpg
var imgSuffix = loadUrl.substring(loadUrl.lastIndexOf(".") + 1, loadUrl.length);
if (
imgSuffix.toLocaleLowerCase() != ("jpg") &&
imgSuffix.toLocaleLowerCase() != ("jpeg") &&
imgSuffix.toLocaleLowerCase() != ("png") &&
imgSuffix.toLocaleLowerCase() != ("bmp") &&
imgSuffix.toLocaleLowerCase() != ("svg") &&
imgSuffix.toLocaleLowerCase() != ("gif")
) {
//如果后缀没有包含以上图片,将后缀改为jpg
imgSuffix = 'jpg';
}
//更换存储方式,变为将整个路径存储下来,然后去除非法字符
var regIllegal = /[&\|\\\*^%$#@\-:.?\/=!]/g;
//获取图片名字
var imgName = loadUrl.replace(regIllegal, '');
//最终的名字
var filename = imgName + '.' + imgSuffix;
//console.log('loadurl:'+loadUrl+',fileName:'+filename);
var relativePath = defaultSettingOptions['imgStoragePath'] + filename;
setImageSessionItem(loadUrl, {
'localPath': relativePath,
'time': (new Date()).valueOf()
});
//将是否过期标识传出
return isOutOfTimeHeader + relativePath;
};
/**
* @description 删除某一张网络图片的本地缓存,同时也会删除缓存键值
*/
ImgLoaderFactory.clearNetUrlImgCache = function(netImgUrl, successCallback, errorCallback) {
MobileFrame.plusReady(function() {
//删除该键值对应的缓存
removeImageSessionItem(netImgUrl);
MobileFrame.delFile(ImgLoaderFactory.getRelativePathFromLoadUrl(netImgUrl), successCallback, errorCallback);
});
};
/**
* @description 给指定的图片dom 设置本地图片属性
* @param {HTMLElement} $img 目标图片dom,这里为原生的dom对象
* @param {String} relativePath 本地图片路径
*/
function setImgFromLocalCache($img, relativePath) {
/*例如:
* 本地相对路径("downloads/imgs/logo.jpg")转成SD卡绝对路径
* 例如相对路径:downloads/imgs/logo.jpg
* ("/storage/emulated/0/Android/data/io.dcloud.HBuilder/.HBuilder/downloads/imgs/logo.jpg");
* */
if (!relativePath) {
return;
}
MobileFrame.plusReady(function() {
var sd_path = plus.io.convertLocalFileSystemURL(relativePath);
setImgSrc($img, sd_path, relativePath);
});
};
/**
* @description 通过本地缓存的方法显示网络图片
* @param {HTMLElement} $img 原生dom对象
* @param {String} loadUrl loadurl
*/
ImgLoaderFactory.setImgWidthLocalCache = function($img, loadUrl) {
if ($img == null || loadUrl == null) return;
MobileFrame.plusReady(function() {
var relativePath = ImgLoaderFactory.getRelativePathFromLoadUrl(loadUrl);
//判断需不需要将路径进行编码,如果是中文路径,需要编码后才能下载
var regChinese = /[\u4E00-\u9FA5]/g;
var tmpLoadUrl = loadUrl.replace(regChinese, 'chineseRemoveAfter');
if (tmpLoadUrl.indexOf('chineseRemoveAfter') != -1) {
loadUrl = encodeURI(loadUrl);
}
//判断是否已经缓存过期
var isCacheOutOfTime = false;
if (relativePath.indexOf('***outOfTime***') != -1) {
relativePath.replace('***outOfTime***', '');
isCacheOutOfTime = true;
}
if (relativePath == 'default.jpg') {
//设置默认图片
setImgSrc($img, defaultImg);
} else {
if (isCacheOutOfTime == false) {
//检查图片是否已存在
plus.io.resolveLocalFileSystemURL(relativePath, function(entry) {
//如果文件存在,则直接设置本地图片
setImgFromLocalCache($img, relativePath);
}, function(e) {
readyToGetNetImg($img, loadUrl, relativePath);
});
} else {
//否则,本地缓存已经过期,直接网络获取
readyToGetNetImg($img, loadUrl, relativePath);
}
}
});
};
/**
* @description 准备通过网络获取图片
* @param {HTMLElement} $img 原生dom对象
* @param {String} loadUrl loadurl
* @param {String} relativePath 本地相对路径
*/
function readyToGetNetImg($img, loadUrl, relativePath) {
//如果文件不存在,上网下载
if (MobileFrame.IsNetWorkCanUse() == true) {
//添加进入图片缓存池中
var relativePathKey = MobileFrame.getRelativePathKey(relativePath);
if (requestImgsPool && requestImgsPool[relativePathKey] && Array.isArray(requestImgsPool[relativePathKey])) {
//如果已经存在该条图片缓存池,代表这条资源已经进行请求了,只需要填进响应池子即可
//console.log('已经存在缓存池:'+loadUrl);
requestImgsPool[relativePathKey].push($img);
//设置一张加载中图片,这只是假象,其实并没有下载
setImgSrc($img, defaultLoadingImg);
return;
} else {
//新建缓存池
//console.log('新建缓存池:'+loadUrl);
requestImgsPool[relativePathKey] = [];
requestImgsPool[relativePathKey].push($img);
}
//如果网络状态能用,联网下载图片
setImgFromNet($img, loadUrl, relativePath);
} else {
//采用本地默认图片
setImgSrc($img, defaultImg);
}
};
/**
* @description 从网络下载图片,并给指定的img dom赋值
* @param {HTMLElement} $img 目标img 原生dom
* @param {String} loadUrl 网络图片路径
* @param {String} relativePath 图片下载后的本地路径,如果不指定,采用默认值 在_downloads/imgs/下
*/
function setImgFromNet($img, loadUrl, relativePath) {
relativePath = (typeof(relativePath) == 'string' && relativePath != '') ? relativePath : ImgLoaderFactory.getRelativePathFromLoadUrl(loadUrl);
//下载参数
var options = {
filename: relativePath,
timeout: 3,
retryInterval: 3
};
//解决ios的网络缓存问题
loadUrl = MobileFrame.changImgUrlTypeNoCache(loadUrl);
//1.将图片 设为默认的下载图片
setImgSrc($img, defaultLoadingImg);
//2.创建下载任务
var dtask = plus.downloader.createDownload(loadUrl,
options,
function(d, status) {
if (status == 200) {
//下载成功
//console.log('绝对路径:'+d.filename);
//这里传入的是相对路径,方便缓存显示
setImgFromLocalCache($img, relativePath);
} else {
//下载失败,需删除本地临时文件,否则下次进来时会检查到图片已存在
//console.log("下载失败=" + status + "==" + relativePath);
//dtask.abort();//文档描述:取消下载,删除临时文件;(但经测试临时文件没有删除,故使用delFile()方法删除);
if (relativePath != null) {
MobileFrame.delFile(relativePath);
}
setImgSrc($img, defaultImg);
}
//下载完成,当前任务数-1,并重新检查下载队列
concurrentDownloadTask['CurrentTaskCount']--;
//下载完成,从当前下载队列中去除
currentDownloadTasks[dtask.url] = null;
executeDownloadTasks();
});
//3.启动下载任务,添加进入下载队列中
concurrentDownloadTask['Queue'].push(dtask);
//执行并发下载队列
executeDownloadTasks();
};
/**
* @description 执行下载任务,通过队列中一个一个的进行
*/
function executeDownloadTasks() {
//console.log('检查下载队列');
//先检查是否存在任务超时的
for (var taskItem in currentDownloadTasks) {
if (currentDownloadTasks[taskItem] &&
currentDownloadTasks[taskItem].timeBegin && (new Date()).valueOf() - currentDownloadTasks[taskItem].timeBegin > defaultSettingOptions['maxTimeSingleDownloadTaskSpend']) {
//如果当前下载人已经超时,并且没有自动触发回调
//终止任务下载
currentDownloadTasks[taskItem].taskObj && currentDownloadTasks[taskItem].taskObj.abort&¤tDownloadTasks[taskItem].taskObj.abort();
concurrentDownloadTask['CurrentTaskCount']--;
//从当前任务队列中去除
currentDownloadTasks[taskItem] = null;
//console.log('存在超时的任务,手动剔除');
}
}
//如果当前下载任务小于并发下载数
if (concurrentDownloadTask['CurrentTaskCount'] < defaultSettingOptions['concurrentDownloadCount']) {
if (concurrentDownloadTask['Queue'].length > 0) {
//开启一个下载任务
var nowTask = concurrentDownloadTask['Queue'].shift();
nowTask.start()
//当前任务数++
concurrentDownloadTask['CurrentTaskCount']++;
currentDownloadTasks[nowTask.url] = {
taskObj: nowTask,
timeBegin: (new Date()).valueOf()
}
//console.log('添加一个下载任务');
}else{
//console.log('已经没有了下载任务');
}
}else{
//console.log('已经达到最大下载数量,延迟下载');
}
};
/**
* @description 设置页面中的所有图片(本地缓存方式)
* 注意,只有存在data-img-localcache 标签的图片才会有效果
*/
ImgLoaderFactory.setAllNetImgsWithLocalCache = function() {
//获取页面中所有的图片
var imgs = document.querySelectorAll('img');
MobileFrame.each(imgs, function(key, value) {
var src = this.getAttribute('data-img-localcache');
//console.log('显示图片:' + src);
if (src != null && src != '') {
ImgLoaderFactory.setImgWidthLocalCache(this, src);
}
});
};
})(exports.ImageLoaderFactory = {});
return exports;
});
/**
* 生成模块
*/
{
MobileFrame.ImageUtil = require('scripts/Core/ImageUtil.js');
}
})(this);
注
为了方便,就直接将源码都并入了自己写的框架类中了,
另外感谢@wenju 提供的思路!
欢迎大家留言,讨论!
源码见附件
参考文章
http://ask.dcloud.net.cn/article/256
http://ask.dcloud.net.cn/article/397
说明:为了方便,里面使用的图片来源是从上面文章中的源码项目获取的.
说明:参考了上面文章中的思路,然后自己重新写了一个较为完整的图片本地缓存显示工具.
功能
1.第一次显示图片时下载到本地,然后之后如果本地存在缓存(根据url),则显示本地缓存的图片
- 基于plus的storage,对每一个图片的缓存进行控制管理,增加时间戳参数,来控制本地缓存的有效时间
- 将所有的下载任务放入队列中,进行统一下载管理,增加最大并发请求数,防止一次性请求过多损耗性能
- 加入了图片缓存池机制,对于同一个下载路径的图片不会多次下载,而是填入缓存池,下载后统一回调
- 修改了图片本地路径的获取方法,摆脱第三方依赖
- 重构了代码,并入开发框架中
使用方法
* 外部API:注意,需要显示的图片需要有data-img-localcache这个属性(用来放目标url),dom需要是原生对象
* 清除所有图片缓存:MobileFrame.ImageUtil.ImageLoaderFactory.clearLoaderImgsCache(successCb,errorCb)
* 采取本地缓存显示所有图片:MobileFrame.ImageUtil.ImageLoaderFactory.setAllNetImgsWithLocalCache();
* 清除某一张图片的本地缓存:MobileFrame.ImageUtil.ImageLoaderFactory.clearNetUrlImgCache(src);
* 显示某一张图片:MobileFrame.ImageUtil.ImageLoaderFactory.setImgWidthLocalCache(dom,src);
源码
/**
* @description 移动开发框架
* @author dailc dailc
* @version 1.0
* @time 2016-01-11 16:57:57
* 功能模块:只依赖于plus系统
* @see http://ask.dcloud.net.cn/people/%E6%92%92%E7%BD%91%E8%A6%81%E8%A7%81%E9%B1%BC
* 图片本地缓存模块********************************
* 1.本地缓存显示图片
* 2.增加storage,增加每一个本地缓存的有效时间戳
* 3.增加自定义设置方法,可以根据不同需求,对参数进行修改
* 4.采用下载队列进行下载管理,增加最大并发请求数,防止一次性请求过多损耗性能
* 5.修改了图片本地路径的获取方法,摆脱第三方依赖
* 6.重构代码
* 外部API:注意,需要显示的图片需要有data-img-localcache这个属性(用来放目标url)
* 清除所有图片缓存:MobileFrame.ImageUtil.ImageLoaderFactory.clearLoaderImgsCache(successCb,errorCb)
* 采取本地缓存显示所有图片:MobileFrame.ImageUtil.ImageLoaderFactory.setAllNetImgsWithLocalCache();
* 清除某一张图片的本地缓存:MobileFrame.ImageUtil.ImageLoaderFactory.clearNetUrlImgCache(src);
* 显示某一张图片:MobileFrame.ImageUtil.ImageLoaderFactory.setImgWidthLocalCache(dom,src);
* 图片本地缓存模块完毕********************************
*/
(function(global) {
/**
* 定义全局函数对象 define出来的
*/
var mapping = {};
/**
* 缓存,正在用到的对象,函数中return 出来的,这样就不需要重复执行函数
*/
var cache = {};
/**
* @description 模块定义
* @param {String} id id
* @param {Function} func 对应的函数对象
*/
global.define = function(id, func) {
mapping[id] = func;
};
/**
* @description 生成模块对象,并采用本地缓存
* @param {String} id
*/
global.require = function(id) {
if (!/\.js$/.test(id)) {
id += '.js';
}
if (cache[id]) {
return cache[id];
} else {
return cache[id] = mapping[id]({});
}
};
/**
* @description 配置全局工具类以及一些需要用到的全局函数
*/
(function() {
global.MobileFrame = {};
/**
* 空函数
*/
MobileFrame.noop = function() {};
/**
* @description each遍历操作
* @param {type} elements
* @param {type} callback
* @returns {global}
*/
MobileFrame.each = function(elements, callback, hasOwnProperty) {
if (!elements) {
return this;
}
if (typeof elements.length === 'number') {
[].every.call(elements, function(el, idx) {
return callback.call(el, idx, el) !== false;
});
} else {
for (var key in elements) {
if (hasOwnProperty) {
if (elements.hasOwnProperty(key)) {
if (callback.call(elements[key], key, elements[key]) === false) return elements;
}
} else {
if (callback.call(elements[key], key, elements[key]) === false) return elements;
}
}
}
return global;
};
/**
* @description plusReady
* @param {Function} callback
* @returns {global} 返回的是global
*/
MobileFrame.plusReady = function(callback) {
if (window.plus) {
setTimeout(function() { //解决callback与plusready事件的执行时机问题(典型案例:showWaiting,closeWaiting)
callback();
}, 0);
} else {
document.addEventListener("plusready", function() {
callback();
}, false);
}
return global;
};
/**
* @description 得到相对路径对应的key,这个key可以使缓存池的或者是本地缓存键值
* 主要作用是去除非法字符
* @param {String} relativePath
*/
MobileFrame.getRelativePathKey = function(relativePath) {
var finalKey =
// relativePath.replace('\/', '').replace('.', '').replace('\/', '')
// .replace('_download', '').replace('jpg', '');
relativePath.replace(/[&\|\\\*^%$#@\-]/g, "");
return finalKey;
};
/**
* @description 更改url类型,去除cache,因为cache会造成一些困扰
* @param {String} url 传入的url
*/
MobileFrame.changImgUrlTypeNoCache = function(url) {
url = url || '';
if (url.indexOf('?') != -1) {
url += '&timeRandKey=' + Math.random();
} else {
url += '?timeRandKey=' + Math.random();
}
return url;
};
/**
* @description 删除指定路径的文件
* @param {String} relativePath 绝对路径或相对路径例如: _downloads/imgs/test.jpg
* @param {Function} successCallback 删除成功回调
* @param {Function} errorCallback 失败回调
*/
MobileFrame.delFile = function(relativePath, successCallback, errorCallback) {
if (!relativePath) {
return;
}
MobileFrame.plusReady(function() {
plus.io.resolveLocalFileSystemURL(relativePath, function(entry) {
entry.remove(function(entry) {
if (successCallback && typeof(successCallback) == 'function') {
successCallback(true);
}
}, function(e) {
if (errorCallback && typeof(errorCallback) == 'function') {
errorCallback('删除文件失败!');
}
});
}, function() {
if (errorCallback && typeof(errorCallback) == 'function') {
errorCallback('打开文件路径失败!');
}
});
});
};
/**
* @description 判断网络状态
*/
function GetNetWorkState() {
var NetStateStr = '未知';
var types = {};
types[plus.networkinfo.CONNECTION_UNKNOW] = "未知";
types[plus.networkinfo.CONNECTION_NONE] = "未连接网络";
types[plus.networkinfo.CONNECTION_ETHERNET] = "有线网络";
types[plus.networkinfo.CONNECTION_WIFI] = "WiFi网络";
types[plus.networkinfo.CONNECTION_CELL2G] = "2G蜂窝网络";
types[plus.networkinfo.CONNECTION_CELL3G] = "3G蜂窝网络";
types[plus.networkinfo.CONNECTION_CELL4G] = "4G蜂窝网络";
NetStateStr = types[plus.networkinfo.getCurrentType()];
return NetStateStr;
};
/**
* @description 判断是否有网络
*/
MobileFrame.IsNetWorkCanUse = function() {
var IsCanUseNetWork = false;
if (GetNetWorkState() == '未知' || GetNetWorkState() == '未连接网络') {
IsCanUseNetWork = false;
} else {
IsCanUseNetWork = true;
}
return IsCanUseNetWork;
};
})();
/**
* @description 定义模块功能-图片工具类
*/
define('scripts/Core/ImageUtil.js', function(exports) {
/**
* @description 图片加载工厂,包含图片加载方法
* 例如:时间戳控制缓存,下载队列批次下载,默认图片,下载loading图片,下载失败图片
* 注意相对路径在Android:/sdcard/Android/data/io.dcloud.HBuilder/.HBuilder/...
* iOS:/Library/Pandora/...
*/
(function(ImgLoaderFactory) {
/**
* 默认的options
*/
var defaultSettingOptions = {
//默认的图片缓存目录-存到应用的downloads/imgs下
'imgStoragePath': "_downloads/imgs/",
//默认图片的基座路径
'defaultImgBase': '../../img/mobileFrame/',
//loading图片的名称
'loadingImgName': 'img_loading.jpg',
//error图片名称
'errorImgName': 'img_error.jpg',
//图片缓存的时间戳,毫秒单位,默认为1天
'imgsTimeStamp': 1000 * 60 * 60 * 24 * 1,
//同时最多的downloader 并发下载数目,默认为3个
'concurrentDownloadCount': 3,
//单个下载任务最大的请求时间,防止一些机型上无法触发错误回调,单位毫秒,默认10秒
'maxTimeSingleDownloadTaskSpend': 1000 * 10
};
//默认的下载图片临时变量
var defaultLoadingImg = defaultSettingOptions['defaultImgBase'] + defaultSettingOptions['loadingImgName'];
//默认的显示图片临时变量
var defaultImg = defaultSettingOptions['defaultImgBase'] + defaultSettingOptions['errorImgName'];
/**
* 图片缓存的session头部
*/
var imageSessionKey_header = 'imageSessionKey_util_imgs_';
/**
* 图片缓存的session的管理者
*/
var imageSessionManagerKey = 'imageSessionKey_util_Manager';
/**
* 图片缓存池,用来解决多张图片并发请求问题
* 默认是空的,当有多张图片是同一个请求时,缓存池子中会有数据
* 格式 {'url1':[dom1,dom2]}
*/
var requestImgsPool = {};
/**
* 并发下载任务,包括下载队列,处理最大并发数下载
*/
var concurrentDownloadTask = {
//任务池-还没有下载的任务
Queue: [],
//当前正在下载的任务数量
CurrentTaskCount: 0
};
/**
* 当前的任务队列,包含任务的名称,以及时间戳-用来控制最大的超时时间,防止不能正常触发回调
* 包含:
* taskObj,timeBegin
* 格式:{url1:{task1,time1}}
*/
var currentDownloadTasks = {};
/**
* @description 将对应的图片缓存键值添加进入图片缓存管理中
* @param {String} key 图片路径对应的key
*/
function addImageSessionKeyToManager(key) {
//获取管理者
var manager = plus.storage.getItem(imageSessionManagerKey);
if (manager == null) {
//如果以前的缓存为空,生成缓存
manager = [];
} else {
try {
manager = JSON.parse(manager);
} catch (e) {}
}
if (manager.indexOf(key) == -1) {
manager.push(key);
}
plus.storage.setItem(imageSessionManagerKey, JSON.stringify(manager));
};
/**
* @description 从缓存管理中移除相应的图片缓存
* @param {String} key 图片路径对应的key
*/
function removeImageSessionKeyFromManager(key) {
//获取管理者
var manager = plus.storage.getItem(imageSessionManagerKey);
if (manager == null) {
//这时候肯定没有离线缓存
return;
}
try {
manager = JSON.parse(manager);
} catch (e) {}
var index = -1;
for (var i = 0; i < manager.length || 0; i++) {
if (manager[i] == key) {
index = i;
}
}
if (index != -1) {
//删除对应的index位置
manager.splice(index, 1);
//重新存储
plus.storage.setItem(imageSessionManagerKey, JSON.stringify(manager));
}
};
/**
* 设置图片缓存key
* @param {String} url
* @param {JSON} value 存进去的是图片相关的所有属性,包括时间戳,本地路径等
*/
function setImageSessionItem(url, value) {
if (url == null) {
return;
}
//然后添加进入缓存管理者中
addImageSessionKeyToManager(url);
url = imageSessionKey_header + MobileFrame.getRelativePathKey(url);
value = (value != null) ? value : '';
value = (typeof(value) == 'string') ? value : JSON.stringify(value);
plus.storage.setItem(url, value);
};
/**
* 获取图片缓存key
* @param {String} url
* @return {JSON} item 返回的是一个json对象,包括图片相关的所有属性,包括时间戳,本地路径等
* @example 包含属性:time localPath
*/
function getImageSessionItem(url) {
if (url == null) {
return null;
}
//去除非法字符
url = imageSessionKey_header + MobileFrame.getRelativePathKey(url);
var item = plus.storage.getItem(url);
try {
if (item != null) {
item = JSON.parse(item);
}
} catch (e) {}
return item;
};
/**
* 移除图片缓存key
* @param {String} url
*/
function removeImageSessionItem(url) {
if (url == null) {
return null;
}
removeImageSessionKeyFromManager(url);
//去除非法字符
url = imageSessionKey_header + MobileFrame.getRelativePathKey(url);
var items = plus.storage.removeItem(url);
};
/**
* @description 设置图片的src地址,根据一个url,为所有需要的图片赋值
* @param {HTMLElement} $img 图片的dom,这里为原生dom对象
* @param {String} srcUrl 图片的路径
* @param {String} relativePath 相对路径,用来判断缓存池的
*/
function setImgSrc($img, srcUrl, relativePath) {
if (!srcUrl) {
return;
}
setImgSrcByDom($img, srcUrl);
//如果缓存池子中存在图片
if (!relativePath) {
return;
}
var relativePathKey = MobileFrame.getRelativePathKey(relativePath);
if (requestImgsPool && requestImgsPool[relativePathKey]) {
var imgsData = requestImgsPool[relativePathKey];
//如果是数组
if (Array.isArray(imgsData)) {
for (var i = 0; i < imgsData.length; i++) {
setImgSrcByDom(imgsData[i], srcUrl);
}
} else {
//单条数据--单个dom对象
setImgSrcByDom(imgsData, srcUrl);
}
if (srcUrl != defaultLoadingImg) {
//如果不是loading图片就清空
//清空图片池子中的该条键值
requestImgsPool[relativePathKey] = null;
}
}
};
/**
* @description 设置图片的src地址,一个dom和一个 src一一对应
* @param {HTMLElement} $img 图片的dom,原生dom对象
* @param {String} srcUrl 图片的路径
*/
function setImgSrcByDom($img, srcUrl) {
if (!$img || !($img instanceof HTMLElement)) {
//console.log('该dom不是原生对象,url:' + srcUrl);
return;
}
//暂时去除loading图片的独特样式
// if (srcUrl == defaultLoadingImg) {
// //默认的loading图片,修改css
// $img.style.maxWidth = '30px';
// $img.style.maxHeight = '30px';
// } else {
// //恢复普通高度
// $img.style.maxWidth = '100%';
// $img.style.maxHeight = '100%';
// }
srcUrl = MobileFrame.changImgUrlTypeNoCache(srcUrl);
$img.setAttribute('src', srcUrl);
};
/**
* @description 移除所有的图片缓存键
*/
function clearAllImageSessionKey() {
MobileFrame.plusReady(function() {
var manager = plus.storage.getItem(imageSessionManagerKey);
if (manager == null) {
//这时候肯定没有离线缓存
return;
}
try {
manager = JSON.parse(manager);
} catch (e) {}
if (Array.isArray(manager)) {
for (var i = 0; i < manager.length; i++) {
removeImageSessionItem(manager[i]);
}
}
});
};
/**
* @description 设置图片加载工具的一些基本参数
* @param {JSON} options 参数
* @example 参数没传代表使用默认值,包括:
* imgStoragePath,string型,图片的默认路径
* defaultImgBase,string型,默认图片的基座路径
*/
ImgLoaderFactory.setOptions = function(options) {
if (!options) {
return;
}
//设置参数
for (var key in defaultSettingOptions) {
//如果设置的是有效的
if (options[key] != null) {
defaultSettingOptions[key] = options[key];
}
}
//默认的下载图片临时变量
defaultLoadingImg = defaultSettingOptions['defaultImgBase'] + defaultSettingOptions['loadingImgName'];
//默认的显示图片临时变量
defaultImg = defaultSettingOptions['defaultImgBase'] + defaultSettingOptions['errorImgName'];
};
/**
* @description 清除图片加载工厂的所有图片缓存
* @param {Function} successCallback 成功回调
* @param {Function} errorCallback 失败回调
*/
ImgLoaderFactory.clearLoaderImgsCache = function(successCallback, errorCallback) {
MobileFrame.plusReady(function() {
//遍历目录文件夹下的所有文件,然后删除
var tmpUrl = plus.io.convertLocalFileSystemURL(defaultSettingOptions['imgStoragePath']);
//需要手动加上 file://
tmpUrl = 'file://' + tmpUrl;
//同时清除所有的缓存键值
clearAllImageSessionKey();
plus.io.resolveLocalFileSystemURL(tmpUrl, function(entry) {
entry.removeRecursively(function() {
if (successCallback && typeof(successCallback) == 'function') {
successCallback('清除图片缓存成功!');
}
}, function() {
if (errorCallback && typeof(errorCallback) == 'function') {
errorCallback('清除图片缓存失败!');
}
});
}, function(e) {
if (errorCallback && typeof(errorCallback) == 'function') {
errorCallback('打开图片缓存目录失败!');
}
});
});
};
/**
* @description 从一个网络URL中,获取本地图片缓存相对路径
* @param {String} loadUrl 图片的网络路径,如果为null,则返回一个null
* @return {String} 返回路径或者是 ***outOfTime***表示缓存时间戳过去
* @example 获取相对路径可以有很多种方法
* 比如可以用md5将url加密,或者其它字符串操作等等
* 我这里也是根据项目而进行自适应的
*/
ImgLoaderFactory.getRelativePathFromLoadUrl = function(loadUrl) {
if (loadUrl == null) return null;
//如果loadUrl以../开头,代表是相对路径,采用本地相对路径,这里就直接返回本地路径,这样就是直接赋值了
if (loadUrl.substring(0, 5).indexOf('../') != -1) {
return loadUrl;
}
//图片缓存如果存在,判断是否过期,默认为''
var isOutOfTimeHeader = '';
//如果存在本地缓存,并且没有过期,采用本地缓存中的图片
var loacalImgSessionItem = getImageSessionItem(loadUrl);
if (loacalImgSessionItem != null) {
//判断是否过期 time localPath
if (loacalImgSessionItem.time) {
loacalImgSessionItem.time = parseInt(loacalImgSessionItem.time, 10);
if ((new Date()).valueOf() - loacalImgSessionItem.time > defaultSettingOptions['imgsTimeStamp']) {
//console.log('当前缓存已经过期')
//返回一个特殊字符,代表过期
isOutOfTimeHeader = '***outOfTime***';
} else {
//console.log('缓存未过期');
}
}
if (loacalImgSessionItem.localPath) {
return loacalImgSessionItem.localPath;
}
}
//获取图片后缀,如果没有获取到后缀,默认是jpg
var imgSuffix = loadUrl.substring(loadUrl.lastIndexOf(".") + 1, loadUrl.length);
if (
imgSuffix.toLocaleLowerCase() != ("jpg") &&
imgSuffix.toLocaleLowerCase() != ("jpeg") &&
imgSuffix.toLocaleLowerCase() != ("png") &&
imgSuffix.toLocaleLowerCase() != ("bmp") &&
imgSuffix.toLocaleLowerCase() != ("svg") &&
imgSuffix.toLocaleLowerCase() != ("gif")
) {
//如果后缀没有包含以上图片,将后缀改为jpg
imgSuffix = 'jpg';
}
//更换存储方式,变为将整个路径存储下来,然后去除非法字符
var regIllegal = /[&\|\\\*^%$#@\-:.?\/=!]/g;
//获取图片名字
var imgName = loadUrl.replace(regIllegal, '');
//最终的名字
var filename = imgName + '.' + imgSuffix;
//console.log('loadurl:'+loadUrl+',fileName:'+filename);
var relativePath = defaultSettingOptions['imgStoragePath'] + filename;
setImageSessionItem(loadUrl, {
'localPath': relativePath,
'time': (new Date()).valueOf()
});
//将是否过期标识传出
return isOutOfTimeHeader + relativePath;
};
/**
* @description 删除某一张网络图片的本地缓存,同时也会删除缓存键值
*/
ImgLoaderFactory.clearNetUrlImgCache = function(netImgUrl, successCallback, errorCallback) {
MobileFrame.plusReady(function() {
//删除该键值对应的缓存
removeImageSessionItem(netImgUrl);
MobileFrame.delFile(ImgLoaderFactory.getRelativePathFromLoadUrl(netImgUrl), successCallback, errorCallback);
});
};
/**
* @description 给指定的图片dom 设置本地图片属性
* @param {HTMLElement} $img 目标图片dom,这里为原生的dom对象
* @param {String} relativePath 本地图片路径
*/
function setImgFromLocalCache($img, relativePath) {
/*例如:
* 本地相对路径("downloads/imgs/logo.jpg")转成SD卡绝对路径
* 例如相对路径:downloads/imgs/logo.jpg
* ("/storage/emulated/0/Android/data/io.dcloud.HBuilder/.HBuilder/downloads/imgs/logo.jpg");
* */
if (!relativePath) {
return;
}
MobileFrame.plusReady(function() {
var sd_path = plus.io.convertLocalFileSystemURL(relativePath);
setImgSrc($img, sd_path, relativePath);
});
};
/**
* @description 通过本地缓存的方法显示网络图片
* @param {HTMLElement} $img 原生dom对象
* @param {String} loadUrl loadurl
*/
ImgLoaderFactory.setImgWidthLocalCache = function($img, loadUrl) {
if ($img == null || loadUrl == null) return;
MobileFrame.plusReady(function() {
var relativePath = ImgLoaderFactory.getRelativePathFromLoadUrl(loadUrl);
//判断需不需要将路径进行编码,如果是中文路径,需要编码后才能下载
var regChinese = /[\u4E00-\u9FA5]/g;
var tmpLoadUrl = loadUrl.replace(regChinese, 'chineseRemoveAfter');
if (tmpLoadUrl.indexOf('chineseRemoveAfter') != -1) {
loadUrl = encodeURI(loadUrl);
}
//判断是否已经缓存过期
var isCacheOutOfTime = false;
if (relativePath.indexOf('***outOfTime***') != -1) {
relativePath.replace('***outOfTime***', '');
isCacheOutOfTime = true;
}
if (relativePath == 'default.jpg') {
//设置默认图片
setImgSrc($img, defaultImg);
} else {
if (isCacheOutOfTime == false) {
//检查图片是否已存在
plus.io.resolveLocalFileSystemURL(relativePath, function(entry) {
//如果文件存在,则直接设置本地图片
setImgFromLocalCache($img, relativePath);
}, function(e) {
readyToGetNetImg($img, loadUrl, relativePath);
});
} else {
//否则,本地缓存已经过期,直接网络获取
readyToGetNetImg($img, loadUrl, relativePath);
}
}
});
};
/**
* @description 准备通过网络获取图片
* @param {HTMLElement} $img 原生dom对象
* @param {String} loadUrl loadurl
* @param {String} relativePath 本地相对路径
*/
function readyToGetNetImg($img, loadUrl, relativePath) {
//如果文件不存在,上网下载
if (MobileFrame.IsNetWorkCanUse() == true) {
//添加进入图片缓存池中
var relativePathKey = MobileFrame.getRelativePathKey(relativePath);
if (requestImgsPool && requestImgsPool[relativePathKey] && Array.isArray(requestImgsPool[relativePathKey])) {
//如果已经存在该条图片缓存池,代表这条资源已经进行请求了,只需要填进响应池子即可
//console.log('已经存在缓存池:'+loadUrl);
requestImgsPool[relativePathKey].push($img);
//设置一张加载中图片,这只是假象,其实并没有下载
setImgSrc($img, defaultLoadingImg);
return;
} else {
//新建缓存池
//console.log('新建缓存池:'+loadUrl);
requestImgsPool[relativePathKey] = [];
requestImgsPool[relativePathKey].push($img);
}
//如果网络状态能用,联网下载图片
setImgFromNet($img, loadUrl, relativePath);
} else {
//采用本地默认图片
setImgSrc($img, defaultImg);
}
};
/**
* @description 从网络下载图片,并给指定的img dom赋值
* @param {HTMLElement} $img 目标img 原生dom
* @param {String} loadUrl 网络图片路径
* @param {String} relativePath 图片下载后的本地路径,如果不指定,采用默认值 在_downloads/imgs/下
*/
function setImgFromNet($img, loadUrl, relativePath) {
relativePath = (typeof(relativePath) == 'string' && relativePath != '') ? relativePath : ImgLoaderFactory.getRelativePathFromLoadUrl(loadUrl);
//下载参数
var options = {
filename: relativePath,
timeout: 3,
retryInterval: 3
};
//解决ios的网络缓存问题
loadUrl = MobileFrame.changImgUrlTypeNoCache(loadUrl);
//1.将图片 设为默认的下载图片
setImgSrc($img, defaultLoadingImg);
//2.创建下载任务
var dtask = plus.downloader.createDownload(loadUrl,
options,
function(d, status) {
if (status == 200) {
//下载成功
//console.log('绝对路径:'+d.filename);
//这里传入的是相对路径,方便缓存显示
setImgFromLocalCache($img, relativePath);
} else {
//下载失败,需删除本地临时文件,否则下次进来时会检查到图片已存在
//console.log("下载失败=" + status + "==" + relativePath);
//dtask.abort();//文档描述:取消下载,删除临时文件;(但经测试临时文件没有删除,故使用delFile()方法删除);
if (relativePath != null) {
MobileFrame.delFile(relativePath);
}
setImgSrc($img, defaultImg);
}
//下载完成,当前任务数-1,并重新检查下载队列
concurrentDownloadTask['CurrentTaskCount']--;
//下载完成,从当前下载队列中去除
currentDownloadTasks[dtask.url] = null;
executeDownloadTasks();
});
//3.启动下载任务,添加进入下载队列中
concurrentDownloadTask['Queue'].push(dtask);
//执行并发下载队列
executeDownloadTasks();
};
/**
* @description 执行下载任务,通过队列中一个一个的进行
*/
function executeDownloadTasks() {
//console.log('检查下载队列');
//先检查是否存在任务超时的
for (var taskItem in currentDownloadTasks) {
if (currentDownloadTasks[taskItem] &&
currentDownloadTasks[taskItem].timeBegin && (new Date()).valueOf() - currentDownloadTasks[taskItem].timeBegin > defaultSettingOptions['maxTimeSingleDownloadTaskSpend']) {
//如果当前下载人已经超时,并且没有自动触发回调
//终止任务下载
currentDownloadTasks[taskItem].taskObj && currentDownloadTasks[taskItem].taskObj.abort&¤tDownloadTasks[taskItem].taskObj.abort();
concurrentDownloadTask['CurrentTaskCount']--;
//从当前任务队列中去除
currentDownloadTasks[taskItem] = null;
//console.log('存在超时的任务,手动剔除');
}
}
//如果当前下载任务小于并发下载数
if (concurrentDownloadTask['CurrentTaskCount'] < defaultSettingOptions['concurrentDownloadCount']) {
if (concurrentDownloadTask['Queue'].length > 0) {
//开启一个下载任务
var nowTask = concurrentDownloadTask['Queue'].shift();
nowTask.start()
//当前任务数++
concurrentDownloadTask['CurrentTaskCount']++;
currentDownloadTasks[nowTask.url] = {
taskObj: nowTask,
timeBegin: (new Date()).valueOf()
}
//console.log('添加一个下载任务');
}else{
//console.log('已经没有了下载任务');
}
}else{
//console.log('已经达到最大下载数量,延迟下载');
}
};
/**
* @description 设置页面中的所有图片(本地缓存方式)
* 注意,只有存在data-img-localcache 标签的图片才会有效果
*/
ImgLoaderFactory.setAllNetImgsWithLocalCache = function() {
//获取页面中所有的图片
var imgs = document.querySelectorAll('img');
MobileFrame.each(imgs, function(key, value) {
var src = this.getAttribute('data-img-localcache');
//console.log('显示图片:' + src);
if (src != null && src != '') {
ImgLoaderFactory.setImgWidthLocalCache(this, src);
}
});
};
})(exports.ImageLoaderFactory = {});
return exports;
});
/**
* 生成模块
*/
{
MobileFrame.ImageUtil = require('scripts/Core/ImageUtil.js');
}
})(this);
注
为了方便,就直接将源码都并入了自己写的框架类中了,
另外感谢@wenju 提供的思路!
欢迎大家留言,讨论!
源码见附件
收起阅读 »地图定位问题终于搞清楚了。。
不得不吐槽下mui的社区,问问题基本没人答复。小白经常被一个问题卡死,严重拖累进度和心情。
自己在qq群里反复询问,在高德社区发帖,一步步搜索问题。终于搞懂了大部分。下面进入正题:
自己的APP需要定位模块,类似滴滴打车的需求。
所以找了高德的JS API,用浏览器定位(小白注意:我们用mui开发的Hybrid APP不能用高德的原生Android API,下面会讲到怎么使用系统的原生能力)
所谓的浏览器定位实际上都是使用了HTML5的Geolocation功能。关于定位,分为GPS定位和网络定位2种。GPS定位,精度较高,可达到10米,但室内不可用,且超级费电。网络定位,分为wifi定位和基站定位,都是通过获取wifi或者基站信息,然后查询对应的wifi或者基站位置数据库,得到的定位地点。定位数据库可以不断完善不断补充,所以,越定位越准确。我们可以先判断浏览器是否支持geolocation, 如果不支持,可以提示错误,或者进入其他逻辑处理流程,现在移动端的智能手机浏览器绝大部分都是支持的。代码如下:
if( navigator.geolocation ){
alert('恭喜,您的浏览器支持HTML5 Geolocation API');
navigator.geolocation.getCurrentPosition( successCallback, handleLocationError,{maximumAge:15000, timeout:10000, enableHighAccuracy:true});
}else{
alert( "对不起,您的浏览器不支持html5定位");
}
function successCallback (){
alert('成功获取到位置信息');
};
function handleLocationError(error) {
console.log(error);
switch(error.code){
case 0:
alert("获取位置信息出错!");
break;
case 1:
alert("您设置了阻止该页面获取位置信息!");
break;
case 2:
alert("浏览器无法确定您的位置!");
break;
case 3:
alert("获取位置信息超时!");
break;
default:
alert('不明原因');
break;
}
}
当然浏览器定位不是那么好用,由于各种其他原因和我们的天朝的社会主义特色政策,国行Android手机“阉割”了谷歌GMS服务包,导致HTML5的geolocation无法使用wifi和基站定位服务。经常会定位失败。但是提供地图和定位相关服务的不止是google一家,还有百度、高德、搜狗啊。
咋一看,百度js api也有自己的geolocation(http://developer.baidu.com/map/reference/index.php?title=Class:%E6%9C%8D%E5%8A%A1%E7%B1%BB/Geolocation) 这个取代html5自带的不就可以了么,经过实验,真是图样图森破了,原来所有的javascript API都还是调用的浏览器自身的geolocation进行封装实现的,也就是说如果原本不能wifi+基站定位,用百度、高德的javascript api的效果是一样的。
由于我人在北京,每次都把我定位到了一个叫“军诚水果超市”的地方。开始因为我听说高德的浏览器有很大偏差,以为是定位偏差的问题。但直到我注意到这里:(http://lbs.amap.com/api/javascript-api/reference/plugin/) 在AMap.Geolocation 插件中有这样一句话:当浏览器原生定位接口定位失败后,系统会调用IP定位,返回定位点所在城市中心点。此时,定位精度范围返回“null”。使用高德JS API浏览器定位,精度如果返回null就说明定位失败了,而不是定位偏差。(国内的文档太不规范,着实把我骗到了)
最后要说明的是,如果是开发Android原生软件的话,可以在APP里封装百度地图定位的SDK,这个是可以解决没有谷歌GMS服务包无法定位的问题的,因为百度地图定位SDK实际上是起到和谷歌服务包里面的定位模块一样的作用,由这里也可以看到HTML5 Webapp和原生APP的一个差别,不是一个层面的解决方案。
记得项目目录里的menifest.json文件里可以配置百度和高德的SDK么?这就是HTML5+API赋予你的强大能力!如果你配置正确的话,即可使用系统原生的定位,不需要坑爹的浏览器定位了。这就是Hybrid APP相对Web APP的一个优势。
最终解决方案是:配置好百度或高德的SDK,使用HTML5+API的plus.geolocation.getCurrentPosition()利用原生定位获取到坐标点后,再用百度或高德的JS API去实现其他的地图模块功能。
参考的文章:
http://www.html5col.com/getcurrentposition/
http://blog.csdn.net/albert528108/article/details/39213419
不得不吐槽下mui的社区,问问题基本没人答复。小白经常被一个问题卡死,严重拖累进度和心情。
自己在qq群里反复询问,在高德社区发帖,一步步搜索问题。终于搞懂了大部分。下面进入正题:
自己的APP需要定位模块,类似滴滴打车的需求。
所以找了高德的JS API,用浏览器定位(小白注意:我们用mui开发的Hybrid APP不能用高德的原生Android API,下面会讲到怎么使用系统的原生能力)
所谓的浏览器定位实际上都是使用了HTML5的Geolocation功能。关于定位,分为GPS定位和网络定位2种。GPS定位,精度较高,可达到10米,但室内不可用,且超级费电。网络定位,分为wifi定位和基站定位,都是通过获取wifi或者基站信息,然后查询对应的wifi或者基站位置数据库,得到的定位地点。定位数据库可以不断完善不断补充,所以,越定位越准确。我们可以先判断浏览器是否支持geolocation, 如果不支持,可以提示错误,或者进入其他逻辑处理流程,现在移动端的智能手机浏览器绝大部分都是支持的。代码如下:
if( navigator.geolocation ){
alert('恭喜,您的浏览器支持HTML5 Geolocation API');
navigator.geolocation.getCurrentPosition( successCallback, handleLocationError,{maximumAge:15000, timeout:10000, enableHighAccuracy:true});
}else{
alert( "对不起,您的浏览器不支持html5定位");
}
function successCallback (){
alert('成功获取到位置信息');
};
function handleLocationError(error) {
console.log(error);
switch(error.code){
case 0:
alert("获取位置信息出错!");
break;
case 1:
alert("您设置了阻止该页面获取位置信息!");
break;
case 2:
alert("浏览器无法确定您的位置!");
break;
case 3:
alert("获取位置信息超时!");
break;
default:
alert('不明原因');
break;
}
}
当然浏览器定位不是那么好用,由于各种其他原因和我们的天朝的社会主义特色政策,国行Android手机“阉割”了谷歌GMS服务包,导致HTML5的geolocation无法使用wifi和基站定位服务。经常会定位失败。但是提供地图和定位相关服务的不止是google一家,还有百度、高德、搜狗啊。
咋一看,百度js api也有自己的geolocation(http://developer.baidu.com/map/reference/index.php?title=Class:%E6%9C%8D%E5%8A%A1%E7%B1%BB/Geolocation) 这个取代html5自带的不就可以了么,经过实验,真是图样图森破了,原来所有的javascript API都还是调用的浏览器自身的geolocation进行封装实现的,也就是说如果原本不能wifi+基站定位,用百度、高德的javascript api的效果是一样的。
由于我人在北京,每次都把我定位到了一个叫“军诚水果超市”的地方。开始因为我听说高德的浏览器有很大偏差,以为是定位偏差的问题。但直到我注意到这里:(http://lbs.amap.com/api/javascript-api/reference/plugin/) 在AMap.Geolocation 插件中有这样一句话:当浏览器原生定位接口定位失败后,系统会调用IP定位,返回定位点所在城市中心点。此时,定位精度范围返回“null”。使用高德JS API浏览器定位,精度如果返回null就说明定位失败了,而不是定位偏差。(国内的文档太不规范,着实把我骗到了)
最后要说明的是,如果是开发Android原生软件的话,可以在APP里封装百度地图定位的SDK,这个是可以解决没有谷歌GMS服务包无法定位的问题的,因为百度地图定位SDK实际上是起到和谷歌服务包里面的定位模块一样的作用,由这里也可以看到HTML5 Webapp和原生APP的一个差别,不是一个层面的解决方案。
记得项目目录里的menifest.json文件里可以配置百度和高德的SDK么?这就是HTML5+API赋予你的强大能力!如果你配置正确的话,即可使用系统原生的定位,不需要坑爹的浏览器定位了。这就是Hybrid APP相对Web APP的一个优势。
最终解决方案是:配置好百度或高德的SDK,使用HTML5+API的plus.geolocation.getCurrentPosition()利用原生定位获取到坐标点后,再用百度或高德的JS API去实现其他的地图模块功能。
参考的文章:
http://www.html5col.com/getcurrentposition/
http://blog.csdn.net/albert528108/article/details/39213419
收起阅读 »解决使用Mui索引列表“indexed-list-select” 动态加载数据后搜索失效问题
记得前不久刚用上Mui,感觉很赞,后来在开发中遇到一个问题,是关于使用Mui的索引列表的一个问题,因为官方示例数据都是纯静态演示数据,而我实际项目中的数据是需要根据不同的条件来动态加载数据,开发过程中发现动态加载进来的数据在搜索中失效,后来也在官网上提问过,当时问题的链接mui.indexedlist搜索功能失效问题,也有很多热心的网友给了回复,但都没有解决这个问题,后来自己利用空闲的时间去捣腾了一下,然后达到了效果,现在把他分享出来,希望能给遇到同样问题的人一点帮助,下载附件打开即可
记得前不久刚用上Mui,感觉很赞,后来在开发中遇到一个问题,是关于使用Mui的索引列表的一个问题,因为官方示例数据都是纯静态演示数据,而我实际项目中的数据是需要根据不同的条件来动态加载数据,开发过程中发现动态加载进来的数据在搜索中失效,后来也在官网上提问过,当时问题的链接mui.indexedlist搜索功能失效问题,也有很多热心的网友给了回复,但都没有解决这个问题,后来自己利用空闲的时间去捣腾了一下,然后达到了效果,现在把他分享出来,希望能给遇到同样问题的人一点帮助,下载附件打开即可
收起阅读 »Android平台本地(离线)打包指南 - Android Studio
新版离线sdk文档已发布,离线打包请参考最新文档
<!--
预备环境
- AndroidStudio开发环境,要求安装Android4.0或以上(API 14)SDK。
- 下载HBuilder离线打包Android版SDK(5+ SDK下载)。
离线打包SDK目录说明
- HBuilder-Hello:离线打包演示应用;
- HBuilder-Integrate: 5+ SDK 集成和插件开发示例
- libs:SDK库文件目录;
- Feature列表.xls:Android平台各扩展Feature API对应的permission;
- Readme.txt:版本说明文件。
配置编译工程
-
导入演示应用HBuilder-Hello工程
启动AndroidStudio 点击导入工程“import project”
选中工程所在目录点击“OK”
选择生成的AndroidStudio工程的保存路径,然后点击“next”,
在演示工程中已经配置好HelloH5应用相关资源,完成导入后,可Shift F10直接编译运行:
从SDK目录中拷贝要使用的feature库到工程app/libs目录
参考“Feature列表.xls”文档确定应用中使用到的扩展API,拷贝libs目录中对应的“jar”文件拷贝到工程的app->libs目录下,如果有引用“so”文件需要拷贝到工程的app->src->main->jniLibs下对应目录中。如使用分享功能(新浪微博分享、腾讯微博分享,微信分享),则需要拷贝以下文件:
拷贝完成后在工程中刷新可在工程的libs目录下显示:
-
配置应用的权限
参考“Feature列表.xls”文档确定应用中使用到的扩展API,在AndroidManifest.xml文件中删除不用到API的权限。如不使用音频(plus.audio.*)相关5+ API,则可删除Audio相关的权限:
-
配置其它第三方库的数据
由于第三方库需要使用一些特定的数据,所以在使用时需要将相关参数配置到AndroidManifest.xml文件中。如使用分享功能,则需要配置各分享平台的申请应用的相关参数:
如果不使用此类API,则可将此数据删除。
插件的配置方法请参考以下文档
Android离线打包插件配置
推送插件配置
分享插件配置
登陆鉴权插件配置
地图插件配置
支付插件配置
定位插件配置 -
<a id="version">配置应用的包名及版本号</a>
打开AndroidManifest.xml文件,在代码视图中修改根节点的package属性值,如下:
其中package为应用的包名,采用反向域名格式,为应用的标识;versionCode为应用的版本号(整数值),用于各应用市场的升级判断,建议与manifest.json中version -> code值一致;versionName为应用的版本名称(字符串),在系统应用管理程序中显示的版本号,建议与manifest.json中version -> name值一致。 -
<a id="name">配置应用名称</a>
打开app->res -> values -> strings.xml文件,修改“app_name”字段值,该值为安装到手机上桌面显示的应用名称,建议与manifest.json中name(基础配置中的应用名称)对应:
-
<a id="channel">配置离线打包广告支持及渠道打包配置</a>
新版本SDK简化了离线打包支持DCloud广告联盟功能。
AndroidManifest.xml文件中添加如下节点到<application>节点中,并配置替换其中5+应用appid、广告联盟会员adid的值和渠道标识,因为涉及到开屏广告业务,项目工程必须以io.dcloud.PandoraEntry作为5+应用的入口Activity。该activity已包含在lib.5plus.base-release中,开发者无需实现。<meta-data android:name="DCLOUD_AD_ID" android:value="广告标识"/> <meta-data android:name="DCLOUD_AD_SPLASH" android:value="true"/><!--如果不开启开屏广告则不设置此字段或者值设置为false <meta-data android:name="DCLOUD_STREAMAPP_CHANNEL" android:value="包名|应用标识|广告标识|渠道,如io.dcloud.appid|appid|adid|google" /><!--为了保证广告统计的有效性,请正确设置此值
- 包名:对应Android项目中build.gradle中的applicationId,如io.dcloud.HBuilder
- 应用标识:对应对应5+或uni-app项目manifest.json中appid
- 广告标识:DCloud的广告标识,开通广告后可在dev.dcloud.net.cn获取,如果没有开通广告,设置值为空即可
- 渠道:渠道包制作指南
注意:提交谷歌应用市场(Google Play)时一定要将渠道标识设置为google!!!
注意:以上操作只是配置5+应用具备广告能力,实际开通需要在manifest.json配置开关,具体参考DCloud广告联盟。
如果不需要支持广告,仍然可以使用Widget和WebView方式集成。
-
配置应用图标和启动界面
将应用的图标(文件名为icon.png)启动图片按照对应的尺寸拷贝到工程的app->src->main->res -> drawable-XXX目录下:
-
更新应用资源
打开app->src->main->assets -> apps 目录,将下面“HelloH5”目录名称修改为应用manifest.json中的id名称(这步非常重要,否则会导致应用无法正常启动),并将所有应用资源拷贝到其下的www目录中:
-
配置应用信息
打开app->src->main->assets -> data下的control.xml文件,修改appid和appver的值:
其中appid值为HBuilder应用的appid,必须与应用manifest.json中的id值完全一致;appver为应用的版本号,用于应用资源的升级,必须保持与manifest.json中的version -> name值完全一致;version值为应用基座版本号(plus.runtime.innerVersion返回的值),不要随意修改。 -
配置完成编译运行
应用配置完毕,按Shift F10编译运行应用
在弹出的设备列表对话框中选择要运行的设备:
点击OK后启动应用:
常见问题
Q: 打包后页面中无法调用5+API
A: 修改app->build.gradle文件defaultConfig节点下miniSdkVersion和targetSdkVersion属性指定版本号为8
注意(新版已不需要添加):打包时如果修改APK的包名,则同时需要修改Rinfomation.java文件import的包名,
同时需要修改AndroidManifest.xml文件中引用包名的插件有
推送插件
微信(登陆,分享 ,支付),微信同时需要修改引入的WXEntryActivity.java和WXPayEntryActivity.java文件所在的包名
Q :使用Android Studio编译时如果提示如下错误
A:
下载附件并解压
点击下载
点击 Android Studio 菜单 File->Settings->Build,Execution,Development->Build Tools->Gradle选择Use local gradle distribution,选择附件解压后的目录点击OK即可
-->
新版离线sdk文档已发布,离线打包请参考最新文档
<!--
预备环境
- AndroidStudio开发环境,要求安装Android4.0或以上(API 14)SDK。
- 下载HBuilder离线打包Android版SDK(5+ SDK下载)。
离线打包SDK目录说明
- HBuilder-Hello:离线打包演示应用;
- HBuilder-Integrate: 5+ SDK 集成和插件开发示例
- libs:SDK库文件目录;
- Feature列表.xls:Android平台各扩展Feature API对应的permission;
- Readme.txt:版本说明文件。
配置编译工程
-
导入演示应用HBuilder-Hello工程
启动AndroidStudio 点击导入工程“import project”
选中工程所在目录点击“OK”
选择生成的AndroidStudio工程的保存路径,然后点击“next”,
在演示工程中已经配置好HelloH5应用相关资源,完成导入后,可Shift F10直接编译运行:
从SDK目录中拷贝要使用的feature库到工程app/libs目录
参考“Feature列表.xls”文档确定应用中使用到的扩展API,拷贝libs目录中对应的“jar”文件拷贝到工程的app->libs目录下,如果有引用“so”文件需要拷贝到工程的app->src->main->jniLibs下对应目录中。如使用分享功能(新浪微博分享、腾讯微博分享,微信分享),则需要拷贝以下文件:
拷贝完成后在工程中刷新可在工程的libs目录下显示:
-
配置应用的权限
参考“Feature列表.xls”文档确定应用中使用到的扩展API,在AndroidManifest.xml文件中删除不用到API的权限。如不使用音频(plus.audio.*)相关5+ API,则可删除Audio相关的权限:
-
配置其它第三方库的数据
由于第三方库需要使用一些特定的数据,所以在使用时需要将相关参数配置到AndroidManifest.xml文件中。如使用分享功能,则需要配置各分享平台的申请应用的相关参数:
如果不使用此类API,则可将此数据删除。
插件的配置方法请参考以下文档
Android离线打包插件配置
推送插件配置
分享插件配置
登陆鉴权插件配置
地图插件配置
支付插件配置
定位插件配置 -
<a id="version">配置应用的包名及版本号</a>
打开AndroidManifest.xml文件,在代码视图中修改根节点的package属性值,如下:
其中package为应用的包名,采用反向域名格式,为应用的标识;versionCode为应用的版本号(整数值),用于各应用市场的升级判断,建议与manifest.json中version -> code值一致;versionName为应用的版本名称(字符串),在系统应用管理程序中显示的版本号,建议与manifest.json中version -> name值一致。 -
<a id="name">配置应用名称</a>
打开app->res -> values -> strings.xml文件,修改“app_name”字段值,该值为安装到手机上桌面显示的应用名称,建议与manifest.json中name(基础配置中的应用名称)对应:
-
<a id="channel">配置离线打包广告支持及渠道打包配置</a>
新版本SDK简化了离线打包支持DCloud广告联盟功能。
AndroidManifest.xml文件中添加如下节点到<application>节点中,并配置替换其中5+应用appid、广告联盟会员adid的值和渠道标识,因为涉及到开屏广告业务,项目工程必须以io.dcloud.PandoraEntry作为5+应用的入口Activity。该activity已包含在lib.5plus.base-release中,开发者无需实现。<meta-data android:name="DCLOUD_AD_ID" android:value="广告标识"/> <meta-data android:name="DCLOUD_AD_SPLASH" android:value="true"/><!--如果不开启开屏广告则不设置此字段或者值设置为false <meta-data android:name="DCLOUD_STREAMAPP_CHANNEL" android:value="包名|应用标识|广告标识|渠道,如io.dcloud.appid|appid|adid|google" /><!--为了保证广告统计的有效性,请正确设置此值
- 包名:对应Android项目中build.gradle中的applicationId,如io.dcloud.HBuilder
- 应用标识:对应对应5+或uni-app项目manifest.json中appid
- 广告标识:DCloud的广告标识,开通广告后可在dev.dcloud.net.cn获取,如果没有开通广告,设置值为空即可
- 渠道:渠道包制作指南
注意:提交谷歌应用市场(Google Play)时一定要将渠道标识设置为google!!!
注意:以上操作只是配置5+应用具备广告能力,实际开通需要在manifest.json配置开关,具体参考DCloud广告联盟。
如果不需要支持广告,仍然可以使用Widget和WebView方式集成。
-
配置应用图标和启动界面
将应用的图标(文件名为icon.png)启动图片按照对应的尺寸拷贝到工程的app->src->main->res -> drawable-XXX目录下:
-
更新应用资源
打开app->src->main->assets -> apps 目录,将下面“HelloH5”目录名称修改为应用manifest.json中的id名称(这步非常重要,否则会导致应用无法正常启动),并将所有应用资源拷贝到其下的www目录中:
-
配置应用信息
打开app->src->main->assets -> data下的control.xml文件,修改appid和appver的值:
其中appid值为HBuilder应用的appid,必须与应用manifest.json中的id值完全一致;appver为应用的版本号,用于应用资源的升级,必须保持与manifest.json中的version -> name值完全一致;version值为应用基座版本号(plus.runtime.innerVersion返回的值),不要随意修改。 -
配置完成编译运行
应用配置完毕,按Shift F10编译运行应用
在弹出的设备列表对话框中选择要运行的设备:
点击OK后启动应用:
常见问题
Q: 打包后页面中无法调用5+API
A: 修改app->build.gradle文件defaultConfig节点下miniSdkVersion和targetSdkVersion属性指定版本号为8
注意(新版已不需要添加):打包时如果修改APK的包名,则同时需要修改Rinfomation.java文件import的包名,
同时需要修改AndroidManifest.xml文件中引用包名的插件有
推送插件
微信(登陆,分享 ,支付),微信同时需要修改引入的WXEntryActivity.java和WXPayEntryActivity.java文件所在的包名
Q :使用Android Studio编译时如果提示如下错误
A:
下载附件并解压
点击下载
点击 Android Studio 菜单 File->Settings->Build,Execution,Development->Build Tools->Gradle选择Use local gradle distribution,选择附件解压后的目录点击OK即可
-->
收起阅读 »首届流应用开发大赛名单揭晓,大众点评、京东、有道等获奖
由DCloud、36氪、CSDN联合举办的首届中国流应用开发大赛,经过一个月的选拨和评选,获奖名单正式揭晓。活动期间,数百人报名参赛,无论是一线互联网公司如大众点评、京东、网易,还是创业公司,如挑食火锅、艺人捧场,都积极开发流应用,流应用在营销、用户和订单获取、开发成本节约等方面的价值成功的吸引了App厂商。
本次大赛的中奖名单如下:
一、优秀应用奖
获奖名单(4名):
大众点评外卖、大众点评爱美丽、有道词典、京东秒杀
奖励:应用商店价值5万元的推广资源
二、优秀创业奖
获奖名单(4名):
挑食火锅、宝贝租车、艺人捧场,弈客围棋
奖励:知名创业媒体36氪提供的融资平台三天banner展示位广告资源,知名投资人约见机会。
提示:体验获奖流应用的方式:使用安装360手机助手的安卓手机,点击下面的图片链接,根据提示在外部浏览器中打开,即可体验。
1.大众点评外卖
流应用体验二维码
2.大众点评爱美丽
流应用体验二维码
3.有道词典
流应用体验二维码
4.京东秒杀
流应用体验二维码
5.挑食火锅
流应用体验二维码
6.宝贝租车
流应用体验二维码
7.艺人捧场
流应用体验二维码
8.弈客围棋
除了上述获奖的App,还有很多创业者开发了体验优质的流应用,如HiMall、枫桥居花卉等。
优秀创业者获得36Kr融资频道Banner宣传:
关于流应用:流应用是DCloud公司开发的一种可以让手机App安装包实现边用边下的技术。利用js的动态语言特点,把手机端App的安装包拆解,流式下载到手机端。类似流媒体边看边下一样,流应用也可以边用边下。再辅以特殊的压缩解码技术,使得流应用可以在5秒内完成App的下载-安装-启动全过程。流应用使用的js经过了强化,可以调用原生40多万API,使得流应用的功能和体验都达到了原生水准。
本次流应用大赛之所以受到众多开发者如此关注,主要得益于流应用具有的开发者优势和用户优势。
在开发者优势方面:
1.降低App的开发成本和人员数量,原来App厂商需要iOS、Android、前端等三个开发团队,流应用只需要1个前端开发即可完成,并且还能打包成原生App以及发布为wap网站和公众号。高效率本就是世界进步发展趋势,在资本寒冬的今天,尤其需要这种高效率开发方案。
2.降低推广成本提高转化率,推广成本降低50-80%。原生App由于安装包比较大,从下载、到安装、到启动,使得推广成本非常高,而流应用从下载到安装启动一步到位,5秒内即可完成,使得App的推广成本降低,转化率提升50%-80%。
3.多端发布,方便测试。流应用基于mui开发App,生成流应用的同时,iOS、Android原生版、浏览器版、微信App和百度直达号版也随之生成,开发一次,多端发布,效率大幅提升,成本大幅下降。
在用户优势方面,流应用具有如下五大特点:
1.秒装秒开:流应用具有秒装秒开的功能,使得用户在下载安装的时候,5秒内即可安装启动,速度非常快。
2.差量实时更新:流应用采用自动差量更新技术,每次更新只需要2-3K流量,使得用户永远使用的都是最新版本的流应用,和原生应用更新相比,每次更新都需要重新下载安装包,启动一次来说,用户体验非常好。
3.省流量:流应用具有很多缩小应用包的专利技术,能大幅减少应用包体积,但毫不影响应用功能体验。。
- 省空间:流应用的安装包只有几百K,和原生App几十M,几百M的App相比,非常小,装一个原生App的空间可以装几十个流应用,流应用更加节省空间。
5.省电更流畅:流应用采用了包体压缩技术、差量更新技术,使得流应用对手机的内存、空间占用率非常少,非常省电,同时也更加流畅。
虽然本次大赛结束了,但流应用发展还在继续。后续提交流应用的开发者还有机会获得由DCloud提供的应用商店推广资源和机械键盘等奖励礼品,欢迎大家继续提交流应用。流应用交流QQ群:471285299。
由DCloud、36氪、CSDN联合举办的首届中国流应用开发大赛,经过一个月的选拨和评选,获奖名单正式揭晓。活动期间,数百人报名参赛,无论是一线互联网公司如大众点评、京东、网易,还是创业公司,如挑食火锅、艺人捧场,都积极开发流应用,流应用在营销、用户和订单获取、开发成本节约等方面的价值成功的吸引了App厂商。
本次大赛的中奖名单如下:
一、优秀应用奖
获奖名单(4名):
大众点评外卖、大众点评爱美丽、有道词典、京东秒杀
奖励:应用商店价值5万元的推广资源
二、优秀创业奖
获奖名单(4名):
挑食火锅、宝贝租车、艺人捧场,弈客围棋
奖励:知名创业媒体36氪提供的融资平台三天banner展示位广告资源,知名投资人约见机会。
提示:体验获奖流应用的方式:使用安装360手机助手的安卓手机,点击下面的图片链接,根据提示在外部浏览器中打开,即可体验。
1.大众点评外卖
流应用体验二维码
2.大众点评爱美丽
流应用体验二维码
3.有道词典
流应用体验二维码
4.京东秒杀
流应用体验二维码
5.挑食火锅
流应用体验二维码
6.宝贝租车
流应用体验二维码
7.艺人捧场
流应用体验二维码
8.弈客围棋
除了上述获奖的App,还有很多创业者开发了体验优质的流应用,如HiMall、枫桥居花卉等。
优秀创业者获得36Kr融资频道Banner宣传:
关于流应用:流应用是DCloud公司开发的一种可以让手机App安装包实现边用边下的技术。利用js的动态语言特点,把手机端App的安装包拆解,流式下载到手机端。类似流媒体边看边下一样,流应用也可以边用边下。再辅以特殊的压缩解码技术,使得流应用可以在5秒内完成App的下载-安装-启动全过程。流应用使用的js经过了强化,可以调用原生40多万API,使得流应用的功能和体验都达到了原生水准。
本次流应用大赛之所以受到众多开发者如此关注,主要得益于流应用具有的开发者优势和用户优势。
在开发者优势方面:
1.降低App的开发成本和人员数量,原来App厂商需要iOS、Android、前端等三个开发团队,流应用只需要1个前端开发即可完成,并且还能打包成原生App以及发布为wap网站和公众号。高效率本就是世界进步发展趋势,在资本寒冬的今天,尤其需要这种高效率开发方案。
2.降低推广成本提高转化率,推广成本降低50-80%。原生App由于安装包比较大,从下载、到安装、到启动,使得推广成本非常高,而流应用从下载到安装启动一步到位,5秒内即可完成,使得App的推广成本降低,转化率提升50%-80%。
3.多端发布,方便测试。流应用基于mui开发App,生成流应用的同时,iOS、Android原生版、浏览器版、微信App和百度直达号版也随之生成,开发一次,多端发布,效率大幅提升,成本大幅下降。
在用户优势方面,流应用具有如下五大特点:
1.秒装秒开:流应用具有秒装秒开的功能,使得用户在下载安装的时候,5秒内即可安装启动,速度非常快。
2.差量实时更新:流应用采用自动差量更新技术,每次更新只需要2-3K流量,使得用户永远使用的都是最新版本的流应用,和原生应用更新相比,每次更新都需要重新下载安装包,启动一次来说,用户体验非常好。
3.省流量:流应用具有很多缩小应用包的专利技术,能大幅减少应用包体积,但毫不影响应用功能体验。。
- 省空间:流应用的安装包只有几百K,和原生App几十M,几百M的App相比,非常小,装一个原生App的空间可以装几十个流应用,流应用更加节省空间。
5.省电更流畅:流应用采用了包体压缩技术、差量更新技术,使得流应用对手机的内存、空间占用率非常少,非常省电,同时也更加流畅。
虽然本次大赛结束了,但流应用发展还在继续。后续提交流应用的开发者还有机会获得由DCloud提供的应用商店推广资源和机械键盘等奖励礼品,欢迎大家继续提交流应用。流应用交流QQ群:471285299。
收起阅读 »Android 平台5+SDK接口说明
初始化SDK
initSDK(ICore core)
说明:
初始化SDK, SDK所有接口必须在初始化之后才可以调用
参数:
- core(ICore ) 5+内核对象
示例
@Override
public void onCoreReady(ICore coreHandler) {
try {
SDK.initSDK(coreHandler);
SDK.requestAllFeature();
} catch (Exception e) {
e.printStackTrace();
}
}
SDK集成使用方法
IApp startWebApp(Activity activity,String appBasePath,String startArgs,IWebviewStateListener listener,IOnCreateSplashView ocs)
说明:
创建并启动5+WebApp对象
参数:
- activity(Activity) Activity对象
- appBasePath(String) 移动应用所在的目录
- startArgs(String) 应用启动时传递的参数
- listener(IWebviewStateListener) Webview事件监听对象,用来监听应用的首页面加载状态
- ocs(IOnCreateSplashView) Splash事件监听对象,用来监听splash页面的加载和关闭事件
示例
public void onCoreInitEnd(ICore coreHandler) {
//创建默认webapp,
String appBasePath = "/apps/HelloH5";//表示 file:///android_asset/apps/HBuilder
String args = "{url:'http://www.baidu.com'}";//设置启动参数
app = SDK.startWebApp(activity,appBasePath, args, new IWebviewStateListener() {
@Override
public Object onCallBack(int pType, Object pArgs) {
switch (pType) {
case IWebviewStateListener.ON_WEBVIEW_READY:
//准备完毕之后添加webview到显示父View中,设置排版不显示状态,避免显示webview时,html内容排版错乱问题
View view = ((IWebview)pArgs).obtainApp().obtainWebAppRootView().obtainMainView();
view.setVisibility(View.INVISIBLE);
rootView.addView(view, 0);
break;
case IWebviewStateListener.ON_PAGE_STARTED:
break;
case IWebviewStateListener.ON_PROGRESS_CHANGED:
break;
case IWebviewStateListener.ON_PAGE_FINISHED:
//页面加载完毕,设置显示webview
app.obtainWebAppRootView().obtainMainView().setVisibility(View.VISIBLE);
break;
}
return null;
}
},this);
}
IWebview createWebview(Activity activity,String pagePath,String basePath,String docPath,String appid, String appName,IWebviewStateListener listener)
说明:
创建一个支持5+API的Webview对象
参数:
- activity(Activity) Activity对象
- pagePath(String) 页面路径
- basePath(String) 应用的默认路径
- docPath(String) 应用的doc目录路径
- appid(String) 应用的appid
- appName(String) 应用名称
- listener(IWebviewStateListener) 加载事件监听器
示例:
public void onCoreInitEnd(ICore coreHandler) {
//设置单页面集成的appid
String appid = "test1";
// 单页面集成时要加载页面的路径,可以是本地文件路径也可以是网络路径
String url = "file:///android_asset/apps/H5Plugin/www/index.html";
webview = SDK.createWebview(activity, url, appid, new IWebviewStateListener() {
@Override
public Object onCallBack(int pType, Object pArgs) {
switch (pType) {
case IWebviewStateListener.ON_WEBVIEW_READY:
// 准备完毕之后添加webview到显示父View中,设置排版不显示状态,避免显示webview时,html内容排版错乱问题
((IWebview) pArgs).obtainFrameView().obtainMainView().setVisibility(View.INVISIBLE);
SDK.attach(mRootView, ((IWebview) pArgs));
break;
case IWebviewStateListener.ON_PAGE_STARTED:
break;
case IWebviewStateListener.ON_PROGRESS_CHANGED:
break;
case IWebviewStateListener.ON_PAGE_FINISHED:
// 页面加载完毕,设置显示webview
webview.obtainFrameView().obtainMainView().setVisibility(View.VISIBLE);
break;
}
return null;
}
});
}
IWebview createWebview(Activity activity,String pagePath,String appid,IWebviewStateListener listener)
说明:
创建一个支持5+API的Webview对象
参数:
- activity(Activity) Activity对象
- pagePath(String) HTML页面的路径
- appid(String) 默认应用的APPID
- listener(IWebviewStateListener) 页面加载事件监听器
WebApp相关方法
void stopWebApp(IApp app)
说明:
停止一个正在运行的5+WebApp
参数:
app(IApp) 正在运行的WebAPP对象
String obtainCurrentRunnbingAppId()
说明:
获取当前正在运行的5+WebApp的APPID
IApp obtainCurrentApp ()
说明:
获取到当前在前台运行的5+Webapp对象
页面相关方法
ArrayList<IWebview> obtainAllIWebview()
说明:
获取运行应用的Webview对象的集合
IWebview obtainWebview(String appid,String webviewId)
说明:
根据APPID和WebviewID获取到指定的Webview对象
参数:
- appid(String) 要获取应用的APPID
- webviewId(String) 获取应用内页面的ID
IWebview obatinFirstPage(IApp app,IWebviewStateListener listener)
说明:
获取指定应用app的首页面Iwebview对象
参数:
- app(IApp) IApp对象
- listener(IWebviewStateListener) 应用首页面的加载状态的监听
void closeWebView(IWebview webview)
说明:
关闭指定IWebview页面
参数:
- webview(IWebview)
设置可使用的5+API权限
void requestAllFeature()
说明:
向5+基座申请全部扩展API调用能力
示例
public void onCoreReady(ICore coreHandler) {
try {
SDK.initSDK(coreHandler);
SDK.requestAllFeature();
} catch (Exception e) {
e.printStackTrace();
}
}
void requestFeature(String mainFeatureName,String subFeatrureName,boolean autoBoot)
说明
单独设置可以使用的API特征名
参数
- mainFeatureName(String) JS 特征名称
- subFeatrureName(String) JS特征二级名称
- autoBoot(boolean) 是否基座自动启动
示例
requestFeature("Share","share-weixin",false);
注册新扩展插件
void registerJsApi(String featureName,String className,String jsContent)
说明:
向5+SDK注册一个新的扩展API
参数:
- featureName(String) 扩展插件的JS功能名称
- className(String) 扩展插件的原生类名
- jsContent(String) 扩展插件的JS代码
示例
// 注册新的5+扩展API
private void regNewJsAPI() {
// 扩展插件的JS特征名称
String featureName = "T";
// 为处理扩展Feature的接收类全名称
String className = "com.HBuilder.integrate.webview.WebViewMode_FeatureImpl";
// content 为扩展Feature而创建的js代码
String content = "(function(plus){function test(){return plus.bridge.execSync('T','test',[arguments]);}plus.T = {test:test};})(window.plus);";
// 注册新的扩展方法
SDK.registerJsApi(featureName, className, content);
}
初始化SDK
initSDK(ICore core)
说明:
初始化SDK, SDK所有接口必须在初始化之后才可以调用
参数:
- core(ICore ) 5+内核对象
示例
@Override
public void onCoreReady(ICore coreHandler) {
try {
SDK.initSDK(coreHandler);
SDK.requestAllFeature();
} catch (Exception e) {
e.printStackTrace();
}
}
SDK集成使用方法
IApp startWebApp(Activity activity,String appBasePath,String startArgs,IWebviewStateListener listener,IOnCreateSplashView ocs)
说明:
创建并启动5+WebApp对象
参数:
- activity(Activity) Activity对象
- appBasePath(String) 移动应用所在的目录
- startArgs(String) 应用启动时传递的参数
- listener(IWebviewStateListener) Webview事件监听对象,用来监听应用的首页面加载状态
- ocs(IOnCreateSplashView) Splash事件监听对象,用来监听splash页面的加载和关闭事件
示例
public void onCoreInitEnd(ICore coreHandler) {
//创建默认webapp,
String appBasePath = "/apps/HelloH5";//表示 file:///android_asset/apps/HBuilder
String args = "{url:'http://www.baidu.com'}";//设置启动参数
app = SDK.startWebApp(activity,appBasePath, args, new IWebviewStateListener() {
@Override
public Object onCallBack(int pType, Object pArgs) {
switch (pType) {
case IWebviewStateListener.ON_WEBVIEW_READY:
//准备完毕之后添加webview到显示父View中,设置排版不显示状态,避免显示webview时,html内容排版错乱问题
View view = ((IWebview)pArgs).obtainApp().obtainWebAppRootView().obtainMainView();
view.setVisibility(View.INVISIBLE);
rootView.addView(view, 0);
break;
case IWebviewStateListener.ON_PAGE_STARTED:
break;
case IWebviewStateListener.ON_PROGRESS_CHANGED:
break;
case IWebviewStateListener.ON_PAGE_FINISHED:
//页面加载完毕,设置显示webview
app.obtainWebAppRootView().obtainMainView().setVisibility(View.VISIBLE);
break;
}
return null;
}
},this);
}
IWebview createWebview(Activity activity,String pagePath,String basePath,String docPath,String appid, String appName,IWebviewStateListener listener)
说明:
创建一个支持5+API的Webview对象
参数:
- activity(Activity) Activity对象
- pagePath(String) 页面路径
- basePath(String) 应用的默认路径
- docPath(String) 应用的doc目录路径
- appid(String) 应用的appid
- appName(String) 应用名称
- listener(IWebviewStateListener) 加载事件监听器
示例:
public void onCoreInitEnd(ICore coreHandler) {
//设置单页面集成的appid
String appid = "test1";
// 单页面集成时要加载页面的路径,可以是本地文件路径也可以是网络路径
String url = "file:///android_asset/apps/H5Plugin/www/index.html";
webview = SDK.createWebview(activity, url, appid, new IWebviewStateListener() {
@Override
public Object onCallBack(int pType, Object pArgs) {
switch (pType) {
case IWebviewStateListener.ON_WEBVIEW_READY:
// 准备完毕之后添加webview到显示父View中,设置排版不显示状态,避免显示webview时,html内容排版错乱问题
((IWebview) pArgs).obtainFrameView().obtainMainView().setVisibility(View.INVISIBLE);
SDK.attach(mRootView, ((IWebview) pArgs));
break;
case IWebviewStateListener.ON_PAGE_STARTED:
break;
case IWebviewStateListener.ON_PROGRESS_CHANGED:
break;
case IWebviewStateListener.ON_PAGE_FINISHED:
// 页面加载完毕,设置显示webview
webview.obtainFrameView().obtainMainView().setVisibility(View.VISIBLE);
break;
}
return null;
}
});
}
IWebview createWebview(Activity activity,String pagePath,String appid,IWebviewStateListener listener)
说明:
创建一个支持5+API的Webview对象
参数:
- activity(Activity) Activity对象
- pagePath(String) HTML页面的路径
- appid(String) 默认应用的APPID
- listener(IWebviewStateListener) 页面加载事件监听器
WebApp相关方法
void stopWebApp(IApp app)
说明:
停止一个正在运行的5+WebApp
参数:
app(IApp) 正在运行的WebAPP对象
String obtainCurrentRunnbingAppId()
说明:
获取当前正在运行的5+WebApp的APPID
IApp obtainCurrentApp ()
说明:
获取到当前在前台运行的5+Webapp对象
页面相关方法
ArrayList<IWebview> obtainAllIWebview()
说明:
获取运行应用的Webview对象的集合
IWebview obtainWebview(String appid,String webviewId)
说明:
根据APPID和WebviewID获取到指定的Webview对象
参数:
- appid(String) 要获取应用的APPID
- webviewId(String) 获取应用内页面的ID
IWebview obatinFirstPage(IApp app,IWebviewStateListener listener)
说明:
获取指定应用app的首页面Iwebview对象
参数:
- app(IApp) IApp对象
- listener(IWebviewStateListener) 应用首页面的加载状态的监听
void closeWebView(IWebview webview)
说明:
关闭指定IWebview页面
参数:
- webview(IWebview)
设置可使用的5+API权限
void requestAllFeature()
说明:
向5+基座申请全部扩展API调用能力
示例
public void onCoreReady(ICore coreHandler) {
try {
SDK.initSDK(coreHandler);
SDK.requestAllFeature();
} catch (Exception e) {
e.printStackTrace();
}
}
void requestFeature(String mainFeatureName,String subFeatrureName,boolean autoBoot)
说明
单独设置可以使用的API特征名
参数
- mainFeatureName(String) JS 特征名称
- subFeatrureName(String) JS特征二级名称
- autoBoot(boolean) 是否基座自动启动
示例
requestFeature("Share","share-weixin",false);
注册新扩展插件
void registerJsApi(String featureName,String className,String jsContent)
说明:
向5+SDK注册一个新的扩展API
参数:
- featureName(String) 扩展插件的JS功能名称
- className(String) 扩展插件的原生类名
- jsContent(String) 扩展插件的JS代码
示例
// 注册新的5+扩展API
private void regNewJsAPI() {
// 扩展插件的JS特征名称
String featureName = "T";
// 为处理扩展Feature的接收类全名称
String className = "com.HBuilder.integrate.webview.WebViewMode_FeatureImpl";
// content 为扩展Feature而创建的js代码
String content = "(function(plus){function test(){return plus.bridge.execSync('T','test',[arguments]);}plus.T = {test:test};})(window.plus);";
// 注册新的扩展方法
SDK.registerJsApi(featureName, className, content);
}
收起阅读 »
chrome://inspect调试html页面空白,DOM无法加载的解决方案
chrome://inspect调试html页面空白,DOM无法加载的解决方案
先描述一下问题
有一段时间没碰huilder hybird app 开发了,今天调试的时候
chrome://inspect/#devices
可以发现页面,但是打开后空白
解决方案
翻墙后就可以了,下次就不需要翻墙了
估计原因
1.chrome升级后原有配置失效
2.浏览器缓存被软件管家清掉了
chrome://inspect调试html页面空白,DOM无法加载的解决方案
先描述一下问题
有一段时间没碰huilder hybird app 开发了,今天调试的时候
chrome://inspect/#devices
可以发现页面,但是打开后空白
解决方案
翻墙后就可以了,下次就不需要翻墙了
估计原因
1.chrome升级后原有配置失效
2.浏览器缓存被软件管家清掉了
收起阅读 »