接入指南

第三方快应用如何接入 SimpleFetch 协议,通过手机桥接发起 HTTP / SSE 请求

本指南面向快应用开发者,将指导你把自己的快应用接入 SimpleFetch 以实现联网。

通信协议的字段级定义见 通信协议,本文仅介绍如何接入 SimpleFetch。

它是怎么工作的

你的快应用  ──互联消息(SF_*)──▶  SimpleFetch 插件  ──真实 HTTP──▶  目标服务器
     ▲                                                                    │
     └──────────────────────── 响应 / SSE 事件 ◀──────────────────────────┘
  • 快应用与手机端通过 AstroBox 的**互联通道(interconnect)**收发 JSON 消息;
  • 握手成功后,快应用进入「桥接模式」,把 fetch 请求改走互联通道;
  • SimpleFetch 插件在手机端执行真实 HTTP 请求,并把结果按协议回传。

你不需要关心手机端如何发请求——只要正确收发 SF_* 消息即可。

正在观望?

如果你还没开始开发快应用,ResonaUI 是一个很好的起点:它不仅集成了 SimpleFetch,自带的 AI 提示词也能降低 AI 辅助开发的门槛、提高应用质量。

正式接入

如果你已经有自己的快应用,接入 SimpleFetch 并不难。针对以下两种情况,我们提供了对应的教程:

  • 已有通信协议实现:只需为 SimpleFetch 开一个分支处理其消息;
  • 还没有互联通信部分:直接复用 ResonaUI 的互联模块即可。

已有通信协议接入

如果你已经有自己的通信协议实现,可以为 SimpleFetch 单独开一个分支来处理它的消息。

获取 simpleFetch.js 源码

任选一种方式:

  1. 直接从 GitHub 下载:simpleFetch.js
  2. 通过 git 克隆后提取:
git clone https://github.com/B4QAQ/ResonaUI

然后在 src/common/js/simpleFetch.js 找到该文件。

根据实际情况修改依赖

simpleFetch.js 依赖 ResonaUI 的 sendInterconnectData 与 getDeviceInfo,二者都有替换方案。

sendInterconnectData —— 对接你的数据发送函数

/**
 * 发送数据到手机 App
 * @param {Object} data 要发送的数据对象
 * @return {Promise<boolean>} 发送结果
 */
export function sendInterconnectData(data) {
  return new Promise((resolve) => {
    if (!global.interconnectInstance) {
      console.log('[设备通信] 发送失败: 通信实例未初始化')
      global.uiAdm.MessageBox('发送数据失败:未初始化')
      resolve(false)
      return
    }

    global.interconnectInstance.send({
      data: data,
      success: () => {
        console.log('[设备通信] 发送成功:', JSON.stringify(data))
        resolve(true)
      },
      fail: (data, code) => {
        console.log('[设备通信] 发送失败, code:', code, 'msg:', data)
        global.uiAdm.MessageBox(`[E${code}]发送数据失败:` + data)
        resolve(false)
      }
    })
  })
}

将上文的 global.interconnectInstance 替换为你自己通信协议的实例即可。

getDeviceInfo —— 网络状态刷新

/**
 * 获取网络信息
 * @return {Promise<Object>} 网络信息对象
 */
export async function getDeviceInfo() {
  const info = {}
  try {
    await new Promise((resolve) => {
      network.getType({
        success: (data) => {
          if (global.NetworkStatus === 'bridge') {
            info.type = 'bridge'
            global.fetchAva = true
          } else {
            Object.assign(info, data)
          }
          resolve()
        },
        fail: () => resolve()
      })
    })

    Object.assign(info, app.getInfo())
    if (global.NetworkStatus !== 'bridge') {
      global.fetchAva = !!app.canIUse('@system.fetch')
    }
    global.NetworkStatus = info.type
  } catch (e) {
    console.error('[X] 获取设备信息失败:', e)
  }

  return info
}

此处对 ResonaUI 的实现做了简写。理论上,getDeviceInfo 只要能正确给 global.NetworkStatus 与 global.fetchAva 赋值即可。

uploadFile —— 文件保存

自SimpleFetch1.2后(JS版本),SimpleFetch集成了文件下载功能,您可以参考以下代码为您的项目集成文件下载功能或直接将其删除

export async function uploadFile(data){
  if (!data?.uri) throw new Error('缺少 uri')
  if (!data?.data) throw new Error('缺少 data')
  const bytes = decodeBase64(data.data)
  await ensureDir(data.uri)
  await new Promise((resolve, reject) => {
    file.writeArrayBuffer({
      uri: data.uri,
      buffer: bytes,
      append: data.append,
      position: data.position,
      success: resolve,
      fail: (d, code) => reject(`写入失败: ${code}`)
    })
  })
}

十分感谢雪松大佬对于文件下载部分的指导

