HarmonyOS鸿蒙Next中rcp网络请求session复用方案

发布于 1周前 作者 caililin 来自 鸿蒙OS

HarmonyOS鸿蒙Next中rcp网络请求session复用方案

rcp 网络请求 session复用方案

背景介绍

官方的只提到了rcp的session可以复用,但如何复用没有相关的介绍,论坛里搜索了下,也没有相关的方案

于是自己写了一个池化的方案,希望各位大佬们指正,提出优化方案

需求

  1. 自己项目的项目需要获取网络图片,需要特定的请求头进行获取,官方的Image组件无法定制请求头相关参数,满足不了。
  2. 第三方组件 ImageKnife (看了代码,底层试用的是http模块) 可以定制请求头,由于项目采用V2版本状态控制进行开发,ImageKnife 对v2的支持还不完善。

问题和方案代码

主要用于网络图片下载,基于自己对池化技术的理解,用拦截器实现了缓存功能。 本来打算加入异步锁来避免公共数据的并发操作,但假如之后感觉速度下降了,等之后确实需要加入在加吧。

存在的问题

  1. 在懒加载列表中控制台会报一堆如下的error,大概跟回调函数有关吧,应该是由于组件在懒加载时被回收了,导致回调调用出错,希望哪位帮忙看下这是因为什么问题导致的
12-25 16:37:27.485   2491-2491     C03f01/NAPI                     com.xxx.xxxx         E     [(ark_crash_holder.cpp:68)(UpdateCallbackPtr)] Failed to update callback info: 140070987694160
  1. 任务队列使用LinkedList进行实现,但是不知道remove是根据什么进行判断相等的,每次调用removeTask都是方法返回的都是false。本人是java方向的,java可以重写equals方法和hash实现,在ArkTs里不知是如何判断的,希望各位指教

方案代码

import { rcp } from "@kit.RemoteCommunicationKit";
import { AppConfig } from "../AppConfig";
import { TokenInterceptor } from "../interceptor/TokenInterceptor";
import { LinkedList, url } from "@kit.ArkTS";
import { image } from "@kit.ImageKit";
import { PixelCache } from "../cache/PixelMapCache";
import { BusinessError, zlib } from "@kit.BasicServicesKit";
import { fileIo } from "@kit.CoreFileKit";

/**
 * 定义下载任务接口
 */
export interface ImageTask {
  url: string; // 图片URL
  callback: (pic: image.PixelMap) => void; // 下载完成后的回调函数
}

/**
 * 下载工具
 * session最多可创建16个
 */
export class DownloadUtil {
  // 最大并发连接数
  private static readonly maxConnections: number = 10;
  // 会话池大小
  private static readonly poolSize: number = 2;
  // 当前使用的连接数
  private static inUseConnections: number = 0;
  // 会话池
  private static sessionPool: Array<rcp.Session> = [];
  // 等待队列
  private static waitList: LinkedList<ImageTask> = new LinkedList();

  /**
   * 初始化请求
   */
  public static init(): void {
    // 预先创建一定数量的session放入池中
    for (let i = 0; i < DownloadUtil.poolSize; i++) {
      DownloadUtil.sessionPool.push(DownloadUtil.createSession());
    }
  }

  /**
   * 创建一个新的Session
   * @returns {rcp.Session} 新的Session实例
   */
  private static createSession(): rcp.Session {
    // 创建Session并配置基础地址、请求头、拦截器等
    return rcp.createSession({
      baseAddress: AppConfig.BASE_API_URL,
      headers: {
        'User-Agent': AppConfig.USER_AGENT,
        "Accept-language": AppConfig.ACCEPT_LANGUAGE,
        'App-OS': AppConfig.APP_OS,
        'App-OS-Version': AppConfig.APP_OS_VERSION,
        'App-Version': AppConfig.APP_VERSION,
        'Host': AppConfig.HOST,
        "Content-type": 'application/x-www-form-urlencoded;charset=UTF-8',
        'Referer': AppConfig.BASE_API_URL,
      },
      interceptors: [new TokenInterceptor()],
      requestConfiguration: {
        processing: {
          validateResponse: (response): boolean => response.statusCode === 200
        },
        dns: {
          dnsOverHttps: {
            url: AppConfig.DOH
          }
        },
        security: {
          remoteValidation: (context) => {
            console.info('context', context);
            return true;
          }
        }
      }
    });
  }

