
将tap模拟成原生click体验
将tap模拟成原生click体验
mui提供了tap事件替换了html5的click事件,解决了300ms延时的问题。不过相比原生app的click体验还是有些许差距的。
仅用微信为例,只有当手指离开屏幕时才触发click事件,如果对象绑定了长按事件,则触发长按操作,离开时不再触发单击事件。
这些逻辑无论是android, ios或者仅有1%的windows mobile都已经封装好了,根本不用关心。
那么,我们应该怎么来实现呢?
下面是详细的填坑历程。。。。。。
坑1.通过原生的touch来实现
//直接对dom添加touchend,这种方法只能针对位置不变且并没有添加longtap事件的DOM有效
//如果在listview中,你上下滑动,那就歇菜了。
//那么自然而然就想到了touch.target的位移,并做出判断是下滑还是单击。
//自己去写复杂度、代码量估计会很可观。
//因此就想到了了在原有的框架代码上去实现。
//下面就到了坑2
document.getElementById("").('touchend', function() {
//
});
坑2.更改mui.gestures.tap.js
坑2.1 自定义事件侦听机制
mui没有提供类似于jq.data('events')获取事件列表的机制,另外官方也推荐使用addEventListener
去绑定事件。
我要去获取当前DOM的事件列表应该怎么做呢?
你问我问毛要去获取DOM的事件列表,,,
呵呵,我总要知道DOM有木有绑定longtap事件好做规避吧
csdn的这个帖子看似有用
http://bbs.csdn.net/topics/390250552
function addEvent(dom,type,fn) {
if(document.addEventListener) {
dom.addEventListener(type, fn, false);
} else if(document.attachEvent) {
dom.attachEvent('on' + type, fn);
} else {
dom['on' + type] = fn;
};
dom[""+type]=!0;
}
实际上并没有什么卵用
思想是好的.....
我总不能每次addEventListener都去调一下这个方法吧!
坑2.2 使用getEventListeners
找啊找,终于找到了getEventListeners()
这个全局方法,在chrome和safari控制台中测试都木有问题。
喜出望外......
这下终于能解决问题了
于是有了以下的方法
var getEvents = function(obj) {
console.log(getEventListeners(obj));
return typeof(getEventListeners) == "function" && getEventListeners(obj);
}
var hasEventype = function(obj, e) {
var es = getEvents(obj);
console.log(es[e]);
return es && !!es[e];
}
调用下试试
if (!hasEventype(target, 'longtap')) {}
报错
getEventListeners
is undefined
R U kidding?!!!!
你丫在逗我.............
我瞬间感受到了深深地恶意
原来这个方法只能在控制台中用,
呵呵,人艰不拆......
坑2.3 使用全局变量规避
给mui添加一个全局变量isLongTapAtived
,看变量名就知道什么意思吧
在mui.gestures.longtap.js
中初始化,在handle
中激活
(function($, name) {
$.isLongTapAtived = false;//初始化
var timer;
var handle = function(event, touch) {
switch (event.type) {
case $.EVENT_START:
clearTimeout(timer);
timer = setTimeout(function() {
$.trigger(session.target, name, touch);
//激活了
$.isLongTapAtived = true;
}, options.holdTimeout);
break;
}
};
});
})(mui, 'longtap');
在mui.gestures.tap.js
中判断有无激活
var handle = function(event, touch) {
var session = $.gestures.session;
var options = this.options;
switch (event.type) {
case $.EVENT_END:
//......
if (touch.distance < options.tapMaxDistance) {
if (touch.deltaTime < options.tapMaxTime) {
//.....
} else {
//如果当前对象添加了长按侦听,略过,否则仍然视为tap事件
//if (!hasEventype(target, 'longtap')) {
if (!$.isLongTapAtived) {
//如果没有longtap事件,离开屏幕是触发tap事件
$.trigger(target, name, touch);
}
//重置
$.isLongTapAtived = false;
}
}
break;
}
};
想法是美好的,现实是他么残酷的。无论有无longtap
事件,都要走一遍longtap
的handle
代码
于是 $.isLongTapAtived === true;
于是 永远trigger
tap
事件
呵呵,想死的心都有了
路子看来走对了,但是应该怎么做???
终极解决方案
mui.isLongTapAtived
依然添加,只是在每一次DOM添加的longtap事件内激活
document.querySelector("#").addEventListener('longtap',function(){
mui.isLongTapAtived=true;
console.log('你触发了longtap事件');
});
这样对开发者是不友好的,不过暂时没办法,只能如此取舍了
代码已提交至https://github.com/phillyx/mui/
并推送给官方
博客已同步至http://www.cnblogs.com/phillyx/p/5157850.html

