使用DataCache三方库提升应用冷启动速度 原创

狼哥Army
发布于 2025-3-30 13:05
浏览
1收藏

介绍

      在鸿蒙生态开发中,DataCache作为三方库之一的高性能组件,针对应用冷启动耗时问题提供了创新解决方案。该组件通过优先加载本地缓存数据,显著缩短应用启动时间,优化用户体验。其内置的通用本地缓存库,支持开发者高效实现缓存逻辑,既提升开发效率,又增强应用响应速度。DataCache采用先进缓存策略,确保数据一致性与快速访问,特别适用于频繁读取相同数据且更新不频繁的场景。本案例将基于DataCache库的数据缓存能力,提升HarmonyOS应用冷启动速度的功能,以优化应用使用体验。

说明

      本案例是参考官方示例代码案例学习,从中学到DataCache三方库的使用,使用Refresh组件和List组件实现下拉刷新,上滑加载更多知识点,每次学习官方示例代码或Codelab都能学习到新的知识点,大家也可以尝试打开两个DevEco,一个是打开参考案例,一个是自己创建的项目,最好代码块里的目录和文件,代码都是亲自手工添加的,不能为了方便复制、粘贴,这样不利用理解参考案例的思路。

效果预览

使用DataCache三方库提升应用冷启动速度-鸿蒙开发者社区

工程目录

├──entry/src/main/ets/
│  ├──commons
│  │  ├──CommonConstants.ets               // 公共常量
│  │  └──CommonTypes.ets                   // 公共类型型
│  ├──utils
│  │  └──Refresh.ets                       // 刷新工具类
│  ├──entryability
│  │  └──EntryAbility.ets                  // 程序入口类
│  ├──model
│  │  └──NewsModel.ets                     // 模型文件
│  ├──pages                 
│  │  └──Index.ets                         // 首页
│  └──view     
│     ├──ListPage.ets                      // News列表页     
│     ├──NewsItem.ets                      // 列表item项         
│     └──TabBar.ets                        // TabBar
└──entry/src/main/resources                // 应用资源目录
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

具体实现

安装DataCache三方库
ohpm install @hadss/datacache
  • 1.