  /**
   * 获取一个Session实例
   * @returns {rcp.Session | null} Session实例或null
   */
  private static getSession(): rcp.Session | null {
    // 优先从会话池中获取Session
    if (DownloadUtil.sessionPool.length > 0) {
      return DownloadUtil.sessionPool.shift()!;
    // 如果当前使用的连接数未达到最大限制,则创建新的Session
    } else if (DownloadUtil.inUseConnections < DownloadUtil.maxConnections) {
      DownloadUtil.inUseConnections++;
      return DownloadUtil.createSession();
    } else {
      // 达到最大限制则返回null
      return null;
    }
  }

  /**
   * 销毁所有Session实例
   */
  public static destroy(): void {
    // 遍历会话池,取消并关闭所有Session
    DownloadUtil.sessionPool.forEach(session => {
      session.cancel();
      session.close();
    });
    PixelCache.clear();
    DownloadUtil.sessionPool = [];
    DownloadUtil.inUseConnections = 0;
  }

  /**
   * 下载图片
   * @param {ImageTask} task 图片下载任务
   */
  public static async downloadImage(task: ImageTask): Promise<void> {
    let cache = PixelCache.getCache(task.url);
    if (cache) {
      task.callback?(cache)
      return
    }

    const session = DownloadUtil.getSession();
    if (session) {
      try {
        // 发起GET请求下载图片
        const response = await session.get(task.url);
        // 创建PixelMap并调用回调函数
        const imageSource = image.createImageSource(response.body)
        const pixelMap = imageSource.createPixelMapSync()
        PixelCache.setCache(task.url, pixelMap)
        task.callback?(pixelMap);
      } catch (e) {
        console.error(`err: err code is ${e.code}, err message is ${JSON.stringify(e)}`);
      } finally {
        // 释放Session并处理下一个任务
        DownloadUtil.releaseSession(session);
        DownloadUtil.processNextTask();
      }
    } else {
      // 如果没有获取到Session,将任务加入等待队列
      DownloadUtil.waitList.add(task);
    }
  }

  /**
   * 下载ugoira文件
   *
   * 本函数旨在从给定的源URL下载ugoira(动画插图)文件,并将其保存到临时目录中,然后解压缩到缓存目录
   * 它首先解析URL以获取文件名和目录路径,然后检查并创建必要的目录结构如果文件尚未存在,
   * 它将下载文件并将其解压缩到指定的缓存目录中
   *
   * @param src ugoira文件的URL如果为空或无效,则函数将直接返回
   * @param callback 下载完成后的回调函数,参数为true表示下载成功,false表示下载失败
   */
  public static downloadUgoiraFile(src:string,callback?:(success:boolean)=>void){
    // 检查src参数是否为空或无效
    if (!src || src.length === 0) {
      callback?(false)
      return
    }
    try {
      // 解析URL以获取源信息
      const source = url.URL.parseURL(src)
      // 提取文件名
      const fileName = src.split('/').pop()
      // 提取文件目录名
      const fileDir = fileName?.split('.').shift()
      // 定义缓存目录
      const cacheDir = `${getContext().cacheDir}/ugoira/${fileDir}`
      if (fileIo.accessSync(cacheDir, fileIo.AccessModeType.EXIST)){
        callback?(true)
        return
      }
      // 定义临时文件目录
      const tempDir = `${getContext().tempDir}/ugoira`
      // 检查并创建临时目录
      if (!fileIo.accessSync(tempDir, fileIo.AccessModeType.EXIST)) {
        fileIo.mkdirSync(tempDir,true)
      }
      // 定义文件路径
      const filePath = `${tempDir}/${fileName}`
      // 打开或创建文件
      const file = fileIo.openSync(filePath,fileIo.OpenMode.CREATE|fileIo.OpenMode.READ_WRITE)
      // 获取下载会话
      const session = DownloadUtil.getSession()
      // 下载文件到本地
      session?.downloadToFile(source,{
        kind:'file',
        file:file.fd,
        keepLocal:true
      }).then(response=>{
        console.log('response',response)
        // 检查并创建缓存目录
        if (!fileIo.accessSync(cacheDir, fileIo.AccessModeType.EXIST)) {
          fileIo.mkdirSync(cacheDir,true)
        }
        // 解压缩文件到缓存目录
        zlib.decompressFile(filePath,cacheDir).then(()=>{
          callback?(true)
          console.log('decompressFile success')
        }).catch((e:BusinessError)=>{
          console.log('err',e)
          callback?(false)
        }).finally(()=>{
          // 关闭文件并删除临时文件
          fileIo.close(file.fd).finally(()=>fileIo.unlink(filePath))
        })
      }).catch((e:BusinessError)=>{
        callback?(false)
        console.log('err',e)
      })
    } catch (e) {
      callback?(false)
      console.log('err',e)
    }
  }

