图片下载本地缓存时间戳显示图片方法
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图片下载本地缓存时间戳显示图片方法相关的知识,希望对你有一定的参考价值。
来源:http://ask.dcloud.net.cn/article/511
参考文章
http://ask.dcloud.net.cn/article/256
http://ask.dcloud.net.cn/article/397
说明:为了方便,里面使用的图片来源是从上面文章中的源码项目获取的.
说明:参考了上面文章中的思路,然后自己重新写了一个较为完整的图片本地缓存显示工具.
功能
1.第一次显示图片时下载到本地,然后之后如果本地存在缓存(根据url),则显示本地缓存的图片
2. 基于plus的storage,对每一个图片的缓存进行控制管理,增加时间戳参数,来控制本地缓存的有效时间
3. 将所有的下载任务放入队列中,进行统一下载管理,增加最大并发请求数,防止一次性请求过多损耗性能
4. 加入了图片缓存池机制,对于同一个下载路径的图片不会多次下载,而是填入缓存池,下载后统一回调
5. 修改了图片本地路径的获取方法,摆脱第三方依赖
6. 重构了代码,并入开发框架中
使用方法
* 外部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 本地相对路径 Swift新async/await并发中利用Task防止指定代码片段执行的数据竞争(Data Race)问题