觉得不错就打赏一下吧
将tap模拟成原生click体验
mui提供了tap事件替换了html5的click事件,解决了300ms延时的问题。不过相比原生app的click体验还是有些许差距的。
仅用微信为例,只有当手指离开屏幕时才触发click事件,如果对象绑定了长按事件,则触发长按操作,离开时不再触发单击事件。
这些逻辑无论是android, ios或者仅有1%的windows mobile都已经封装好了,根本不用关心。
那么,我们应该怎么来实现呢?
下面是详细的填坑历程。。。。。。
坑1.通过原生的touch来实现
//直接对dom添加touchend,这种方法只能针对位置不变且并没有添加longtap事件的DOM有效
//如果在listview中,你上下滑动,那就歇菜了。
//那么自然而然就想到了touch.target的位移,并做出判断是下滑还是单击。
//自己去写复杂度、代码量估计会很可观。
//因此就想到了了在原有的框架代码上去实现。
//下面就到了坑2
document.getElementById("").('touchend', function() {
//
});
坑2.更改mui.gestures.tap.js
坑2.1 自定义事件侦听机制
mui没有提供类似于jq.data('events')获取事件列表的机制,另外官方也推荐使用addEventListener
去绑定事件。
我要去获取当前DOM的事件列表应该怎么做呢?
你问我问毛要去获取DOM的事件列表,,,
呵呵,我总要知道DOM有木有绑定longtap事件好做规避吧
csdn的这个帖子看似有用
http://bbs.csdn.net/topics/390250552
function addEvent(dom,type,fn) {
if(document.addEventListener) {
dom.addEventListener(type, fn, false);
} else if(document.attachEvent) {
dom.attachEvent('on' + type, fn);
} else {
dom['on' + type] = fn;
};
dom[""+type]=!0;
}
实际上并没有什么卵用
思想是好的.....
我总不能每次addEventListener都去调一下这个方法吧!
坑2.2 使用getEventListeners
找啊找,终于找到了getEventListeners()
这个全局方法,在chrome和safari控制台中测试都木有问题。
喜出望外......
这下终于能解决问题了
于是有了以下的方法
var getEvents = function(obj) {
console.log(getEventListeners(obj));
return typeof(getEventListeners) == "function" && getEventListeners(obj);
}
var hasEventype = function(obj, e) {
var es = getEvents(obj);
console.log(es[e]);
return es && !!es[e];
}
调用下试试
if (!hasEventype(target, 'longtap')) {}
报错
getEventListeners
is undefined
R U kidding?!!!!
你丫在逗我.............
我瞬间感受到了深深地恶意
原来这个方法只能在控制台中用,
呵呵,人艰不拆......
坑2.3 使用全局变量规避
给mui添加一个全局变量isLongTapAtived
,看变量名就知道什么意思吧
在mui.gestures.longtap.js
中初始化,在handle
中激活
(function($, name) {
$.isLongTapAtived = false;//初始化
var timer;
var handle = function(event, touch) {
switch (event.type) {
case $.EVENT_START:
clearTimeout(timer);
timer = setTimeout(function() {
$.trigger(session.target, name, touch);
//激活了
$.isLongTapAtived = true;
}, options.holdTimeout);
break;
}
};
});
})(mui, 'longtap');
在mui.gestures.tap.js
中判断有无激活
var handle = function(event, touch) {
var session = $.gestures.session;
var options = this.options;
switch (event.type) {
case $.EVENT_END:
//......
if (touch.distance < options.tapMaxDistance) {
if (touch.deltaTime < options.tapMaxTime) {
//.....
} else {
//如果当前对象添加了长按侦听,略过,否则仍然视为tap事件
//if (!hasEventype(target, 'longtap')) {
if (!$.isLongTapAtived) {
//如果没有longtap事件,离开屏幕是触发tap事件
$.trigger(target, name, touch);
}
//重置
$.isLongTapAtived = false;
}
}
break;
}
};
想法是美好的,现实是他么残酷的。无论有无longtap
事件,都要走一遍longtap
的handle
代码
于是 $.isLongTapAtived === true;
于是 永远trigger
tap
事件
呵呵,想死的心都有了
路子看来走对了,但是应该怎么做???
终极解决方案
mui.isLongTapAtived
依然添加,只是在每一次DOM添加的longtap事件内激活
document.querySelector("#").addEventListener('longtap',function(){
mui.isLongTapAtived=true;
console.log('你触发了longtap事件');
});
这样对开发者是不友好的,不过暂时没办法,只能如此取舍了
代码已提交至https://github.com/phillyx/mui/
并推送给官方
博客已同步至http://www.cnblogs.com/phillyx/p/5157850.html
觉得不错就打赏一下吧
收起阅读 »
【交流分享】解决IOS列表滑动不流畅的问题
很简单的列表:
<body>
<div class="list">
<h1>图片</h1>
<img sr c="xxx.jpg" />
...这里是20个<h1><img>...
<h1>图片</h1>
<img sr c="xxx.jpg" />
</div>
</body>
上述很简单的图文列表运行在IOS上列表滑动起来总是不流畅,但是在Android运行就很快;就算把img删掉,只剩h1文字列表,在IOS上滑起来都有点卡顿,手指只要离开屏幕,列表就停了,没有继续惯性滚动一段距离;
分享一下我的解决办法:
方法一: 使用原生div的滚动
如果像上面代码一样,不使用mui框架,则加入以下css:
body,
html {
height: 100%;
-webkit-overflow-scrolling: touch;//允许独立的滚动区域和触摸回弹
}
.list{
height: 100%;
overflow-y: scroll;//使用原生div滚动
}
如果使用mui框架,则
引入mui.css, 把上面<div class="list">变成 <div class="mui-content">; 加入以下css:
body,
html {
height: 100%;
}
.mui-content{
height: 100%;
overflow-y: scroll;
}
方法二: 使用mui的区域滚动
参考:http://dev.dcloud.net.cn/mui/ui/#scroll
但是这种方式,体验不如上面css的; 按照我们公司IOS同事的话来讲:你会感觉滑动飞得太假;当然也能解决问题;
最后附上源码案例, 很简单, 导入到HBuilder就可以在IOS上运行了
很简单的列表:
<body>
<div class="list">
<h1>图片</h1>
<img sr c="xxx.jpg" />
...这里是20个<h1><img>...
<h1>图片</h1>
<img sr c="xxx.jpg" />
</div>
</body>
上述很简单的图文列表运行在IOS上列表滑动起来总是不流畅,但是在Android运行就很快;就算把img删掉,只剩h1文字列表,在IOS上滑起来都有点卡顿,手指只要离开屏幕,列表就停了,没有继续惯性滚动一段距离;
分享一下我的解决办法:
方法一: 使用原生div的滚动
如果像上面代码一样,不使用mui框架,则加入以下css:
body,
html {
height: 100%;
-webkit-overflow-scrolling: touch;//允许独立的滚动区域和触摸回弹
}
.list{
height: 100%;
overflow-y: scroll;//使用原生div滚动
}
如果使用mui框架,则
引入mui.css, 把上面<div class="list">变成 <div class="mui-content">; 加入以下css:
body,
html {
height: 100%;
}
.mui-content{
height: 100%;
overflow-y: scroll;
}
方法二: 使用mui的区域滚动
参考:http://dev.dcloud.net.cn/mui/ui/#scroll
但是这种方式,体验不如上面css的; 按照我们公司IOS同事的话来讲:你会感觉滑动飞得太假;当然也能解决问题;
最后附上源码案例, 很简单, 导入到HBuilder就可以在IOS上运行了
收起阅读 »
DCloud学习资料【自用】
1、首先记住以下3个常用网站:
DCloud官网:http://www.dcloud.io/
MUI官网:http://dev.dcloud.net.cn/mui/
HTML5+联盟:http://www.html5plus.org/
2、学习顺序
(1)MUI、5+、Native.js等相关知识参照DCloud官方文档
(2)5+API,即HTML5+联盟中的HTML5+规范
(3)MUI
3、资源汇总
MUI-FAQ:http://ask.dcloud.net.cn/docs/#http://ask.dcloud.net.cn/article/122
APP优化技巧:http://ask.dcloud.net.cn/docs/#http://ask.dcloud.net.cn/article/25
视频教程:http://edu.yuantuan.com/classroom/5/courses
4、官方相关视频
如何让Android手机的HTML5 App性能体验接近原生 王安(DCloud CEO
http://v.youku.com/v_show/id_XNzYyNzI3NDQw.html
mui—让HTML5达到原生体验的高性能框架 崔红保(Dcloud 前端工程
http://v.youku.com/v_show/id_XNzYyOTEyMjcy.html?from=s1.8-1-1.2
Native.js,让js像原生一样强大+++江树源(数字天堂CTO)
http://v.youku.com/v_show/id_XNzYzNTcwNDI4.html
(最新)DCloud流应用大会:http://e.vhall.com/944470274
1、首先记住以下3个常用网站:
DCloud官网:http://www.dcloud.io/
MUI官网:http://dev.dcloud.net.cn/mui/
HTML5+联盟:http://www.html5plus.org/
2、学习顺序
(1)MUI、5+、Native.js等相关知识参照DCloud官方文档
(2)5+API,即HTML5+联盟中的HTML5+规范
(3)MUI
3、资源汇总
MUI-FAQ:http://ask.dcloud.net.cn/docs/#http://ask.dcloud.net.cn/article/122
APP优化技巧:http://ask.dcloud.net.cn/docs/#http://ask.dcloud.net.cn/article/25
视频教程:http://edu.yuantuan.com/classroom/5/courses
4、官方相关视频
如何让Android手机的HTML5 App性能体验接近原生 王安(DCloud CEO
http://v.youku.com/v_show/id_XNzYyNzI3NDQw.html
mui—让HTML5达到原生体验的高性能框架 崔红保(Dcloud 前端工程
http://v.youku.com/v_show/id_XNzYyOTEyMjcy.html?from=s1.8-1-1.2
Native.js,让js像原生一样强大+++江树源(数字天堂CTO)
http://v.youku.com/v_show/id_XNzYzNTcwNDI4.html
(最新)DCloud流应用大会:http://e.vhall.com/944470274
收起阅读 »
【交流分享】Android独立应用方式集成HTML5+SDK,Widget方式离线打包,空项目讲解
最近忙着微信和m站,还有流应用开发,好久没有写文章了.
公司打算把原生app全部改写成html5,方便升级版本,毕竟IOS更新审核太久,耽误运营推广;
我的项目百度地图,友盟推送,反馈,统计,千牛商家聊天都是得用原生的,所以以后用MUI写好html后,得集成到原生项目中去.
首先吐槽下官方的集成方式所提到的概念:
Widget方式: 按照字面的意思就是,html相当于小部件一样和原生代码放一块,在需要的时候调用;
独立应用方式: 这个就是Widget方式,概念真多
Webview方式: 只能打开一个界面,不能打开新界面,这个没搞懂使用场景~~(>_<)~~我暂且把这个概念忘了,别搞混
当你打开官方http://ask.dcloud.net.cn/docs/#http://ask.dcloud.net.cn/article/38 又多了个概念: 离线打包
当你运行官方集成案例的时候,又多了个Runtime集成方式; 小白迟早会疯掉的
总之,按照我的理解:
1.如果你的项目原生代码和html混合着用,那么选择Widget方式集成,也就是独立应用方式;
2.如果你的项目是HBuilder写的纯html,没有原生代码,那么用Runtime集成方式,比较简单;
离线打包的概念是相对于HBuilder在线云打包而言的; 我们选上面各种集成方式用eclipse,Android Studio 或者XCode把html编译成安装包就属于离线打包了;这样避免云打包排队或者服务器挂掉的时候,自己还能编译安装包,照常发布更新版本;
ok,理清了官方的概念,你也基本懂了我写的标题:
Android独立应用方式集成HTML5 SDK,Widget方式离线打包,空项目讲解
一.准备
1.打开HBuilder,新建-->移动APP-->勾选"Hello mui",项目名叫"HelloMui";(我以官方mui项目来举例了,到时自行改为自己的项目);
2.打开eclipse,打开你的Android项目;(我这里是新建了一个Android项目,名字叫Widget; 如果你还没配置Android开发环境,则点这里http://jingyan.baidu.com/article/bea41d437a41b6b4c51be6c1.html );
- 去官方下载最新的sdk: http://ask.dcloud.net.cn/article/103 解压,里面有个名字叫'SDK'的文件夹
二.拷贝
1.把下载的官方SDK文件夹里的res文件夹拷贝到Android项目的res,合并;注意不要替换你项目的资源,不然你配的图标和写的strings.xml就没了
说明: res里面有NativeUI用到的图片资源: 进度条,对话框,动画样式,照片选择,底部弹出框,logo,启动页等等;http://www.html5plus.org/doc/zh_cn/nativeui.html
2.把SDK\assets中的data文件夹到eclipse项目中的assets
3.在eclipse的assets目录中创建apps; 在apps中创建一个文件夹,名字和你HBuilder的项目名相同;我这里就叫HelloMui; 在HelloMui中创建www文件夹; 把HBuilder项目拷贝到www文件夹下;
注意: apps和www文件夹名字是固定的,结构也是固定的,不然读取不到html
4.修改manifest.json和control.xml中的id为你的项目名;我这里是HelloMui
5.拷贝SDK\libs里面的jar包到eclipse项目下的libs;如图:
说明:
pdr.jar, ui.jar, nineoldandroids-2.4.0.jar是Webview基础包,必须导入
因为HelloMui项目中获取设备信息网络状态,需引入device.jar;
设置了浏览器运行环境,需引入navigatorui.jar(设置了状态栏,创建快捷方式,log输出,设置cookie都需引入此包)
使用了plus.storage,需引入nopermission.jar
其实在具体的项目中,这些包远远不够的:
比如ajax联网,需引入xhr.jar
全量更新和差量更新,需引入downloader.jar,invocation.jar
原生对话框和底部弹出或者toast,需引入nativeui.jar
设置用户头像,用到拍照,打开相册,需引入camera.jar,gallery.jar
具体请参照: http://ask.dcloud.net.cn/docs/#http://ask.dcloud.net.cn/article/216
6.打开eclipse项目的AndroidManifest.xml,配置权限,具体请参照官方SDK里的Feature-Android.xls文件
<!-- 联网 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 使用存储卡 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
7.把HBuilder-Integrate\src下的io文件夹,拷贝到eclipse项目的src
8.把Android-SDK\HBuilder-Integrate\src\com\HBuilder\integrate\SDK_WebApp.java 拷贝到eclipse项目的src包名下,并修改122行的appBasePath, 为assets的app路径
9.SDK_WebApp.java里面的FrameLayout是用来承载html的,可以放到任何你想放到的位置,比如点击一个按钮,弹窗或者打开新Activity展示; 我这里的话,直接用activity展示,并在AndroidManifest.xml设置启动就打开本界面
10.ok 完了 ,运行吧. 附上eclipse项目源码
最近忙着微信和m站,还有流应用开发,好久没有写文章了.
公司打算把原生app全部改写成html5,方便升级版本,毕竟IOS更新审核太久,耽误运营推广;
我的项目百度地图,友盟推送,反馈,统计,千牛商家聊天都是得用原生的,所以以后用MUI写好html后,得集成到原生项目中去.
首先吐槽下官方的集成方式所提到的概念:
Widget方式: 按照字面的意思就是,html相当于小部件一样和原生代码放一块,在需要的时候调用;
独立应用方式: 这个就是Widget方式,概念真多
Webview方式: 只能打开一个界面,不能打开新界面,这个没搞懂使用场景~~(>_<)~~我暂且把这个概念忘了,别搞混
当你打开官方http://ask.dcloud.net.cn/docs/#http://ask.dcloud.net.cn/article/38 又多了个概念: 离线打包
当你运行官方集成案例的时候,又多了个Runtime集成方式; 小白迟早会疯掉的
总之,按照我的理解:
1.如果你的项目原生代码和html混合着用,那么选择Widget方式集成,也就是独立应用方式;
2.如果你的项目是HBuilder写的纯html,没有原生代码,那么用Runtime集成方式,比较简单;
离线打包的概念是相对于HBuilder在线云打包而言的; 我们选上面各种集成方式用eclipse,Android Studio 或者XCode把html编译成安装包就属于离线打包了;这样避免云打包排队或者服务器挂掉的时候,自己还能编译安装包,照常发布更新版本;
ok,理清了官方的概念,你也基本懂了我写的标题:
Android独立应用方式集成HTML5 SDK,Widget方式离线打包,空项目讲解
一.准备
1.打开HBuilder,新建-->移动APP-->勾选"Hello mui",项目名叫"HelloMui";(我以官方mui项目来举例了,到时自行改为自己的项目);
2.打开eclipse,打开你的Android项目;(我这里是新建了一个Android项目,名字叫Widget; 如果你还没配置Android开发环境,则点这里http://jingyan.baidu.com/article/bea41d437a41b6b4c51be6c1.html );
- 去官方下载最新的sdk: http://ask.dcloud.net.cn/article/103 解压,里面有个名字叫'SDK'的文件夹
二.拷贝
1.把下载的官方SDK文件夹里的res文件夹拷贝到Android项目的res,合并;注意不要替换你项目的资源,不然你配的图标和写的strings.xml就没了
说明: res里面有NativeUI用到的图片资源: 进度条,对话框,动画样式,照片选择,底部弹出框,logo,启动页等等;http://www.html5plus.org/doc/zh_cn/nativeui.html
2.把SDK\assets中的data文件夹到eclipse项目中的assets
3.在eclipse的assets目录中创建apps; 在apps中创建一个文件夹,名字和你HBuilder的项目名相同;我这里就叫HelloMui; 在HelloMui中创建www文件夹; 把HBuilder项目拷贝到www文件夹下;
注意: apps和www文件夹名字是固定的,结构也是固定的,不然读取不到html
4.修改manifest.json和control.xml中的id为你的项目名;我这里是HelloMui
5.拷贝SDK\libs里面的jar包到eclipse项目下的libs;如图:
说明:
pdr.jar, ui.jar, nineoldandroids-2.4.0.jar是Webview基础包,必须导入
因为HelloMui项目中获取设备信息网络状态,需引入device.jar;
设置了浏览器运行环境,需引入navigatorui.jar(设置了状态栏,创建快捷方式,log输出,设置cookie都需引入此包)
使用了plus.storage,需引入nopermission.jar
其实在具体的项目中,这些包远远不够的:
比如ajax联网,需引入xhr.jar
全量更新和差量更新,需引入downloader.jar,invocation.jar
原生对话框和底部弹出或者toast,需引入nativeui.jar
设置用户头像,用到拍照,打开相册,需引入camera.jar,gallery.jar
具体请参照: http://ask.dcloud.net.cn/docs/#http://ask.dcloud.net.cn/article/216
6.打开eclipse项目的AndroidManifest.xml,配置权限,具体请参照官方SDK里的Feature-Android.xls文件
<!-- 联网 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 使用存储卡 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
7.把HBuilder-Integrate\src下的io文件夹,拷贝到eclipse项目的src
8.把Android-SDK\HBuilder-Integrate\src\com\HBuilder\integrate\SDK_WebApp.java 拷贝到eclipse项目的src包名下,并修改122行的appBasePath, 为assets的app路径
9.SDK_WebApp.java里面的FrameLayout是用来承载html的,可以放到任何你想放到的位置,比如点击一个按钮,弹窗或者打开新Activity展示; 我这里的话,直接用activity展示,并在AndroidManifest.xml设置启动就打开本界面
10.ok 完了 ,运行吧. 附上eclipse项目源码
收起阅读 »
离线打包下打开浏览器调试模式
由于自己编写了一些插件,所以不得不采取离线打包的方式来发布应用,在这个过程中,被不能浏览器调试浪费了不少时间,最后狠下心反编译了官方云打包的apk,发现需要修改一个配置文件
配置文件的位置:assets\data\control.xml
作如下修改即可打开调试模式
<hbuilder version="1.9.9.21259" debug="true">
<apps>
<app
appid="HBuilder"
appver="1.5.3" />
</apps>
</hbuilder>
由于自己编写了一些插件,所以不得不采取离线打包的方式来发布应用,在这个过程中,被不能浏览器调试浪费了不少时间,最后狠下心反编译了官方云打包的apk,发现需要修改一个配置文件
配置文件的位置:assets\data\control.xml
作如下修改即可打开调试模式
<hbuilder version="1.9.9.21259" debug="true">
<apps>
<app
appid="HBuilder"
appver="1.5.3" />
</apps>
</hbuilder>
收起阅读 »

【分享】本地缓存下载文件,download的二次封装
说明:
(1)由于平时项目中大量用到了附件下载等功能,所以就花了一个时间,把plus的downlaod进行了二次封装,用本地缓存方式来下载任何文件.
(2)这个也是在前面得本地缓存下载图片的基础上完善的,拓展了下,可以下载任何文件
功能:
1.本地缓存下载文件,如果用本地缓存方式,在缓存有效期会优先使用本地缓存
- 基于plus的storage,对每一个文件的缓存进行控制管理,增加时间戳参数,来控制本地缓存的有效时间
- 将所有的下载任务放入队列中,进行统一下载管理,增加最大并发请求数,防止一次性请求过多损耗性能
- 加入了文件缓存池机制,对于同一个下载路径的文件不会多次下载,而是填入缓存池,下载后统一回调
- 加入了手动剔除超时任务的处理,如果存在一个任务一直没有下载完成,也没有触发回调,会在下次下载时剔除任务,并触发错误回调
注: 这也是从自己写的框架中扒下来的,为了方便,就去除了一些无用的了。
使用方法:详情见示例源码
* 1.setOptions 设置下载参数
* 2.clearAllLocalFileCache 清除所有的本地缓存,设置参数路径内的缓存
* 3.clearNetUrlFileCache 删除某一个下载路径对应的本地缓存
* 4.downloadFileWidthLocalCache 通过本地缓存方法下载网络文件
* 5.RestoreOptions 还原默认的下载参数
* 6.abortTaskByUrl 根据url,取消对应的任务
* 7.abortAllTask 取消所有的任务
一个示例调用例子:
function downloadFile(IsWithCache) {
var showProgressbar = null;
var isCompleted = false;
DownloadUtil.downloadFileWidthLocalCache(fileUrl, {
beforeDownload: function() {
console.log('准备开始上传');
showProgressbar = plus.nativeUI.showWaiting('准备开始上传', {
back: "close",
padlock: true
});
showProgressbar.onclose = function() {
if (isCompleted == false) {
DownloadUtil.abortTaskByUrl(fileUrl);
}
};
},
successDownload: function(relativePath) {
isCompleted = true;
if (showProgressbar) {
showProgressbar.close();
}
console.log('下载成功:' + relativePath);
Zepto('#fileName').text(relativePath);
},
errorDownload: function(msg) {
isCompleted = true;
if (showProgressbar) {
showProgressbar.close();
}
Zepto('#fileName').text('下载失败:' + msg);
},
downloading: function(progress, tips) {
console.log('下载进度为:' + progress + '%,' + tips);
if (showProgressbar) {
showProgressbar.setTitle(parseInt(progress) + "%," + tips);
}
}
}, IsWithCache);
};
源码:
/**
* @description 移动开发框架
* @author dailc dailc
* @version 1.0
* @time 2016-01-11 16:57:57
* 功能模块:
* 通用框架类************************************
* scripts/Core/MobileFrame.js
* 1.包含一个plusReady 操作
* 2.包含一个 each操作
* 3.IsInclude 判断是否包含文件
* 4.IsNetWorkCanUse 判断是否有网络
* 通用框架类完毕*********************************
* 常用工具类****************************************
* scripts/Core/MobileFrame_CommonUtil.js
* 1.compareVersion 比较两个版本大小
* 2.getRelativePathKey 得到一个path的key-这个key是去除了非法字符的,可以用来本地缓存
* 3.changImgUrlTypeWithRandomKey 将url后面加上随机的key,用来去除缓存,否则同一个url会有缓存
* 4.delFile 删除本地文件
* 常用工具类完毕*************************************
* File工具类***************************************
* scripts/Core/MobileFrame_FileUtil.js
* 1.delFile 删除文件
* File工具类完毕************************************
* 下载工具类****************************************
* 1.本地缓存下载文件
* 2.增加storage,增加每一个本地缓存的有效时间戳
* 3.增加自定义设置方法,可以根据不同需求,对参数进行修改
* 4.采用下载队列进行下载管理,增加最大并发请求数,防止一次性请求过多损耗性能
* 注意:如果用了图片工具类.并且自定义了下载路径,下载工具类的默认参数也会相应变化,需要手动设置回来
* 1.setOptions 设置下载参数
* 2.clearAllLocalFileCache 清除所有的本地缓存,设置参数路径内的缓存
* 3.clearNetUrlFileCache 删除某一个下载路径对应的本地缓存
* 4.downloadFileWidthLocalCache 通过本地缓存方法下载网络文件
* 5.RestoreOptions 还原默认的下载参数
* 6.abortTaskByUrl 根据url,取消对应的任务
* 7.abortAllTask 取消所有的任务
* 下载工具类完毕*************************************
*/
(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 {
if (mapping[id]) {
cache[id] = mapping[id]({})
}
return cache[id];
}
};
/**
* @description 定义模块功能--通用框架类
*/
define('scripts/Core/MobileFrame.js', function(exports) {
/**
* 空函数
*/
exports.noop = function() {};
/**
* @description each遍历操作
* @param {type} elements
* @param {type} callback
* @returns {global}
*/
exports.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
*/
exports.plusReady = function(callback) {
if (window.plus) {
setTimeout(function() { //解决callback与plusready事件的执行时机问题(典型案例:showWaiting,closeWaiting)
callback();
}, 0);
} else {
document.addEventListener("plusready", function() {
callback();
}, false);
}
return global;
};
/**
* @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 判断是否有网络
*/
exports.IsNetWorkCanUse = function() {
var IsCanUseNetWork = false;
if (GetNetWorkState() == '未知' || GetNetWorkState() == '未连接网络') {
IsCanUseNetWork = false;
} else {
IsCanUseNetWork = true;
}
return IsCanUseNetWork;
};
/**
* @description 判断是否存在js或者css
* @param {String} name js或者css的名字
*/
exports.IsInclude = function(name) {
var js = /js$/i.test(name);
var es = document.getElementsByTagName(js ? 'script' : 'link');
for (var i = 0; i < es.length; i++)
if (es[i][js ? 'src' : 'href'].indexOf(name) != -1) return true;
return false;
};
return exports;
});
//*** 通用框架类完毕
/**
* @description 定义模块功能-常用工具类
*/
define('scripts/Core/MobileFrame_CommonUtil.js', function(exports) {
/**
* @description 比较两个版本大小
* 比较版本大小,如果新版本nowVersion大于旧版本OldResourceVersion则返回true,否则返回false
* @param {String} OldVersion
* @param {String} nowVersion
*/
exports.compareVersion = function(OldVersion, nowVersion) {
if (!OldVersion || !nowVersion || OldVersion == '' || nowVersion == '') {
return false;
}
//第二份参数 是 数组的最大长度
var OldVersionA = OldVersion.split(".", 4);
var nowVersionA = nowVersion.split(".", 4);
for (var i = 0; i < OldVersionA.length && i < nowVersionA.length; i++) {
var strOld = OldVersionA[i];
var numOld = parseInt(strOld);
var strNow = nowVersionA[i];
var numNow = parseInt(strNow);
//小版本到高版本
if (numNow > numOld
//||strNow.length>strOld.length
) {
return true;
} else if (numNow < numOld) {
return false;
}
}
//如果是版本 如 1.6 - 1.6.1
if (nowVersionA.length > OldVersionA.length && 0 == nowVersion.indexOf(OldVersion)) {
return true;
}
};
/**
* @description 得到相对路径对应的key,这个key可以使缓存池的或者是本地缓存键值
* 主要作用是去除非法字符
* @param {String} relativePath
*/
exports.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
*/
exports.changImgUrlTypeWithRandomKey = function(url) {
url = url || '';
if (url.indexOf('?') != -1) {
url += '&timeRandKey=' + Math.random();
} else {
url += '?timeRandKey=' + Math.random();
}
return url;
};
return exports;
});
//*** 常用工具类完毕
/**
* @description 定义模块功能-File工具类
*/
define('scripts/Core/MobileFrame_FileUtil.js', function(exports) {
var MobileFrame = require('scripts/Core/MobileFrame.js');
/**
* @description 删除指定路径的文件
* @param {String} relativePath 绝对路径或相对路径例如: _downloads/imgs/test.jpg
* @param {Function} successCallback 删除成功回调
* @param {Function} errorCallback 失败回调
*/
exports.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('打开文件路径失败!');
}
});
});
};
return exports;
});
//*** File工具类完毕
/**
* @description 定义模块功能-下载工具类,使用本地缓存进行下载
*/
define('scripts/Core/DownLoadUtil.js', function(exports) {
var MobileFrame = require('scripts/Core/MobileFrame.js');
var CommonUtil = require('scripts/Core/MobileFrame_CommonUtil.js');
var FileUtil = require('scripts/Core/MobileFrame_FileUtil.js');
/**
* 默认的options
*/
var defaultSettingOptions = {
//默认的下载缓存目录-存到应用的downloads/downloadFiles下
'downloadStoragePath': "_downloads/downloadFiles/",
//本地缓存的时间戳,毫秒单位,默认为1天
'fileCacheTimeStamp': 1000 * 60 * 60 * 24 * 1,
//同时最多的downloader 并发下载数目,默认为3个
'concurrentDownloadCount': 3,
//超时请求时间
'timeout': 3,
//超时请求后的重试次数
'retryInterval': 3,
//单个下载任务最大的请求时间,防止一些机型上无法触发错误回调,单位毫秒,默认10秒
'maxTimeSingleDownloadTaskSpend': 1000 * 10,
//获取相对路径的函数,如果不传,则用默认的路径处理方法
'getRelativePathFromLoadUrlCallback': null,
//监听进度的步长
'ListenerProgressStep':5
};
/**
* 备份一个默认的设置
*/
var oldDefaultSettingOptions = defaultSettingOptions;
/**
* 文件缓存的session头部
*/
var SessionKey_header = 'downloadFile_SessionKey_util_caches_';
/**
* 文件缓存的session的管理者
*/
var SessionManagerKey = 'downloadFile_SessionKey_util_Manager';
/**
* 文件缓存池,用来解决同一个url多次并发请求问题
* 默认是空的,当有多个url是同一个请求时,缓存池子中会有数据
* 格式 {'url1':[succCb1,succCb2]}
*/
var requestUrlCachePool = {};
/**
* 并发下载任务,包括下载队列,处理最大并发数下载
*/
var concurrentDownloadTask = {
//任务池-还没有下载的任务
Queue: [],
//当前正在下载的任务数量
CurrentTaskCount: 0
};
/**
* 当前的任务队列,包含任务的名称,以及时间戳-用来控制最大的超时时间,防止不能正常触发回调
* 包含:
* taskObj,timeBegin
* 格式:{url1:{task1,time1}}
*/
var currentDownloadTasks = {};
/**
* @description 将对应的缓存键值添加进入缓存管理中
* @param {String} key url对应的key
*/
function addSessionKeyToManager(key) {
//获取管理者
var manager = plus.storage.getItem(SessionManagerKey);
if (manager == null) {
//如果以前的缓存为空,生成缓存
manager = [];
} else {
try {
manager = JSON.parse(manager);
} catch (e) {}
}
if (manager.indexOf(key) == -1) {
manager.push(key);
}
plus.storage.setItem(SessionManagerKey, JSON.stringify(manager));
};
/**
* @description 从缓存管理中移除相应的缓存key
* @param {String} key url对应的key
*/
function removeSessionKeyFromManager(key) {
//获取管理者
var manager = plus.storage.getItem(SessionManagerKey);
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(SessionManagerKey, JSON.stringify(manager));
}
};
/**
* 设置缓存key
* @param {String} url
* @param {JSON} value 存进去的是相关的所有属性,包括时间戳,本地路径等
*/
function setSessionItem(url, value) {
if (url == null) {
return;
}
//然后添加进入缓存管理者中
addSessionKeyToManager(url);
url = SessionKey_header + CommonUtil.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 getSessionItem(url) {
if (url == null) {
return null;
}
//去除非法字符
url = SessionKey_header + CommonUtil.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 removeSessionItem(url) {
if (url == null) {
return null;
}
removeSessionKeyFromManager(url);
//去除非法字符
url = SessionKey_header + CommonUtil.getRelativePathKey(url);
var items = plus.storage.removeItem(url);
};
/**
* @description 移除所有的缓存键
*/
function clearAllSessionKey() {
MobileFrame.plusReady(function() {
var manager = plus.storage.getItem(SessionManagerKey);
if (manager == null) {
//这时候肯定没有离线缓存
return;
}
try {
manager = JSON.parse(manager);
} catch (e) {}
if (Array.isArray(manager)) {
for (var i = 0; i < manager.length; i++) {
removeSessionItem(manager[i]);
}
}
});
};
/**
* @description 设置options
* @param {JSON} options
*/
exports.setOptions = function(options) {
if (!options) {
return;
}
//设置参数
for (var key in defaultSettingOptions) {
//如果设置的是有效的
if (options[key] != null) {
defaultSettingOptions[key] = options[key];
}
}
};
/**
* @description 还原下载工具的参数,还原到默认值
*/
exports.RestoreOptions = function() {
if (oldDefaultSettingOptions) {
defaultSettingOptions = oldDefaultSettingOptions;
}
};
/**
* @description 清除下载工具的的所有本地缓存---路径为设置参数中的StoragePath
* @param {Function} successCallback 成功回调
* @param {Function} errorCallback 失败回调
*/
exports.clearAllLocalFileCache = function(successCallback, errorCallback) {
MobileFrame.plusReady(function() {
//遍历目录文件夹下的所有文件,然后删除
var tmpUrl = plus.io.convertLocalFileSystemURL(defaultSettingOptions['downloadStoragePath']);
//需要手动加上 file://
tmpUrl = 'file://' + tmpUrl;
//同时清除所有的缓存键值
clearAllSessionKey();
plus.io.resolveLocalFileSystemURL(tmpUrl, function(entry) {
entry.removeRecursively(function() {
if (successCallback && typeof(successCallback) == 'function') {
successCallback('清除本地缓存成功!路径:' + defaultSettingOptions['downloadStoragePath']);
}
}, function() {
if (errorCallback && typeof(errorCallback) == 'function') {
errorCallback('清除本地缓存失败!路径:' + defaultSettingOptions['downloadStoragePath']);
}
});
}, function(e) {
if (errorCallback && typeof(errorCallback) == 'function') {
errorCallback('打开本地缓存目录失败!' + defaultSettingOptions['downloadStoragePath']);
}
});
});
};
/**
* @description 删除某一个网络路径文件对应的的本地缓存,同时也会删除缓存键值
*/
exports.clearNetUrlFileCache = function(netUrl, successCallback, errorCallback) {
MobileFrame.plusReady(function() {
//删除该键值对应的缓存
removeSessionItem(netUrl);
FileUtil.delFile(getRelativePathFromLoadUrl(netUrl), successCallback, errorCallback);
});
};
/**
* @description 根据url,取消这个路径对应的下载任务
* @param {String} loadUrl
*/
exports.abortTaskByUrl = function(loadUrl) {
//取消进行中任务
currentDownloadTasks[loadUrl].taskObj && currentDownloadTasks[loadUrl].taskObj.abort && currentDownloadTasks[loadUrl].taskObj.abort();
concurrentDownloadTask['CurrentTaskCount']--;
//从当前任务队列中去除
currentDownloadTasks[loadUrl] = null;
//触发错误回调
checkDownloadSuccessOrError(loadUrl, false);
//取消队列中的任务
//清除队列中对应id的任务
for (var i = 0; i < concurrentDownloadTask['Queue'].length; i++) {
if (concurrentDownloadTask['Queue'][i].task &&
concurrentDownloadTask['Queue'][i].task.url == loadUrl) {
concurrentDownloadTask['Queue'][i].callbacks
&&concurrentDownloadTask['Queue'][i].callbacks.errorDownload
&&concurrentDownloadTask['Queue'][i].callbacks.errorDownload('下载队列中的任务被外部强行终结,url:' + loadUrl);
//移除对应位置的元素
concurrentDownloadTask['Queue'].splice(i,1);
}
}
};
/**
* @description 取消下载工具类中的所有下载任务
*/
exports.abortAllTask = function() {
//先取消进行中的任务
for (var taskItem in currentDownloadTasks) {
currentDownloadTasks[taskItem].taskObj && currentDownloadTasks[taskItem].taskObj.abort && currentDownloadTasks[taskItem].taskObj.abort();
//从当前任务队列中去除
currentDownloadTasks[taskItem] = null;
//触发错误回调
checkDownloadSuccessOrError(taskItem, false);
}
//清除备用队列
//取消队列中的任务
//清除队列中所有任务
for (var i = 0; i < concurrentDownloadTask['Queue'].length; i++) {
if (concurrentDownloadTask['Queue'][i].task ) {
concurrentDownloadTask['Queue'][i].callbacks
&&concurrentDownloadTask['Queue'][i].callbacks.errorDownload
&&concurrentDownloadTask['Queue'][i].callbacks.errorDownload('下载队列中的任务被外部强行终结,url:' + loadUrl);
}
}
concurrentDownloadTask['Queue'] = [];
concurrentDownloadTask['CurrentTaskCount'] = 0;
};
/**
* @description 路径处理方法,优先从回调函数中获取
* @param {String} loadUrl
*/
function getRelativePathFromLoadUrl(loadUrl) {
var relativePath = null;
if (defaultSettingOptions['getRelativePathFromLoadUrlCallback'] && typeof(defaultSettingOptions['getRelativePathFromLoadUrlCallback']) == 'function') {
//如果存在传入的回调
relativePath = defaultSettingOptions['getRelativePathFromLoadUrlCallback'](loadUrl);
} else {
//采用默认的路径处理
//获取图片后缀,如果没有获取到后缀
var fileSuffix = loadUrl.substring(loadUrl.lastIndexOf(".") + 1, loadUrl.length);
fileSuffix = fileSuffix || 'file';
//更换存储方式,变为将整个路径存储下来,然后去除非法字符
var regIllegal = /[&\|\\\*^%$#@\-:.?\/=!]/g;
//获取文件名字
var fileName = loadUrl.replace(regIllegal, '');
//最终的名字
var finalFileFullName = fileName + '.' + fileSuffix;
relativePath = defaultSettingOptions['downloadStoragePath'] + finalFileFullName;
}
return relativePath;
};
/**
* @description 判断该下载对应的本地缓存是否过期,
* @param {String} loadUrl
*/
function IsLocalCacheOutOfTime(loadUrl) {
//如果存在本地缓存,并且没有过期,采用本地缓存中的文件
var loacalSessionItem = getSessionItem(loadUrl);
if (loacalSessionItem != null) {
//判断是否过期 time localPath
if (loacalSessionItem.time) {
loacalSessionItem.time = parseInt(loacalSessionItem.time, 10);
if ((new Date()).valueOf() - loacalSessionItem.time > defaultSettingOptions['fileCacheTimeStamp']) {
//console.log('当前缓存已经过期')
//返回一个特殊字符,代表过期
return true;
} else {
//console.log('缓存未过期');
return false;
}
}
}
return false;
};
/**
* @description 通过本地缓存的方法下载文件
* @param {String} loadUrl loadurl
* @param {JSON} callbackOptions 存放各种回调函数
* 包括 beforeDownload,downloading successDownload,errorDownload
* @param {Boolean} IsUseCache 是否使用缓存,默认为true
*/
exports.downloadFileWidthLocalCache = function(loadUrl, callbackOptions, IsUseCache) {
if (loadUrl == null) return;
IsUseCache = typeof(IsUseCache) == 'boolean' ? IsUseCache : true;
callbackOptions = callbackOptions || {};
MobileFrame.plusReady(function() {
var relativePath = getRelativePathFromLoadUrl(loadUrl);
//判断需不需要将路径进行编码,如果是中文路径,需要编码后才能下载
var regChinese = /[\u4E00-\u9FA5]/g;
var tmpLoadUrl = loadUrl.replace(regChinese, 'chineseRemoveAfter');
if (tmpLoadUrl.indexOf('chineseRemoveAfter') != -1) {
loadUrl = encodeURI(loadUrl);
}
//判断缓存是否过期
if (IsLocalCacheOutOfTime(loadUrl) == false && IsUseCache == true) {
//如果缓存没有过期,并且使用了缓存
//检查文件是否已存在,如果存在就采取本地文件,否则重新获取
plus.io.resolveLocalFileSystemURL(relativePath, function(entry) {
//如果文件存在,则直接回调本地路径
callbackOptions.successDownload && callbackOptions.successDownload(relativePath, true);
}, function(e) {
readyToDownloadFromNet(loadUrl, callbackOptions);
});
} else {
//如果没有使用缓存或者缓存已经过期,从网络获取
readyToDownloadFromNet(loadUrl, callbackOptions);
}
});
};
/**
* @description 准备通过网络下载
* @param {String} loadUrl loadurl
* @param {JSON} callbackOptions 存放各种回调函数
* 包括 beforeDownload,downloading successDownload,errorDownload
*/
function readyToDownloadFromNet(loadUrl, callbackOptions) {
callbackOptions = callbackOptions || {};
//如果文件不存在,上网下载
if (MobileFrame.IsNetWorkCanUse() == true) {
//添加进入缓存池中
var relativePath = getRelativePathFromLoadUrl(loadUrl);
var relativePathKey = CommonUtil.getRelativePathKey(relativePath);
if (requestUrlCachePool && requestUrlCachePool[relativePathKey] && Array.isArray(requestUrlCachePool[relativePathKey])) {
//如果已经存在该条缓存池,代表这条资源已经进行请求了,只需要填进响应池子即可
//console.log('已经存在缓存池:'+relativePathKey);
requestUrlCachePool[relativePathKey].push(callbackOptions);
//1.下载之前的回调
callbackOptions.beforeDownload && callbackOptions.beforeDownload();
return;
} else {
//新建缓存池
//console.log('新建缓存池:'+relativePathKey);
requestUrlCachePool[relativePathKey] = [];
requestUrlCachePool[relativePathKey].push(callbackOptions);
}
//如果网络状态能用,联网下载
downloadFileFromNet(loadUrl, callbackOptions);
} else {
callbackOptions.errorDownload && callbackOptions.errorDownload('下载失败:' + '没有网络!' + ',url:' + loadUrl);
}
};
/**
* @description 从网络下载文件,并通过回调函数回调
* @param {String} loadUrl 网络路径
* @param {JSON} callbackOptions 存放各种回调函数
* 包括 beforeDownload,downloading successDownload,errorDownload
*/
function downloadFileFromNet(loadUrl, callbackOptions) {
var relativePath = getRelativePathFromLoadUrl(loadUrl);
if (relativePath == null) {
return;
}
callbackOptions = callbackOptions || {};
//下载参数
var options = {
filename: relativePath,
timeout: defaultSettingOptions['timeout'],
retryInterval: defaultSettingOptions['retryInterval']
};
//存一个最原始的地址,缓存是根据最原始的地址来的
var originalUrl = loadUrl;
//解决ios的网络缓存问题
loadUrl = CommonUtil.changImgUrlTypeWithRandomKey(loadUrl);
//1.下载之前的回调
callbackOptions.beforeDownload && callbackOptions.beforeDownload();
//2.创建下载任务
var dtask = plus.downloader.createDownload(loadUrl,
options,
function(d, status) {
if (status == 200) {
//下载成功
//console.log('绝对路径:'+d.filename);
//这里传入的是相对路径,方便缓存显示,回调过去的是相对路径
checkDownloadSuccessOrError(originalUrl, true);
} else {
//下载失败,需删除本地临时文件,否则下次进来时会检查到图片已存在
//console.log("下载失败=" + status + "==" + relativePath);
//dtask.abort();//文档描述:取消下载,删除临时文件;(但经测试临时文件没有删除,故使用delFile()方法删除);
if (relativePath != null) {
FileUtil.delFile(relativePath);
}
checkDownloadSuccessOrError(originalUrl, false);
}
//下载完成,当前任务数-1,并重新检查下载队列
concurrentDownloadTask['CurrentTaskCount']--;
//下载完成,从当前下载队列中去除
currentDownloadTasks[dtask.url] = null;
executeDownloadTasks();
});
//3.添加进度监听器,监听步长也由外部传入
var step = 0;
var progress = 0;
dtask.addEventListener("statechanged", function(task, status) {
switch (task.state) {
case 1: // 开始
callbackOptions.downloading && callbackOptions.downloading(0, "开始下载...");
break;
case 2: // 已连接到服务器
callbackOptions.downloading && callbackOptions.downloading(0, "已连接到服务器");
break;
case 3:
//每隔一定的比例显示一次
if (task.totalSize != 0) {
var progress = task.downloadedSize / task.totalSize * 100;
progress = Math.round(progress);
if (progress - step>=defaultSettingOptions.ListenerProgressStep) {
step = progress;
callbackOptions.downloading && callbackOptions.downloading(parseInt(progress), "下载中");
}
}
break;
case 4: // 下载完成
callbackOptions.downloading && callbackOptions.downloading(100, "下载完成100%");
break;
}
});
//4.启动下载任务,添加进入下载队列中
concurrentDownloadTask['Queue'].push({
task: dtask,
callbacks: callbackOptions
});
//执行并发下载队列
executeDownloadTasks();
};
/**
* @description 某一个url下载成功后检查回调和缓存池
* @param {String} loadUrl
* @param {Boolean} IsSuccess
*/
function checkDownloadSuccessOrError(loadUrl, IsSuccess) {
var relativePath = getRelativePathFromLoadUrl(loadUrl);
var relativePathKey = CommonUtil.getRelativePathKey(relativePath);
if (requestUrlCachePool && requestUrlCachePool[relativePathKey]) {
var callbackData = requestUrlCachePool[relativePathKey];
//如果是数组
if (Array.isArray(callbackData)) {
for (var i = 0; i < callbackData.length; i++) {
if (IsSuccess == true) {
callbackData[i].successDownload && callbackData[i].successDownload(relativePath, IsSuccess);
} else {
callbackData[i].errorDownload && callbackData[i].errorDownload('下载失败', IsSuccess);
}
}
} else {
//单条数据--单个对调
if (IsSuccess == true) {
callbackData.successDownload && callbackData.successDownload(relativePath, IsSuccess);
} else {
callbackData.errorDownload && callbackData.errorDownload('下载失败', IsSuccess);
}
}
requestUrlCachePool[relativePathKey] = null;
}
};
/**
* @description 执行下载任务,通过队列中一个一个的进行
*/
function executeDownloadTasks() {
//console.log('检查下载队列');
//先检查是否存在任务超时的
//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 && currentDownloadTasks[taskItem].taskObj.abort();
concurrentDownloadTask['CurrentTaskCount']--;
//从当前任务队列中去除
currentDownloadTasks[taskItem] = null;
//触发错误回调
checkDownloadSuccessOrError(taskItem, false);
//console.log('存在超时的任务,手动剔除');
}
}
//如果当前下载任务小于并发下载数
if (concurrentDownloadTask['CurrentTaskCount'] < defaultSettingOptions['concurrentDownloadCount']) {
if (concurrentDownloadTask['Queue'].length > 0) {
//开启一个下载任务
var nowTaskOptions = concurrentDownloadTask['Queue'].shift();
var nowTask = nowTaskOptions.task;
nowTask.start()
//当前任务数++
concurrentDownloadTask['CurrentTaskCount']++;
currentDownloadTasks[nowTask.url] = {
taskObj: nowTask,
timeBegin: (new Date()).valueOf()
}
//console.log('添加一个下载任务');
} else {
//console.log('已经没有了下载任务');
}
} else {
//console.log('已经达到最大下载数量,延迟下载');
}
};
return exports;
});
//*** 下载工具类完毕
})(this);
欢迎留言讨论!
源码项目见附件
说明:
(1)由于平时项目中大量用到了附件下载等功能,所以就花了一个时间,把plus的downlaod进行了二次封装,用本地缓存方式来下载任何文件.
(2)这个也是在前面得本地缓存下载图片的基础上完善的,拓展了下,可以下载任何文件
功能:
1.本地缓存下载文件,如果用本地缓存方式,在缓存有效期会优先使用本地缓存
- 基于plus的storage,对每一个文件的缓存进行控制管理,增加时间戳参数,来控制本地缓存的有效时间
- 将所有的下载任务放入队列中,进行统一下载管理,增加最大并发请求数,防止一次性请求过多损耗性能
- 加入了文件缓存池机制,对于同一个下载路径的文件不会多次下载,而是填入缓存池,下载后统一回调
- 加入了手动剔除超时任务的处理,如果存在一个任务一直没有下载完成,也没有触发回调,会在下次下载时剔除任务,并触发错误回调
注: 这也是从自己写的框架中扒下来的,为了方便,就去除了一些无用的了。
使用方法:详情见示例源码
* 1.setOptions 设置下载参数
* 2.clearAllLocalFileCache 清除所有的本地缓存,设置参数路径内的缓存
* 3.clearNetUrlFileCache 删除某一个下载路径对应的本地缓存
* 4.downloadFileWidthLocalCache 通过本地缓存方法下载网络文件
* 5.RestoreOptions 还原默认的下载参数
* 6.abortTaskByUrl 根据url,取消对应的任务
* 7.abortAllTask 取消所有的任务
一个示例调用例子:
function downloadFile(IsWithCache) {
var showProgressbar = null;
var isCompleted = false;
DownloadUtil.downloadFileWidthLocalCache(fileUrl, {
beforeDownload: function() {
console.log('准备开始上传');
showProgressbar = plus.nativeUI.showWaiting('准备开始上传', {
back: "close",
padlock: true
});
showProgressbar.onclose = function() {
if (isCompleted == false) {
DownloadUtil.abortTaskByUrl(fileUrl);
}
};
},
successDownload: function(relativePath) {
isCompleted = true;
if (showProgressbar) {
showProgressbar.close();
}
console.log('下载成功:' + relativePath);
Zepto('#fileName').text(relativePath);
},
errorDownload: function(msg) {
isCompleted = true;
if (showProgressbar) {
showProgressbar.close();
}
Zepto('#fileName').text('下载失败:' + msg);
},
downloading: function(progress, tips) {
console.log('下载进度为:' + progress + '%,' + tips);
if (showProgressbar) {
showProgressbar.setTitle(parseInt(progress) + "%," + tips);
}
}
}, IsWithCache);
};
源码:
/**
* @description 移动开发框架
* @author dailc dailc
* @version 1.0
* @time 2016-01-11 16:57:57
* 功能模块:
* 通用框架类************************************
* scripts/Core/MobileFrame.js
* 1.包含一个plusReady 操作
* 2.包含一个 each操作
* 3.IsInclude 判断是否包含文件
* 4.IsNetWorkCanUse 判断是否有网络
* 通用框架类完毕*********************************
* 常用工具类****************************************
* scripts/Core/MobileFrame_CommonUtil.js
* 1.compareVersion 比较两个版本大小
* 2.getRelativePathKey 得到一个path的key-这个key是去除了非法字符的,可以用来本地缓存
* 3.changImgUrlTypeWithRandomKey 将url后面加上随机的key,用来去除缓存,否则同一个url会有缓存
* 4.delFile 删除本地文件
* 常用工具类完毕*************************************
* File工具类***************************************
* scripts/Core/MobileFrame_FileUtil.js
* 1.delFile 删除文件
* File工具类完毕************************************
* 下载工具类****************************************
* 1.本地缓存下载文件
* 2.增加storage,增加每一个本地缓存的有效时间戳
* 3.增加自定义设置方法,可以根据不同需求,对参数进行修改
* 4.采用下载队列进行下载管理,增加最大并发请求数,防止一次性请求过多损耗性能
* 注意:如果用了图片工具类.并且自定义了下载路径,下载工具类的默认参数也会相应变化,需要手动设置回来
* 1.setOptions 设置下载参数
* 2.clearAllLocalFileCache 清除所有的本地缓存,设置参数路径内的缓存
* 3.clearNetUrlFileCache 删除某一个下载路径对应的本地缓存
* 4.downloadFileWidthLocalCache 通过本地缓存方法下载网络文件
* 5.RestoreOptions 还原默认的下载参数
* 6.abortTaskByUrl 根据url,取消对应的任务
* 7.abortAllTask 取消所有的任务
* 下载工具类完毕*************************************
*/
(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 {
if (mapping[id]) {
cache[id] = mapping[id]({})
}
return cache[id];
}
};
/**
* @description 定义模块功能--通用框架类
*/
define('scripts/Core/MobileFrame.js', function(exports) {
/**
* 空函数
*/
exports.noop = function() {};
/**
* @description each遍历操作
* @param {type} elements
* @param {type} callback
* @returns {global}
*/
exports.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
*/
exports.plusReady = function(callback) {
if (window.plus) {
setTimeout(function() { //解决callback与plusready事件的执行时机问题(典型案例:showWaiting,closeWaiting)
callback();
}, 0);
} else {
document.addEventListener("plusready", function() {
callback();
}, false);
}
return global;
};
/**
* @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 判断是否有网络
*/
exports.IsNetWorkCanUse = function() {
var IsCanUseNetWork = false;
if (GetNetWorkState() == '未知' || GetNetWorkState() == '未连接网络') {
IsCanUseNetWork = false;
} else {
IsCanUseNetWork = true;
}
return IsCanUseNetWork;
};
/**
* @description 判断是否存在js或者css
* @param {String} name js或者css的名字
*/
exports.IsInclude = function(name) {
var js = /js$/i.test(name);
var es = document.getElementsByTagName(js ? 'script' : 'link');
for (var i = 0; i < es.length; i++)
if (es[i][js ? 'src' : 'href'].indexOf(name) != -1) return true;
return false;
};
return exports;
});
//*** 通用框架类完毕
/**
* @description 定义模块功能-常用工具类
*/
define('scripts/Core/MobileFrame_CommonUtil.js', function(exports) {
/**
* @description 比较两个版本大小
* 比较版本大小,如果新版本nowVersion大于旧版本OldResourceVersion则返回true,否则返回false
* @param {String} OldVersion
* @param {String} nowVersion
*/
exports.compareVersion = function(OldVersion, nowVersion) {
if (!OldVersion || !nowVersion || OldVersion == '' || nowVersion == '') {
return false;
}
//第二份参数 是 数组的最大长度
var OldVersionA = OldVersion.split(".", 4);
var nowVersionA = nowVersion.split(".", 4);
for (var i = 0; i < OldVersionA.length && i < nowVersionA.length; i++) {
var strOld = OldVersionA[i];
var numOld = parseInt(strOld);
var strNow = nowVersionA[i];
var numNow = parseInt(strNow);
//小版本到高版本
if (numNow > numOld
//||strNow.length>strOld.length
) {
return true;
} else if (numNow < numOld) {
return false;
}
}
//如果是版本 如 1.6 - 1.6.1
if (nowVersionA.length > OldVersionA.length && 0 == nowVersion.indexOf(OldVersion)) {
return true;
}
};
/**
* @description 得到相对路径对应的key,这个key可以使缓存池的或者是本地缓存键值
* 主要作用是去除非法字符
* @param {String} relativePath
*/
exports.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
*/
exports.changImgUrlTypeWithRandomKey = function(url) {
url = url || '';
if (url.indexOf('?') != -1) {
url += '&timeRandKey=' + Math.random();
} else {
url += '?timeRandKey=' + Math.random();
}
return url;
};
return exports;
});
//*** 常用工具类完毕
/**
* @description 定义模块功能-File工具类
*/
define('scripts/Core/MobileFrame_FileUtil.js', function(exports) {
var MobileFrame = require('scripts/Core/MobileFrame.js');
/**
* @description 删除指定路径的文件
* @param {String} relativePath 绝对路径或相对路径例如: _downloads/imgs/test.jpg
* @param {Function} successCallback 删除成功回调
* @param {Function} errorCallback 失败回调
*/
exports.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('打开文件路径失败!');
}
});
});
};
return exports;
});
//*** File工具类完毕
/**
* @description 定义模块功能-下载工具类,使用本地缓存进行下载
*/
define('scripts/Core/DownLoadUtil.js', function(exports) {
var MobileFrame = require('scripts/Core/MobileFrame.js');
var CommonUtil = require('scripts/Core/MobileFrame_CommonUtil.js');
var FileUtil = require('scripts/Core/MobileFrame_FileUtil.js');
/**
* 默认的options
*/
var defaultSettingOptions = {
//默认的下载缓存目录-存到应用的downloads/downloadFiles下
'downloadStoragePath': "_downloads/downloadFiles/",
//本地缓存的时间戳,毫秒单位,默认为1天
'fileCacheTimeStamp': 1000 * 60 * 60 * 24 * 1,
//同时最多的downloader 并发下载数目,默认为3个
'concurrentDownloadCount': 3,
//超时请求时间
'timeout': 3,
//超时请求后的重试次数
'retryInterval': 3,
//单个下载任务最大的请求时间,防止一些机型上无法触发错误回调,单位毫秒,默认10秒
'maxTimeSingleDownloadTaskSpend': 1000 * 10,
//获取相对路径的函数,如果不传,则用默认的路径处理方法
'getRelativePathFromLoadUrlCallback': null,
//监听进度的步长
'ListenerProgressStep':5
};
/**
* 备份一个默认的设置
*/
var oldDefaultSettingOptions = defaultSettingOptions;
/**
* 文件缓存的session头部
*/
var SessionKey_header = 'downloadFile_SessionKey_util_caches_';
/**
* 文件缓存的session的管理者
*/
var SessionManagerKey = 'downloadFile_SessionKey_util_Manager';
/**
* 文件缓存池,用来解决同一个url多次并发请求问题
* 默认是空的,当有多个url是同一个请求时,缓存池子中会有数据
* 格式 {'url1':[succCb1,succCb2]}
*/
var requestUrlCachePool = {};
/**
* 并发下载任务,包括下载队列,处理最大并发数下载
*/
var concurrentDownloadTask = {
//任务池-还没有下载的任务
Queue: [],
//当前正在下载的任务数量
CurrentTaskCount: 0
};
/**
* 当前的任务队列,包含任务的名称,以及时间戳-用来控制最大的超时时间,防止不能正常触发回调
* 包含:
* taskObj,timeBegin
* 格式:{url1:{task1,time1}}
*/
var currentDownloadTasks = {};
/**
* @description 将对应的缓存键值添加进入缓存管理中
* @param {String} key url对应的key
*/
function addSessionKeyToManager(key) {
//获取管理者
var manager = plus.storage.getItem(SessionManagerKey);
if (manager == null) {
//如果以前的缓存为空,生成缓存
manager = [];
} else {
try {
manager = JSON.parse(manager);
} catch (e) {}
}
if (manager.indexOf(key) == -1) {
manager.push(key);
}
plus.storage.setItem(SessionManagerKey, JSON.stringify(manager));
};
/**
* @description 从缓存管理中移除相应的缓存key
* @param {String} key url对应的key
*/
function removeSessionKeyFromManager(key) {
//获取管理者
var manager = plus.storage.getItem(SessionManagerKey);
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(SessionManagerKey, JSON.stringify(manager));
}
};
/**
* 设置缓存key
* @param {String} url
* @param {JSON} value 存进去的是相关的所有属性,包括时间戳,本地路径等
*/
function setSessionItem(url, value) {
if (url == null) {
return;
}
//然后添加进入缓存管理者中
addSessionKeyToManager(url);
url = SessionKey_header + CommonUtil.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 getSessionItem(url) {
if (url == null) {
return null;
}
//去除非法字符
url = SessionKey_header + CommonUtil.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 removeSessionItem(url) {
if (url == null) {
return null;
}
removeSessionKeyFromManager(url);
//去除非法字符
url = SessionKey_header + CommonUtil.getRelativePathKey(url);
var items = plus.storage.removeItem(url);
};
/**
* @description 移除所有的缓存键
*/
function clearAllSessionKey() {
MobileFrame.plusReady(function() {
var manager = plus.storage.getItem(SessionManagerKey);
if (manager == null) {
//这时候肯定没有离线缓存
return;
}
try {
manager = JSON.parse(manager);
} catch (e) {}
if (Array.isArray(manager)) {
for (var i = 0; i < manager.length; i++) {
removeSessionItem(manager[i]);
}
}
});
};
/**
* @description 设置options
* @param {JSON} options
*/
exports.setOptions = function(options) {
if (!options) {
return;
}
//设置参数
for (var key in defaultSettingOptions) {
//如果设置的是有效的
if (options[key] != null) {
defaultSettingOptions[key] = options[key];
}
}
};
/**
* @description 还原下载工具的参数,还原到默认值
*/
exports.RestoreOptions = function() {
if (oldDefaultSettingOptions) {
defaultSettingOptions = oldDefaultSettingOptions;
}
};
/**
* @description 清除下载工具的的所有本地缓存---路径为设置参数中的StoragePath
* @param {Function} successCallback 成功回调
* @param {Function} errorCallback 失败回调
*/
exports.clearAllLocalFileCache = function(successCallback, errorCallback) {
MobileFrame.plusReady(function() {
//遍历目录文件夹下的所有文件,然后删除
var tmpUrl = plus.io.convertLocalFileSystemURL(defaultSettingOptions['downloadStoragePath']);
//需要手动加上 file://
tmpUrl = 'file://' + tmpUrl;
//同时清除所有的缓存键值
clearAllSessionKey();
plus.io.resolveLocalFileSystemURL(tmpUrl, function(entry) {
entry.removeRecursively(function() {
if (successCallback && typeof(successCallback) == 'function') {
successCallback('清除本地缓存成功!路径:' + defaultSettingOptions['downloadStoragePath']);
}
}, function() {
if (errorCallback && typeof(errorCallback) == 'function') {
errorCallback('清除本地缓存失败!路径:' + defaultSettingOptions['downloadStoragePath']);
}
});
}, function(e) {
if (errorCallback && typeof(errorCallback) == 'function') {
errorCallback('打开本地缓存目录失败!' + defaultSettingOptions['downloadStoragePath']);
}
});
});
};
/**
* @description 删除某一个网络路径文件对应的的本地缓存,同时也会删除缓存键值
*/
exports.clearNetUrlFileCache = function(netUrl, successCallback, errorCallback) {
MobileFrame.plusReady(function() {
//删除该键值对应的缓存
removeSessionItem(netUrl);
FileUtil.delFile(getRelativePathFromLoadUrl(netUrl), successCallback, errorCallback);
});
};
/**
* @description 根据url,取消这个路径对应的下载任务
* @param {String} loadUrl
*/
exports.abortTaskByUrl = function(loadUrl) {
//取消进行中任务
currentDownloadTasks[loadUrl].taskObj && currentDownloadTasks[loadUrl].taskObj.abort && currentDownloadTasks[loadUrl].taskObj.abort();
concurrentDownloadTask['CurrentTaskCount']--;
//从当前任务队列中去除
currentDownloadTasks[loadUrl] = null;
//触发错误回调
checkDownloadSuccessOrError(loadUrl, false);
//取消队列中的任务
//清除队列中对应id的任务
for (var i = 0; i < concurrentDownloadTask['Queue'].length; i++) {
if (concurrentDownloadTask['Queue'][i].task &&
concurrentDownloadTask['Queue'][i].task.url == loadUrl) {
concurrentDownloadTask['Queue'][i].callbacks
&&concurrentDownloadTask['Queue'][i].callbacks.errorDownload
&&concurrentDownloadTask['Queue'][i].callbacks.errorDownload('下载队列中的任务被外部强行终结,url:' + loadUrl);
//移除对应位置的元素
concurrentDownloadTask['Queue'].splice(i,1);
}
}
};
/**
* @description 取消下载工具类中的所有下载任务
*/
exports.abortAllTask = function() {
//先取消进行中的任务
for (var taskItem in currentDownloadTasks) {
currentDownloadTasks[taskItem].taskObj && currentDownloadTasks[taskItem].taskObj.abort && currentDownloadTasks[taskItem].taskObj.abort();
//从当前任务队列中去除
currentDownloadTasks[taskItem] = null;
//触发错误回调
checkDownloadSuccessOrError(taskItem, false);
}
//清除备用队列
//取消队列中的任务
//清除队列中所有任务
for (var i = 0; i < concurrentDownloadTask['Queue'].length; i++) {
if (concurrentDownloadTask['Queue'][i].task ) {
concurrentDownloadTask['Queue'][i].callbacks
&&concurrentDownloadTask['Queue'][i].callbacks.errorDownload
&&concurrentDownloadTask['Queue'][i].callbacks.errorDownload('下载队列中的任务被外部强行终结,url:' + loadUrl);
}
}
concurrentDownloadTask['Queue'] = [];
concurrentDownloadTask['CurrentTaskCount'] = 0;
};
/**
* @description 路径处理方法,优先从回调函数中获取
* @param {String} loadUrl
*/
function getRelativePathFromLoadUrl(loadUrl) {
var relativePath = null;
if (defaultSettingOptions['getRelativePathFromLoadUrlCallback'] && typeof(defaultSettingOptions['getRelativePathFromLoadUrlCallback']) == 'function') {
//如果存在传入的回调
relativePath = defaultSettingOptions['getRelativePathFromLoadUrlCallback'](loadUrl);
} else {
//采用默认的路径处理
//获取图片后缀,如果没有获取到后缀
var fileSuffix = loadUrl.substring(loadUrl.lastIndexOf(".") + 1, loadUrl.length);
fileSuffix = fileSuffix || 'file';
//更换存储方式,变为将整个路径存储下来,然后去除非法字符
var regIllegal = /[&\|\\\*^%$#@\-:.?\/=!]/g;
//获取文件名字
var fileName = loadUrl.replace(regIllegal, '');
//最终的名字
var finalFileFullName = fileName + '.' + fileSuffix;
relativePath = defaultSettingOptions['downloadStoragePath'] + finalFileFullName;
}
return relativePath;
};
/**
* @description 判断该下载对应的本地缓存是否过期,
* @param {String} loadUrl
*/
function IsLocalCacheOutOfTime(loadUrl) {
//如果存在本地缓存,并且没有过期,采用本地缓存中的文件
var loacalSessionItem = getSessionItem(loadUrl);
if (loacalSessionItem != null) {
//判断是否过期 time localPath
if (loacalSessionItem.time) {
loacalSessionItem.time = parseInt(loacalSessionItem.time, 10);
if ((new Date()).valueOf() - loacalSessionItem.time > defaultSettingOptions['fileCacheTimeStamp']) {
//console.log('当前缓存已经过期')
//返回一个特殊字符,代表过期
return true;
} else {
//console.log('缓存未过期');
return false;
}
}
}
return false;
};
/**
* @description 通过本地缓存的方法下载文件
* @param {String} loadUrl loadurl
* @param {JSON} callbackOptions 存放各种回调函数
* 包括 beforeDownload,downloading successDownload,errorDownload
* @param {Boolean} IsUseCache 是否使用缓存,默认为true
*/
exports.downloadFileWidthLocalCache = function(loadUrl, callbackOptions, IsUseCache) {
if (loadUrl == null) return;
IsUseCache = typeof(IsUseCache) == 'boolean' ? IsUseCache : true;
callbackOptions = callbackOptions || {};
MobileFrame.plusReady(function() {
var relativePath = getRelativePathFromLoadUrl(loadUrl);
//判断需不需要将路径进行编码,如果是中文路径,需要编码后才能下载
var regChinese = /[\u4E00-\u9FA5]/g;
var tmpLoadUrl = loadUrl.replace(regChinese, 'chineseRemoveAfter');
if (tmpLoadUrl.indexOf('chineseRemoveAfter') != -1) {
loadUrl = encodeURI(loadUrl);
}
//判断缓存是否过期
if (IsLocalCacheOutOfTime(loadUrl) == false && IsUseCache == true) {
//如果缓存没有过期,并且使用了缓存
//检查文件是否已存在,如果存在就采取本地文件,否则重新获取
plus.io.resolveLocalFileSystemURL(relativePath, function(entry) {
//如果文件存在,则直接回调本地路径
callbackOptions.successDownload && callbackOptions.successDownload(relativePath, true);
}, function(e) {
readyToDownloadFromNet(loadUrl, callbackOptions);
});
} else {
//如果没有使用缓存或者缓存已经过期,从网络获取
readyToDownloadFromNet(loadUrl, callbackOptions);
}
});
};
/**
* @description 准备通过网络下载
* @param {String} loadUrl loadurl
* @param {JSON} callbackOptions 存放各种回调函数
* 包括 beforeDownload,downloading successDownload,errorDownload
*/
function readyToDownloadFromNet(loadUrl, callbackOptions) {
callbackOptions = callbackOptions || {};
//如果文件不存在,上网下载
if (MobileFrame.IsNetWorkCanUse() == true) {
//添加进入缓存池中
var relativePath = getRelativePathFromLoadUrl(loadUrl);
var relativePathKey = CommonUtil.getRelativePathKey(relativePath);
if (requestUrlCachePool && requestUrlCachePool[relativePathKey] && Array.isArray(requestUrlCachePool[relativePathKey])) {
//如果已经存在该条缓存池,代表这条资源已经进行请求了,只需要填进响应池子即可
//console.log('已经存在缓存池:'+relativePathKey);
requestUrlCachePool[relativePathKey].push(callbackOptions);
//1.下载之前的回调
callbackOptions.beforeDownload && callbackOptions.beforeDownload();
return;
} else {
//新建缓存池
//console.log('新建缓存池:'+relativePathKey);
requestUrlCachePool[relativePathKey] = [];
requestUrlCachePool[relativePathKey].push(callbackOptions);
}
//如果网络状态能用,联网下载
downloadFileFromNet(loadUrl, callbackOptions);
} else {
callbackOptions.errorDownload && callbackOptions.errorDownload('下载失败:' + '没有网络!' + ',url:' + loadUrl);
}
};
/**
* @description 从网络下载文件,并通过回调函数回调
* @param {String} loadUrl 网络路径
* @param {JSON} callbackOptions 存放各种回调函数
* 包括 beforeDownload,downloading successDownload,errorDownload
*/
function downloadFileFromNet(loadUrl, callbackOptions) {
var relativePath = getRelativePathFromLoadUrl(loadUrl);
if (relativePath == null) {
return;
}
callbackOptions = callbackOptions || {};
//下载参数
var options = {
filename: relativePath,
timeout: defaultSettingOptions['timeout'],
retryInterval: defaultSettingOptions['retryInterval']
};
//存一个最原始的地址,缓存是根据最原始的地址来的
var originalUrl = loadUrl;
//解决ios的网络缓存问题
loadUrl = CommonUtil.changImgUrlTypeWithRandomKey(loadUrl);
//1.下载之前的回调
callbackOptions.beforeDownload && callbackOptions.beforeDownload();
//2.创建下载任务
var dtask = plus.downloader.createDownload(loadUrl,
options,
function(d, status) {
if (status == 200) {
//下载成功
//console.log('绝对路径:'+d.filename);
//这里传入的是相对路径,方便缓存显示,回调过去的是相对路径
checkDownloadSuccessOrError(originalUrl, true);
} else {
//下载失败,需删除本地临时文件,否则下次进来时会检查到图片已存在
//console.log("下载失败=" + status + "==" + relativePath);
//dtask.abort();//文档描述:取消下载,删除临时文件;(但经测试临时文件没有删除,故使用delFile()方法删除);
if (relativePath != null) {
FileUtil.delFile(relativePath);
}
checkDownloadSuccessOrError(originalUrl, false);
}
//下载完成,当前任务数-1,并重新检查下载队列
concurrentDownloadTask['CurrentTaskCount']--;
//下载完成,从当前下载队列中去除
currentDownloadTasks[dtask.url] = null;
executeDownloadTasks();
});
//3.添加进度监听器,监听步长也由外部传入
var step = 0;
var progress = 0;
dtask.addEventListener("statechanged", function(task, status) {
switch (task.state) {
case 1: // 开始
callbackOptions.downloading && callbackOptions.downloading(0, "开始下载...");
break;
case 2: // 已连接到服务器
callbackOptions.downloading && callbackOptions.downloading(0, "已连接到服务器");
break;
case 3:
//每隔一定的比例显示一次
if (task.totalSize != 0) {
var progress = task.downloadedSize / task.totalSize * 100;
progress = Math.round(progress);
if (progress - step>=defaultSettingOptions.ListenerProgressStep) {
step = progress;
callbackOptions.downloading && callbackOptions.downloading(parseInt(progress), "下载中");
}
}
break;
case 4: // 下载完成
callbackOptions.downloading && callbackOptions.downloading(100, "下载完成100%");
break;
}
});
//4.启动下载任务,添加进入下载队列中
concurrentDownloadTask['Queue'].push({
task: dtask,
callbacks: callbackOptions
});
//执行并发下载队列
executeDownloadTasks();
};
/**
* @description 某一个url下载成功后检查回调和缓存池
* @param {String} loadUrl
* @param {Boolean} IsSuccess
*/
function checkDownloadSuccessOrError(loadUrl, IsSuccess) {
var relativePath = getRelativePathFromLoadUrl(loadUrl);
var relativePathKey = CommonUtil.getRelativePathKey(relativePath);
if (requestUrlCachePool && requestUrlCachePool[relativePathKey]) {
var callbackData = requestUrlCachePool[relativePathKey];
//如果是数组
if (Array.isArray(callbackData)) {
for (var i = 0; i < callbackData.length; i++) {
if (IsSuccess == true) {
callbackData[i].successDownload && callbackData[i].successDownload(relativePath, IsSuccess);
} else {
callbackData[i].errorDownload && callbackData[i].errorDownload('下载失败', IsSuccess);
}
}
} else {
//单条数据--单个对调
if (IsSuccess == true) {
callbackData.successDownload && callbackData.successDownload(relativePath, IsSuccess);
} else {
callbackData.errorDownload && callbackData.errorDownload('下载失败', IsSuccess);
}
}
requestUrlCachePool[relativePathKey] = null;
}
};
/**
* @description 执行下载任务,通过队列中一个一个的进行
*/
function executeDownloadTasks() {
//console.log('检查下载队列');
//先检查是否存在任务超时的
//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 && currentDownloadTasks[taskItem].taskObj.abort();
concurrentDownloadTask['CurrentTaskCount']--;
//从当前任务队列中去除
currentDownloadTasks[taskItem] = null;
//触发错误回调
checkDownloadSuccessOrError(taskItem, false);
//console.log('存在超时的任务,手动剔除');
}
}
//如果当前下载任务小于并发下载数
if (concurrentDownloadTask['CurrentTaskCount'] < defaultSettingOptions['concurrentDownloadCount']) {
if (concurrentDownloadTask['Queue'].length > 0) {
//开启一个下载任务
var nowTaskOptions = concurrentDownloadTask['Queue'].shift();
var nowTask = nowTaskOptions.task;
nowTask.start()
//当前任务数++
concurrentDownloadTask['CurrentTaskCount']++;
currentDownloadTasks[nowTask.url] = {
taskObj: nowTask,
timeBegin: (new Date()).valueOf()
}
//console.log('添加一个下载任务');
} else {
//console.log('已经没有了下载任务');
}
} else {
//console.log('已经达到最大下载数量,延迟下载');
}
};
return exports;
});
//*** 下载工具类完毕
})(this);
欢迎留言讨论!
源码项目见附件
收起阅读 »
用native.js跳转到指定应用在设置里的详情页面 设置-应用-xx应用详情
用native.js跳转到指定应用在设置里的详情页面 设置-应用-xx应用详情
原生的方法也用注释写在了里面,方便大家了解native.js的使用。
function startActivity(packageName) {
// 原生
// Uri packageURI = Uri.parse("package:"+ "com.xxxx.xxx");
// Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,packageURI);
// startActivity(intent);
var Uri = plus.android.importClass("android.net.Uri");
var Settings = plus.android.importClass("android.provider.Settings");
var context = plus.android.runtimeMainActivity();
var packageURI = Uri.parse("package:" + packageName);
var intent = plus.android.newObject("android.content.Intent", Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
context.startActivity(intent);
}
用native.js跳转到指定应用在设置里的详情页面 设置-应用-xx应用详情
原生的方法也用注释写在了里面,方便大家了解native.js的使用。
function startActivity(packageName) {
// 原生
// Uri packageURI = Uri.parse("package:"+ "com.xxxx.xxx");
// Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,packageURI);
// startActivity(intent);
var Uri = plus.android.importClass("android.net.Uri");
var Settings = plus.android.importClass("android.provider.Settings");
var context = plus.android.runtimeMainActivity();
var packageURI = Uri.parse("package:" + packageName);
var intent = plus.android.newObject("android.content.Intent", Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
context.startActivity(intent);
}
收起阅读 »

使用mui开发web app第二天
解决横屏问题:
(转载)
真机运行时,manifest并不会实时生效,要打包才生效。
横屏有3个层级:
- 手机禁止横屏
- 手机允许横屏,但manifest禁止横屏
- 手机允许横屏、manifest允许横屏,但页面代码禁止横屏。
js里控制:
//仅支持竖屏显示
plus.screen.lockOrientation("landscape-primary");
manifest.json里控制:
"orientation": [
"portrait-primary",
"portrait-secondary"
]
**真机测试初始还是竖屏,看帖子应该没什么问题,打包后会解决。
解决横屏问题:
(转载)
真机运行时,manifest并不会实时生效,要打包才生效。
横屏有3个层级:
- 手机禁止横屏
- 手机允许横屏,但manifest禁止横屏
- 手机允许横屏、manifest允许横屏,但页面代码禁止横屏。
js里控制:
//仅支持竖屏显示
plus.screen.lockOrientation("landscape-primary");
manifest.json里控制:
"orientation": [
"portrait-primary",
"portrait-secondary"
]
**真机测试初始还是竖屏,看帖子应该没什么问题,打包后会解决。
收起阅读 »
使用mui开发web app第一天
之前一直使用jqm做手机wap站,现在有了新需求,要做web app。使用jqm封装以后出现了如下几个问题:
1、上拉下拉时,头部跟脚部无法固定。这个问题暂时使用iscrool.js搞定,但是拖动时会出现卡屏现象,不流畅。
2、跳页出现白屏。
3、页面元素虽然都是自己设计的,已经十分偏向于IOS了,但是一些控件还是差距很大。很low的感觉。
再说下个人掌握的技能:
1、HTML5跟CSS3 ------ 熟练
2、JQuery ----- 熟练
3、JS ---- 能看懂(这点我不得不承认,以前太依赖JQ了,JS的书写水平也就是小学生水准的,getElementById啥的这个水平)
由于出现了jqm的问题,不得已寻找其他框架。并且在朋友的推荐下(也不算朋友,一面之缘,他说这个不错),发现了MUI,于是开始入坑。
今天完成的工作:
1、下载HBuilder,创建demo : hello mui,发现确实不错。
2、看了网站的文档,对这个框架有了基础的了解。
问题:
我要做的是pad,左侧边栏是功能栏,需要固定住。右侧是主体部分。右侧包括了头部跟webview部分。
结语:
明天我会带ipad来真机实测下,并解决这个问题。
之前一直使用jqm做手机wap站,现在有了新需求,要做web app。使用jqm封装以后出现了如下几个问题:
1、上拉下拉时,头部跟脚部无法固定。这个问题暂时使用iscrool.js搞定,但是拖动时会出现卡屏现象,不流畅。
2、跳页出现白屏。
3、页面元素虽然都是自己设计的,已经十分偏向于IOS了,但是一些控件还是差距很大。很low的感觉。
再说下个人掌握的技能:
1、HTML5跟CSS3 ------ 熟练
2、JQuery ----- 熟练
3、JS ---- 能看懂(这点我不得不承认,以前太依赖JQ了,JS的书写水平也就是小学生水准的,getElementById啥的这个水平)
由于出现了jqm的问题,不得已寻找其他框架。并且在朋友的推荐下(也不算朋友,一面之缘,他说这个不错),发现了MUI,于是开始入坑。
今天完成的工作:
1、下载HBuilder,创建demo : hello mui,发现确实不错。
2、看了网站的文档,对这个框架有了基础的了解。
问题:
我要做的是pad,左侧边栏是功能栏,需要固定住。右侧是主体部分。右侧包括了头部跟webview部分。
结语:
明天我会带ipad来真机实测下,并解决这个问题。

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一统江山的局面。”
收起阅读 »