  /**
   * 释放Session
   * @param {rcp.Session} session 要释放的Session实例
   */
  private static releaseSession(session: rcp.Session): void {
    // 如果会话池未满,则将Session放回池中
    if (DownloadUtil.sessionPool.length < DownloadUtil.poolSize) {
      DownloadUtil.sessionPool.push(session);
    } else {
      // 否则关闭Session并减少当前使用的连接数
      session.close();
      DownloadUtil.inUseConnections--;
    }
  }

  /**
   * 处理下一个等待中的任务
   */
  private static processNextTask(): void {
    // 从等待队列中获取下一个任务并下载图片
    const nextTask = DownloadUtil.waitList.removeFirst();
    if (nextTask) {
      DownloadUtil.downloadImage(nextTask);
    }
  }

  public static removeTask(task:ImageTask){
    const success = DownloadUtil.waitList.remove(task);
    console.log('removeTask',success)
  }
}

更多关于HarmonyOS鸿蒙Next中rcp网络请求session复用方案的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

在HarmonyOS鸿蒙Next中,rcp网络请求的session复用方案主要依赖于HttpSession对象的管理和复用机制。HttpSession是用于维护客户端与服务器之间会话状态的对象,通过复用HttpSession可以减少网络请求的开销,提升性能。

在鸿蒙Next中,HttpSession的复用可以通过以下方式实现:

  1. Session管理:通过HttpSessionManager来管理HttpSession的生命周期。HttpSessionManager负责创建、获取和销毁HttpSession对象。在同一个会话中,多个请求可以共享同一个HttpSession对象。

  2. Session ID传递:客户端在首次请求时,服务器会生成一个唯一的Session ID,并将其通过Set-Cookie头返回给客户端。客户端在后续请求中通过Cookie头将Session ID传递给服务器,服务器根据Session ID找到对应的HttpSession对象,实现会话的复用。

  3. Session超时处理HttpSession对象有一个超时时间,超过该时间未使用的HttpSession会被自动销毁。可以通过HttpSession.setMaxInactiveInterval()方法设置超时时间,确保会话的合理复用和资源释放。

  4. 线程安全HttpSession对象是线程安全的,多个请求可以同时访问同一个HttpSession对象,但需要注意对共享数据的同步处理,避免并发问题。

  5. Session存储HttpSession对象可以存储在内存中,也可以持久化到外部存储中。在鸿蒙Next中,默认情况下HttpSession存储在内存中,但可以通过配置将其持久化到文件或数据库中,以支持分布式环境下的会话共享。

通过以上机制,鸿蒙Next中的rcp网络请求可以实现HttpSession的复用,减少网络请求的开销,提升系统的性能和可扩展性。

更多关于HarmonyOS鸿蒙Next中rcp网络请求session复用方案的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS鸿蒙Next中,实现RCP网络请求的Session复用,可以通过以下方案:

  1. Session管理:使用HttpSession对象管理会话,确保同一会话的请求可以复用Session。
  2. Cookie处理:在请求头中携带Session ID的Cookie,服务器通过Cookie识别并复用Session。
  3. 连接池:利用HttpURLConnectionOkHttp的连接池机制,复用TCP连接,减少建立连接的开销。
  4. 单例模式:设计单例的HTTP客户端,确保所有请求通过同一客户端发出,共享Session和连接池。

通过这些方法,可以有效提升网络请求的效率和性能。

回到顶部
AI 助手
你好,我是IT营的 AI 助手
您可以尝试点击下方的快捷入口开启体验!