起因

原ATRI文本文件大小为1.6MB,而watch gt这破表加载300KB的文本文件时,不管是import的形式,还是storage的形式,亦或是file.read的形式,都会卡到重启。
于是,在此背景下研究了一个分块加载文本的代码格式

举个例子

我有如下一个json的文本列表,我就会在达到某一个条件时加载其中符合条件的那个。
PS:之前试过使用require分块加载,但是还是会报错,不知道是系统原因还是啥原因,人家小米全import加载都不会报错(
jsonDataList: [
      'b999.json', 'b101.json', 'b102.json', 'b103.json', 'b111.json', 'b112.json', 'b113.json', 'b114.json',
      'b121.json', 'b122.json', 'b123.json', 'b124.json', 'b200.json', 'b201.json', 'b202.json', 'b203.json',
      'b204.json', 'b205.json', 'b206.json', 'b207.json', 'b301.json', 'b302.json', 'b303.json', 'b304.json',
      'b401.json', 'b402.json', 'b403.json', 'b404.json', 'b405.json', 'b406.json', 'b407.json', 'b501.json',
      'b601.json', 'b701.json'
    ],
如下代码为ATRI加载文本文件使用的方法
PS:原计划是将文本放在本地的,但是我不会读取读取本地文件,开发文档也没讲,寄!
async loadData(chapter) {
    const num = this.jsonDataList[chapter];

    // 将回调转换为Promise
    return new Promise((resolve, reject) => {
      file.readText({
        uri: `internal://files/${num}`,
        success: (data) => {
          try {
            const parsedData = JSON.parse(data.text);
            resolve(parsedData); // 正确传递解析后的数据
          } catch (e) {
            reject("JSON解析失败");
          }
        },
        fail: (_, code) => {
          reject(`文件读取失败,错误码:${code}`);
        }
      });
    });
  },
最近在做一个可查找的词典,但是由于词库过大,加载200KB的索引会卡死,于是将索引分为26份,按照首字母(全部转化为小写)进行查找,不必将所有索引全部加载,实现按需查找的功能,并且也不会爆内存,完美!
下面给出我写的下载模块,因为watch gt不支持数据块传输,所以只能使用全部下载字节流的方式了:
import fetch from '@blueos.network.fetch'
import file from '@blueos.storage.file'
import prompt from '@blueos.window.prompt'
export default {
  data: {
    indexData: null,
    jsonDataList: ['output_part_1.json'],
  },
  async fetchPromise(options) {
    return new Promise((resolve, reject) => {
      fetch.fetch({
        ...options,
        success: resolve,
        fail: reject,
      });
    });
  },
  async writeTextPromise(options) {
    return new Promise((resolve, reject) => {
      file.writeText({
        ...options,
        success: resolve,
        fail: (data, code) => reject({ data, code }),
      });
    });
  },
  async writeArrayBufferPromise(options) {
    return new Promise((resolve, reject) => {
      file.writeArrayBuffer({
        ...options,
        success: resolve,
        fail: (data, code) => reject({ data, code }),
      });
    });
  },
  async getFile(fileName) {
    return new Promise((resolve, reject) => {
      file.get({
        uri: `internal://files/${fileName}`,
        success: () => {
          resolve(true);
        },
        fail: (_, code) => {
          reject(`handling fail, code = ${code}`);
        },
      });
    });
  },
  async downloadFile(data) {
    prompt.showToast({ message: "开始下载" });
    let totalNum = data.length;
    let hasDownload = 0;
    for (let i = 0; i < totalNum; i++) {
      const fileName = data[i];
      let isInFile = false;
      try {
        isInFile = await this.getFile(fileName);
      } catch (error) {
        console.log(error);
      }
      if (isInFile) hasDownload++;
      if (!isInFile) {
        try {
          let fileUri = `internal://files/${fileName}`;
          let baseUrl = 'https://chorblack.top'
          const chunkResponse = await this.fetchPromise({
            url: `${baseUrl}/app/dictionary/${fileName}`,
            responseType: "arraybuffer",
          });
          const textData = chunkResponse.data;
          await this.writeArrayBufferPromise({
            uri: fileUri,
            buffer: new Uint8Array(textData),
          });
          prompt.showToast({
            message: `还剩${data.length - i - 1}`
          })
          hasDownload++;
        } catch (error) {
          console.error(`下载失败: ${fileName}`, error);

        }
      }
    }
    if (hasDownload==totalNum) prompt.showToast({ message: "下载完成" });
  }
}
下面提供分块传输的方法:
<template>
  <div class="wrapper">
    <text class="title">{{ title }}</text>
    <input class="btn" type="button" value="下载" onclick="onDetailBtnClick" />
    <input
      class="btn"
      type="button"
      value="跳转到列表"
      onclick="onDetailList"
    />
  </div>
</template>

<script>
import router from '@blueos.app.appmanager.router'
import fetch from '@blueos.network.fetch'
import prompt from '@blueos.window.prompt'
import file from '@blueos.storage.file'
export default {
  data: {
    title: '👏欢迎体验应用开发',
  },
  onDetailList() {
    router.push({
      uri: '/pages/DemoDetail',
    })
  },

  onDetailBtnClick() {
    this.downloadFile('b101.json');
  },
  downLoadTest() {

  },
  async fetchPromise(options) {
    return new Promise((resolve, reject) => {
      fetch.fetch({
        ...options,
        success: resolve,
        fail: reject,
      });
    });
  },
  async writeArrayBufferPromise(options) {
    return new Promise((resolve, reject) => {
      file.writeArrayBuffer({
        ...options,
        success: resolve,
        fail: (data, code) => reject({ data, code }),
      });
    });
  },

  async downloadFile(name, chunkSize = 4096) {
    let downloaded = 0,
      totalSize = 0,
      startByte = 0;
    let fileUri = `internal://files/${name}`;
    let baseUrl = '支持分块传输的url'
    // 0. 清空文件缓存
    await this.writeArrayBufferPromise({
      uri: fileUri,
      buffer: new Uint8Array(0), // 写入空缓冲
      append: false, // 非追加模式会覆盖文件
    });

    // 1. 获取文件总大小
    try {
      const totalSizeResponse = await this.fetchPromise({
        url: `${baseUrl}/${name}`,
        responseType: "arraybuffer",
        header: { Range: "bytes=0-0" },
      });
      const contentRange = totalSizeResponse.headers["Content-Range"];
      totalSize = contentRange ? parseInt(contentRange.split("/")[1]) : 0;
      prompt.showToast({ message: `文件大小: ${totalSize}` });
    } catch (error) {
      prompt.showToast({ message: "获取文件大小失败" });
      return;
    }

    // 2. 分块下载
    while (downloaded < totalSize) {
      
      const endByte = Math.min(startByte + chunkSize - 1, totalSize - 1);
      try {
        const chunkResponse = await this.fetchPromise({
          url: `${baseUrl}/${name}`,
          responseType: "arraybuffer",
          header: { Range: `bytes=${startByte}-${endByte}` },
        });
        if (chunkResponse.code === 206) {
          const data = chunkResponse.data;
          //prompt.showToast({ message: `字节数:${data.length}` });
          await this.writeArrayBufferPromise({
            uri: fileUri,
            buffer: new Uint8Array(data),
            append: true, // 追加模式
          });
          downloaded += data.byteLength;
          
          startByte = endByte + 1;
          prompt.showToast({ message:`Progress: ${((downloaded / totalSize) * 100).toFixed(1)}%` });
          console.log(`Progress: ${((downloaded / totalSize) * 100).toFixed(1)}%`);
        } else {
          prompt.showToast({ message: `错误代码:${chunkResponse.code}` });
          return;
        }
      } catch (error) {
        prompt.showToast({ message: error });
        return;
      }
    }

    prompt.showToast({ message: "下载完成" });
  }

}
</script>

<style lang="scss">
@import './../../assets/styles/style.scss';

.wrapper {
  @include flex-box-mixins(column, center, center);
  .title {
    font-size: 7 * $size-factor;
    text-align: center;
    color: $black;
  }

  .btn {
    width: 55 * $size-factor;
    height: 12 * $size-factor;
    border-radius: 7 * $size-factor;
    background-color: $brand;
    color: $white;
    font-size: 5 * $size-factor;
    margin-top: 7 * $size-factor;
  }
}
</style>