进阶移植:关于`global.uiAdm.MessageBox

代码里出现的 global.uiAdm.MessageBox 只是对 prompt.showToast 的简单封装,具体实现如下,按需替换成你自己的提示组件即可:

/**
 * 显示提示框
 */
export function MessageBox(text = '未指定文本', time = 1500) {
  prompt.showToast({ message: text, duration: time })
}

在实际请求中使用

完成上面两步后,就可以在你的 fetch 封装里使用 SimpleFetch 了:

import fetch from '@system.fetch'
import * as simpleFetch from './simpleFetch.js'

/**
 * 发送网络请求
 * @param {string} url 请求地址
 * @return {Object} 请求结果
 */
export async function sendFetch(url) {
  // 检查网络状态
  if (global.NetworkStatus === 'none') {
    console.log('[X] 无网络连接')
    return { status: 503, result: '无网络连接' }
  }

  const requestConfig = { timeout: 10000 }
  requestConfig.url = encodeURI(url)
  requestConfig.method = 'GET'

  // 桥接模式走 SimpleFetch,否则走原生 fetch
  const fetcher = global.NetworkStatus === 'bridge' ? simpleFetch : fetch

  try {
    const response = await fetcher.fetch(requestConfig)
    return response.data.data
  } catch (e) {
    console.log('[X] 请求异常:', e)
  }
}

发起下载同理,此处略

设计上,SimpleFetch 的 fetch 函数返回值与 @system.fetch 完全一致,因此可以直接替换系统 fetch 使用。


无通信协议接入

如果你还没有通信协议,接入 SimpleFetch 更加简单——直接复用 ResonaUI 的互联模块即可。

克隆 ResonaUI

git clone https://github.com/B4QAQ/ResonaUI

粘贴相关文件

在 ResonaUI 中找到 simpleFetch.js 和 interconnect.js,处理好目录关系(图省事可以把整个目录结构搬过来)。

移植依赖函数

实现网络状态刷新 getDeviceInfo:

/**
 * 获取网络信息
 * @return {Promise<Object>} 网络信息对象
 */
export async function getDeviceInfo() {
  const info = {}
  try {
    await new Promise((resolve) => {
      network.getType({
        success: (data) => {
          if (global.NetworkStatus === 'bridge') {
            info.type = 'bridge'
            global.fetchAva = true
          } else {
            Object.assign(info, data)
          }
          resolve()
        },
        fail: () => resolve()
      })
    })

    Object.assign(info, app.getInfo())
    if (global.NetworkStatus !== 'bridge') {
      global.fetchAva = !!app.canIUse('@system.fetch')
    }
    global.NetworkStatus = info.type
  } catch (e) {
    console.error('[X] 获取设备信息失败:', e)
  }

  return info
}

此处对 ResonaUI 的实现做了简写。理论上,getDeviceInfo 只要能正确给 global.NetworkStatus 与 global.fetchAva 赋值即可。

进阶移植:关于global.uiAdm.MessageBox

文件中同样有大量 global.uiAdm.MessageBox,它只是对 prompt.showToast 的简单封装:

/**
 * 显示提示框
 */
export function MessageBox(text = '未指定文本', time = 1500) {
  prompt.showToast({ message: text, duration: time })
}

在 app.ux 中初始化

在 app.ux 中做以下初始化:

import { initInterconnect } from './common/js/interconnect.js'

export default {
  async onCreate() {
    initInterconnect()
  },
}

在实际代码中使用

import fetch from '@system.fetch'
import * as simpleFetch from './simpleFetch.js'

/**
 * 发送网络请求
 * @param {string} url 请求地址
 * @return {Object} 请求结果
 */
export async function sendFetch(url) {
  // 检查网络状态
  if (global.NetworkStatus === 'none') {
    console.log('[X] 无网络连接')
    return { status: 503, result: '无网络连接' }
  }

  const requestConfig = { timeout: 10000 }
  requestConfig.url = encodeURI(url)
  requestConfig.method = 'GET'

  // 桥接模式走 SimpleFetch,否则走原生 fetch
  const fetcher = global.NetworkStatus === 'bridge' ? simpleFetch : fetch

  try {
    const response = await fetcher.fetch(requestConfig)
    return response.data.data
  } catch (e) {
    console.log('[X] 请求异常:', e)
  }
}

发起下载同理,此处略

设计上,SimpleFetch 的 fetch 函数返回值与 @system.fetch 完全一致,因此可以直接替换系统 fetch 使用。


接入后检查清单

  • sendInterconnectData 已对接你自己的发送函数(或已复用 interconnect.js);
  • getDeviceInfo 能正确设置 global.NetworkStatus 与 global.fetchAva;
  • app.ux 启动时调用了 initInterconnect()(从零接入时);
  • 桥接模式下请求确实走 simpleFetch.fetch,非桥接时回退原生 @system.fetch;
  • 握手机制生效:心跳每 10 秒发送,超时能断开并重连。

握手、心跳、分片、SSE 等字段细节与完整时序见 通信协议。

大纲