项目场景:
采用 vue + cordova 开发的App, 项目首屏为 three.js 编写的3D场景
问题描述
3D模型的数据是首屏启动后前端调用接口获取的json数据,数据大小30M.
用户在首页与其他页面切换时,调用该接口时间过长,并消耗大量流量
解决思路:
- 首页页面不销毁,做缓存. (可行,但three.js的页面内存占用量极大,不做特殊处理的话会导致其他页面卡顿,并造成app崩溃闪退)
- 将3D模型数据一并打入app,从项目资源中读取(缺点:3D模型更新时需要重新发版App.不能及时获取到最新的3D模型)
- 首次调用接口后,将3D模型数据缓存到手机文件中,之后再次获取时,从手机文件中读取.(缺点:根据手机性能,读取也存在卡顿问题,部分手机读取大文件会闪退,可以将数据切片处理解决该问题)
实现步骤:
这里主要记录 如何利用 cordova cordova-plugin-file 插件 向手机存储/读取文件,其他逻辑省略
在cordova项目根目录下 安装 cordova-plugin-file 插件
cordova plugin add cordova-plugin-file
安装之后,在cordova项目下, plugins文件下, 会有该插件
编写 写入文件方法
// 写入文件
writeFileLast(dataObj, name) { //dataObj为你要存入为文件,//name是我自己用,根据自己情况使用
return new Promise((resolve, reject) => { //因为是异步 我加了promise 方便同步代码
window.resolveLocalFileSystemURL(cordova.file.externalDataDirectory,
(dir) => {
//dir大概是你存文件的那个文件夹 调用getFile根据参数获取文件夹下的文件
console.log("文件路径: " + cordova.file.externalDataDirectory);
dir.getFile(`${name}.txt`, { create: true },
//第一个参数是你想获取的文件名字
//第二个参数 为true的时候 意味着如果在此文件夹下没有读取到对应名字的文件,文件夹下
创建一个以第一个参数为名字的空白文件
(fileEntry) => {
//fileEntry是上一个步骤获取的文件
fileEntry.createWriter(function (fileWriter) {
//写入成功的回调
fileWriter.onwriteend = function () {
console.log("Successful file write...");
resolve()
};
//写入失败的回调
fileWriter.onerror = function (e) {
console.log("Failed file write: " + e.toString());
reject("Failed file write: " + e.toString())
};
// 如果dataObj是null,则创建一个新的文件
if (!dataObj) {
dataObj = new Blob(['我是一个测试文件'], { type: 'text/plain' });
}
// 写入内容
fileWriter.write(dataObj);
})
},
(err) => { console.log('getFile错误', err) }
);
}, (err) => { console.log('resolveLocalFileSystemURL错误', err); });
})
},
编写 读取文件方法
// 读取文件
readFileLast(name) { //name 要读取的文件名字
//let _this = this
return new Promise((resolve, reject) => {
window.resolveLocalFileSystemURL(cordova.file.externalDataDirectory,
// 成功回调
(dir) => { //和读取参数大同小异 以下不在赘述
console.log("文件存储路径: " + cordova.file.externalDataDirectory);
//在该目录下获取 一个基础变量 所有文件操作基于此变量
dir.getFile(`${name}.txt`, { create: false },
// 成功回调
(fileEntry) => {
console.log("指定目录下获取到相关文件 --基础文件信息", fileEntry);
fileEntry.file(function (file) {
let reader = new FileReader(); //创建一个读取器
reader.onloadend = (e) => {
console.log('文件信息:' + fileEntry)
console.log('文件信息:' + file)
// console.log('内容', reader.result);
//_this.isLoad = true;
resolve(reader.result) //将结果送出去
}
reader.readAsText(file); //读取文本类型文件使用
// reader.readAsArrayBuffer(file); //读取二进制文件 (大概吧,我忘记了~)
})
},
// 失败回调
(err) => {
console.log('getFile错误', err)
//this.isLoad = false;
// 失败 但保证要继续运行
resolve(false)
}
);
},
// 失败回调
(err) => { console.log('resolveLocalFileSystemURL错误', err); });
})
},
最后,还有一点就是,性能差的手机,读取10M以上的数据,都可能会有闪退现象,建议存文件的时候,先进行切片处理. 分享一下我的切片思路
// 包体拆分
splitFile(obj) { //obj是我要存的文件 数据格式是JSON
let data = JSON.stringify(obj); //先序列化成json字符串
if (data.length > 10000 * 1000) { //大概大于10M
let num = Math.ceil(data.length / (10000 * 1000)); //拆分份数
let arr = [];
for (let i = 0; i < num; i++) {
let str = data.slice(i * 10000 * 1000, (i + 1) * 10000 * 1000);
arr.push(str);
}
console.log('json数据拆分后的字符串数组', arr);
this.config.num = arr.length //这个config 是一个记录配置信息 记录了文件切割了多少份 以
及文件的名字,到时候从本地读取的时候 要根据config里文件名字 + 份数索引 循环从本地读取
arr.forEach((item, index) => {
//给每份切割包 一个文件名字
let fileName = this.config.upFileName.replace('.json', '') + index
this.writeFileLast(item, fileName);
})
} else { //小于10M 直接存 不拆分
let fileName = this.config.upFileName.replace('.json', '') + 0
this.writeFileLast(data, fileName);
}
// 配置文件 这个文件一并存入手机
let fileName = this.config.upFileName.replace('.json', '')
let file = JSON.stringify(this.config)
console.log('存配置文件 json', file);
this.writeFileLast(file, fileName);
},
//读取的时候 先读取手机的config配置文件 从里面获取 文件名字 还有份数
//循环读取 文件名字+索引 读取出每个分包
//之后将分包 通过js合并 最后在序列化 JSON.parse()转换回来
// 循环读取文件
loadFile() {
let fileList = []
// 读取所有分包文件
for (let i = 0; i < this.config.num; i++) {
let fileName = this.config.upFileName.replace('.json', '') + i
let fileItem = this.readFileLast(fileName)
fileList.push(fileItem)
}
// 全部读取完毕 且没有异常
Promise.all(fileList).then(async res => {
console.log('全部读取完毕');
// 把分包拼成总包
let result = {
data: JSON.parse(res.join(''))
}
console.log('拼接后的总包', result);
if (this.isLoad) {//保证全部读取成功
this.create3D(result)
} else {
console.log('有文件读取失败');
}
})
}
成功存入后,进入你的手机文件 Andriod->Data->你的包名(com.开头的,参考我的下图)->files文件夹下,就是我们刚才存入的文件了