HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

【交流分享】Android独立应用方式集成HTML5+SDK,Widget方式离线打包,空项目讲解

源码分享 技术分享 SDK widget 集成 Android

最近忙着微信和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 );

  1. 去官方下载最新的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 );

  1. 去官方下载最新的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的二次封装

技术分享 downloader

说明:
(1)由于平时项目中大量用到了附件下载等功能,所以就花了一个时间,把plus的downlaod进行了二次封装,用本地缓存方式来下载任何文件.
(2)这个也是在前面得本地缓存下载图片的基础上完善的,拓展了下,可以下载任何文件
功能:
1.本地缓存下载文件,如果用本地缓存方式,在缓存有效期会优先使用本地缓存

  1. 基于plus的storage,对每一个文件的缓存进行控制管理,增加时间戳参数,来控制本地缓存的有效时间
  2. 将所有的下载任务放入队列中,进行统一下载管理,增加最大并发请求数,防止一次性请求过多损耗性能
  3. 加入了文件缓存池机制,对于同一个下载路径的文件不会多次下载,而是填入缓存池,下载后统一回调
  4. 加入了手动剔除超时任务的处理,如果存在一个任务一直没有下载完成,也没有触发回调,会在下次下载时剔除任务,并触发错误回调
    注: 这也是从自己写的框架中扒下来的,为了方便,就去除了一些无用的了。

使用方法:详情见示例源码

 * 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.本地缓存下载文件,如果用本地缓存方式,在缓存有效期会优先使用本地缓存

  1. 基于plus的storage,对每一个文件的缓存进行控制管理,增加时间戳参数,来控制本地缓存的有效时间
  2. 将所有的下载任务放入队列中,进行统一下载管理,增加最大并发请求数,防止一次性请求过多损耗性能
  3. 加入了文件缓存池机制,对于同一个下载路径的文件不会多次下载,而是填入缓存池,下载后统一回调
  4. 加入了手动剔除超时任务的处理,如果存在一个任务一直没有下载完成,也没有触发回调,会在下次下载时剔除任务,并触发错误回调
    注: 这也是从自己写的框架中扒下来的,为了方便,就去除了一些无用的了。

使用方法:详情见示例源码

 * 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

用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个层级:

  1. 手机禁止横屏
  2. 手机允许横屏,但manifest禁止横屏
  3. 手机允许横屏、manifest允许横屏,但页面代码禁止横屏。

js里控制:
//仅支持竖屏显示
plus.screen.lockOrientation("landscape-primary");

manifest.json里控制:
"orientation": [
"portrait-primary",
"portrait-secondary"
]

**真机测试初始还是竖屏,看帖子应该没什么问题,打包后会解决。

继续阅读 »

解决横屏问题:
(转载)
真机运行时,manifest并不会实时生效,要打包才生效。
横屏有3个层级:

  1. 手机禁止横屏
  2. 手机允许横屏,但manifest禁止横屏
  3. 手机允许横屏、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一统江山的局面。”

收起阅读 »

真是醉了, 我想问问官方文档咋回事

下拉刷新

真是醉了, 我想问问官方文档咋回事

API 不想说啥了, 真的..., 各种找不到, 属性 参数 一个比一个难找

子页 不 支持 'slide-in-right' 也没看到个说明

独立一级页, 安卓机下拉刷新也是个大BUG, 哎, 刚用就发现这么多坑

不知道什么时候能趟完这些坑

继续阅读 »

真是醉了, 我想问问官方文档咋回事

API 不想说啥了, 真的..., 各种找不到, 属性 参数 一个比一个难找

子页 不 支持 'slide-in-right' 也没看到个说明

独立一级页, 安卓机下拉刷新也是个大BUG, 哎, 刚用就发现这么多坑

不知道什么时候能趟完这些坑

收起阅读 »

使用Native.js实现打开页面默认弹出软键盘

弹出软键盘 Native.JS focus

先来体验下这个神奇的功能(兼容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

Geolocation

参考问题: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。。。

收起阅读 »

【分享】图片下载本地缓存时间戳显示图片方法

技术分享 downloader 图片加工

参考文章
http://ask.dcloud.net.cn/article/256
http://ask.dcloud.net.cn/article/397

说明:为了方便,里面使用的图片来源是从上面文章中的源码项目获取的.
说明:参考了上面文章中的思路,然后自己重新写了一个较为完整的图片本地缓存显示工具.
功能
1.第一次显示图片时下载到本地,然后之后如果本地存在缓存(根据url),则显示本地缓存的图片

  1. 基于plus的storage,对每一个图片的缓存进行控制管理,增加时间戳参数,来控制本地缓存的有效时间
  2. 将所有的下载任务放入队列中,进行统一下载管理,增加最大并发请求数,防止一次性请求过多损耗性能
  3. 加入了图片缓存池机制,对于同一个下载路径的图片不会多次下载,而是填入缓存池,下载后统一回调
  4. 修改了图片本地路径的获取方法,摆脱第三方依赖
  5. 重构了代码,并入开发框架中

使用方法

 * 外部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&&currentDownloadTasks[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),则显示本地缓存的图片

  1. 基于plus的storage,对每一个图片的缓存进行控制管理,增加时间戳参数,来控制本地缓存的有效时间
  2. 将所有的下载任务放入队列中,进行统一下载管理,增加最大并发请求数,防止一次性请求过多损耗性能
  3. 加入了图片缓存池机制,对于同一个下载路径的图片不会多次下载,而是填入缓存池,下载后统一回调
  4. 修改了图片本地路径的获取方法,摆脱第三方依赖
  5. 重构了代码,并入开发框架中

使用方法

 * 外部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&&currentDownloadTasks[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 提供的思路!
欢迎大家留言,讨论!

源码见附件

收起阅读 »