EntryAbility.ets使用三方库
  1. 定义LocalStorage本地存储变量和网络请求http变量

    export default class EntryAbility extends UIAbility {
      // LocalStorage是ArkTS为构建页面级别状态变量提供存储的内存内的“数据库”。
      // 构建本地存储
      storage: LocalStorage = new LocalStorage()
      // 创建一个HTTP请求,里面包括发起请求、中断请求、订阅/取消订阅HTTP Response Header事件。
      net = http.createHttp()
        
      ......  
    }
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
  2. 定义本地缓存获取和存储方法

      /**
       * 获取本地缓存数据
       * @returns
       */
      async getCache(): Promise<ResData[] | undefined> {
        const dataCache = DataCache.getInstance(this.context)
        const listData = await dataCache.get<ResData[]>('List')
        if (listData) {
          // 存储到LocalStorage
          this.storage.setOrCreate('List', listData);
        }
        return listData;
      }
    
      /**
       * 设置本地缓存数据
       * @param data
       */
      async setCache(data: ResData[]) {
        const dataCache = DataCache.getInstance(this.context)
        const arrData: Array<Promise<void>> = []
        data.forEach(it => {
          it.imagesUrl.forEach(img => {
            arrData.push((async() => {
              const url = await this.net.request(img.url)
              let base64 = new util.Base64Helper()
              let arr = new Uint8Array(url.result as ArrayBuffer)
              let base64Str = IMAGE_BASE64_PREFIX + base64.encodeToStringSync(arr)
              img.url = base64Str
            })())
          })
        })
        await Promise.all(arrData)
        // 有效期为2天
        dataCache.put('List', data, 2*24*60*60)
      }
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
    • 11.
    • 12.
    • 13.
    • 14.
    • 15.
    • 16.
    • 17.
    • 18.
    • 19.
    • 20.
    • 21.
    • 22.
    • 23.
    • 24.
    • 25.
    • 26.
    • 27.
    • 28.
    • 29.
    • 30.
    • 31.
    • 32.
    • 33.
    • 34.
    • 35.
    • 36.
  3. 在onCreate回调函数添加数据缓存

    // 冷启动时,从本地缓存获取数据,没缓存数据,请求网络数据
        this.getCache().then((cache) => {
          if (!cache) {
            this.net.request(`${getUrl(this.context)}pageNum=1&pageSize=${PAGE_SIZE}`).then((data) => {
              const resData: ResData[] = JSON.parse(data.result as string).data
              this.storage.setOrCreate('List', resData)
              this.setCache(resData)
            }).catch(() => {
              hilog.info(DOMAIN, 'testTag', '%{public}s', '网络请求失败');
              this.storage.setOrCreate('Fail', true)
            })
          }
        })
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
    • 11.
    • 12.
    • 13.
  4. 加载页面时,记得、记得、记得把上面定义的LocalStorage变量作为参数传入,不然在页面获取不到LocalStorage存储的数据。

      onWindowStageCreate(windowStage: window.WindowStage): void {
        // 记得在loadContent页面时,把localStorage变量传入
        windowStage.loadContent('pages/Index', this.storage, (err) => {
          if (err.code) {
            hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err));
            return;
          }
          hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.');
        });
      }
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
Index.ets页面接收LocalStorage
  1. 获取当前UIAbility实例内共享的LocalStorage实例

    let storage = LocalStorage.getShared()
    
    @Entry(storage)
    @Component
    struct Index {
      build() {
        Column() {
          TabBar()
        }
        .height(FULL_HEIGHT)
        .width(FULL_WIDTH)
        .backgroundColor('#F1F3F5')
      }
    }
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
    • 11.
    • 12.
    • 13.
    • 14.
  2. ListPage.ets组件监听LocalStorage存储数据

    export default struct ListPage {
      @State isRefresh: boolean = false;
      @State newsModel: NewsModel = new NewsModel();
      // Fail Key 在EntryAbility设置
      @LocalStorageLink('Fail') isFail: boolean = false;
      // List Key 在EntryAbility设置,并且监听数据变化回调getInitData函数
      @LocalStorageLink('List') @Watch('getInitData') resData: ResData[] = [];
      context = getContext();
    
      /**
       * 初始化数据
       */
      getInitData() {
        this.newsModel.currentPage = 1
        if (this.resData.length === this.newsModel.pageSize) {
          this.newsModel.currentPage++;
          this.newsModel.hasMore = true
        } else {
          this.newsModel.hasMore = false
        }
        this.newsModel.newsData = this.resData
        if (this.resData.length !== 0) {
          this.newsModel.pageState = Const.PageState.SUCCESS
        }
      }
    
      aboutToAppear(): void {
        this.getInitData()
      }
    
      ......
    }
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
    • 11.
    • 12.
    • 13.
    • 14.
    • 15.
    • 16.
    • 17.
    • 18.
    • 19.
    • 20.
    • 21.
    • 22.
    • 23.
    • 24.
    • 25.
    • 26.
    • 27.
    • 28.
    • 29.
    • 30.
    • 31.
    • 32.
  3. 代码分析是基于ListPage.ets组件

      build() {
        Column() {
          if (!this.isFail && this.newsModel.pageState === Const.PageState.SUCCESS) {
            // 请求网络成功,状态为成功时,显示新闻列表布局
            this.newsLayout()
          } else if (!this.isFail && this.newsModel.pageState === Const.PageState.LOADING) {
            // 请求网络成功,状态为正在加载中时,显示加载布局
            this.loadingLayout()
          } else {
            // 请求网络失败,显示失败布局
            this.failLayout()
          }
        }
        .justifyContent(FlexAlign.Center)
      }
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
    • 11.
    • 12.
    • 13.
    • 14.
    • 15.
  4. 失败布局

      @Builder
      failLayout() {
        Image($r('app.media.none'))
          .height(120)
          .width(120)
        Text('网络加载失败')
          .opacity(0.6)
          .fontSize(16)
          .fontColor('#182431')
          .margin({ top: 12 })
      }
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
    • 11.
  5. 刷新布局

      @Builder
      refreshLayout(refresh: Refresh) {
        Row() {
          Image(refresh.imageSrc)
            .width(Const.REFRESH_IMAGE_WIDTH)
            .height(Const.REFRESH_IMAGE_HEIGHT)
    
          Text(refresh.textValue)
            .margin({
              left: Const.REFRESH_TEXT_MARGIN_LEFT,
              bottom: Const.REFRESH_TEXT_MARGIN_BOTTOM
            })
            .fontSize(Const.REFRESH_TEXT_FONT_SIZE)
            .textAlign(TextAlign.Center)
        }
        .clip(true)
        .width(Const.FULL_WIDTH)
        .justifyContent(FlexAlign.Center)
        .height(refresh.heightValue)
      }
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
    • 11.
    • 12.
    • 13.
    • 14.
    • 15.
    • 16.
    • 17.
    • 18.
    • 19.
    • 20.
  6. 新闻列表布局

      @Builder
      loadingLayout() {
        // 刷新布局
        this.refreshLayout({
          isVisible: true,
          imageSrc: $r('app.media.ic_pull_up_load'),
          textValue: '加载中...',
          heightValue: this.newsModel.pullDownRefreshHeight,
        })
      }
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
  7. 新闻列表布局

      @Builder
      refreshBuilder() {
        // 刷新布局
        this.refreshLayout({
          isVisible: true,
          imageSrc: this.newsModel.pullDownRefreshImage,
          textValue: this.newsModel.pullDownRefreshText,
          heightValue: this.newsModel.pullDownRefreshHeight,
        })
      }
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
      @Builder
      newsLayout() {
        // 新闻列表布局使用刷新组件
        Refresh({ refreshing: $$this.isRefresh, builder: this.refreshBuilder }) {
          List() {
            ForEach(this.newsModel.newsData, (item: ResData) => {
              ListItem() {
                NewsItem({ itemData: item })
              }
              .width(Const.FULL_WIDTH)
              .backgroundColor(Color.White)
              .margin({ bottom: Const.LIST_ITEM_MARGIN_TOP })
              .borderRadius(Const.LIST_ITEM_BORDER_RADIUS)
              .align(Alignment.Top)
            }, (item: ResData, index?: number) => `${item.id}${index}`)
    
            ListItem() {
              if (this.newsModel.hasMore) {
                this.loadMore({
                  isVisible: true,
                  imageSrc: this.newsModel.pullUpLoadImage,
                  textValue: this.newsModel.pullUpLoadText,
                  heightValue: this.newsModel.pullUpLoadHeight,
                })
              } else {
                this.noMore()
              }
            }
          }
          .width(Const.FULL_WIDTH)
          .height(Const.FULL_HEIGHT)
          .padding({ left: Const.LIST_MARGIN_LEFT, right: Const.LIST_MARGIN_RIGHT })
          .backgroundColor($r('app.color.listColor'))
          .edgeEffect(EdgeEffect.None)
          .onScrollIndex((start: number, end: number) => {
            // 监听当前列表的第一个索引。
            this.newsModel.startIndex = start;
            this.newsModel.endIndex = end;
            // 加载更新数据
            loadMore(this.newsModel, getUrl(this.context));
          })
        }
        .width(Const.FULL_WIDTH)
        .onStateChange((state: RefreshStatus) => {
          // 刷新状态
          refreshState(this.newsModel, state);
        })
        .onRefreshing(() => {
          // 刷新请求
          refreshing(this.newsModel, getUrl(this.context)).finally(() => {
            this.isRefresh = false;
          });
        })
        .refreshOffset(Const.CUSTOM_LAYOUT_HEIGHT)
        .pullToRefresh(true)
      }
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
    • 11.
    • 12.
    • 13.
    • 14.
    • 15.
    • 16.
    • 17.
    • 18.
    • 19.
    • 20.
    • 21.
    • 22.
    • 23.
    • 24.
    • 25.
    • 26.
    • 27.
    • 28.
    • 29.
    • 30.
    • 31.
    • 32.
    • 33.
    • 34.
    • 35.
    • 36.
    • 37.
    • 38.
    • 39.
    • 40.
    • 41.
    • 42.
    • 43.
    • 44.
    • 45.
    • 46.
    • 47.
    • 48.
    • 49.
    • 50.
    • 51.
    • 52.
    • 53.
    • 54.
    • 55.
    • 56.
  8. 加载更多数据

      @Builder
      loadMore(refresh: Refresh) {
        Column() {
          if (refresh.isVisible) {
            this.refreshLayout(refresh)
          }
        }
      }
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
  9. 没有更新数据

      @Builder
      noMore() {
        Row() {
          Text($r('app.string.prompt_message'))
            .margin({ left: Const.NO_MORE_NORMAL_PADDING })
            .fontSize(Const.NO_MORE_TITLE_FONT)
            .textAlign(TextAlign.Center)
        }
        .width(Const.FULL_WIDTH)
        .margin({ bottom: Const.TAB_HEIGHT })
        .justifyContent(FlexAlign.Center)
        .height(Const.CUSTOM_LAYOUT_HEIGHT)
      }
    
    • 1.
    • 2.
    • 3.
    • 4.
    • 5.
    • 6.
    • 7.
    • 8.
    • 9.
    • 10.
    • 11.
    • 12.
    • 13.
点击Clear按钮,清除本地缓存数据,下次冷启动会通过网络获取数据。
DataCache.getInstance(getContext(this)).clear().then(() => {
   promptAction.showToast({
        message: $r('app.string.clear_success'),
        duration: Const.ANIMATION_DURATION
   });
});
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

总结

      DataCache库是一个高性能的本地缓存解决方案,能够显著提升应用冷启动速度和数据加载效率。通过简单的API接口,开发者可以方便地集成缓存功能,实现数据的快速存储和获取。DataCache还支持缓存失效控制和缓存淘汰机制,帮助开发者有效管理缓存数据,避免缓存污染和内存泄漏。在实际项目中,DataCache库适用于需要频繁读写数据、对性能要求较高的场景,如首页数据缓存、图片预加载等,能够显著提升用户体验和应用性能。

相关权限

  1. ohos.permission.INTERNET
  2. ohos.permission.GET_NETWORK_INFO

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
分类
标签
已于2025-3-30 13:37:07修改
2
收藏 1
回复
举报
2
1
回复
    相关推荐