强大的鸿蒙HarmonyOS网络调试工具PageSpy 介绍及使用

PageSpy 是一款兼容 Web、小程序、React Native、鸿蒙 App 等平台项目的开源调试平台。通过封装原生 API,它能将调用原生方法时的参数进行过滤和转化,并整理成特定格式的消息,供调试端消费。调试端接收到这些消息后,会以类似本地控制台的功能界面呈现出来。

当遇到无法在本地使用控制台调试的情况时,PageSpy 就显得尤为强大和便捷。该开源神器支持Web、小程序和HarmonyOS等各种平台。相当于你可以远程查看设备的日志,即便设备选在千里之外,也可以抓取它的日志和查看报警报错信息,岂不美哉?

接下来,我将通过几个典型场景来具体了解 PageSpy 的优势。

PageSpy For HarmonyOS介绍

@huolala/page-spy-harmony 是由货拉拉开源项目 PageSpy 在鸿蒙端实现的 SDK。 PageSpy 用于本地开发调试、远程调试用户设备,支持调试 WEB / 小程序 / 鸿蒙等平台,更多细节内容介绍请参考 主仓库。

page-spy-harmony 三方库地址
https://ohpm.openharmony.cn/#/cn/detail/@huolala%2Fpage-spy-harmony

常见调试场景
  1. 本地调试 H5 与 Webview 应用:传统 H5 信息查看面板在移动端存在屏幕尺寸限制、操作不便、显示效果差、信息被截断等问题。PageSpy 则解决了这些问题,提供了更为直观和高效的方式。

  2. 远程办公与跨地域协作:传统沟通方式如邮件、电话或视频会议,不仅效率低下,且信息传递不完整,容易引发误解和误判。PageSpy 可以帮助开发者通过一个网页界面实时查看调试信息,提高了远程调试的效率。

  3. 用户终端出现白屏问题:面对用户终端异常问题,通常依赖数据监控和日志分析定位问题,耗时且需要深入了解业务和技术实现。PageSpy 的介入可以简化这一流程,帮助快速定位问题。

搭建后台服务环境

在引入 鸿蒙客户端SDK 前,需先部署 PageSpy 服务,因为该方案是需要在网页上查看调试信息的,需要部署个后台服务。PageSpy 后台服务提供多种部署方案,可以根据自身情况选择任意一种进行部署:

方式一: 使用 Node 部署:通过 yarn 或 npm 安装。

bash 复制代码
 yarn global add @huolala-tech/page-spy-api@latest  # 如果你使用 npm
 npm install -g @huolala-tech/page-spy-api@latest

安装完成后,可以在命令行中执行 page-spy-api 启动服务。启动后,打开浏览器访问 http://localhost:6752 体验,测试完成后部署到服务器上。

方式二:使用 Docker 部署

bash 复制代码
    docker run -d --restart=always -v ./log:/app/log -v ./data:/app/data -p 6752:6752 --name="pageSpy" ghcr.io/huolalatech/page-spy-web:latest

启动完成后,打开浏览器访问 http://localhost:6752 体验,测试完成后部署到服务器上。推荐使用docker方式在服务端部署,简单快捷,一行命令搞定。

这里我选择了使用 docker进行部署。安装部署完成后服务自动启动,端口号是6752.浏览器打开 http://yourremoteip:6752 就可以体验了。

服务部署完成后,打开对应网页,点击web页面左上角的开始调试按钮。如果有客户端接入进来了,则能看到以下页面:

鸿蒙应用集成使用方法

鸿蒙应用的集成方法很简单,只需几行代码,无侵入的完成集成。

  1. 安装依赖 :由于鸿蒙应用商店最低要求使用 API 12,建议使用 API 17。我们的应用最低也是在 API 17,可以使用基于 API 16 版本开发的 @huolala/page-spy-harmony@2.1.x 版本。

    bash 复制代码
    ohpm install @huolala/page-spy-harmony

    这将安装 ^2.1.0 版本。

  2. 连接服务:首先创建一个 axios 的单例对象并导出,项目中所有网络请求均使用该对象。

    javascript 复制代码
    import axios from '@ohos/axios';
    const axiosInstance = axios.create({
      baseURL: 'https://api.github.com',
      timeout: 1000,
    });
    export {axiosInstance};

    然后在 EntryAbility 的 onWindowStageCreate 生命周期方法中创建 PageSpy 对象。

    javascript 复制代码
    import { PageSpy } from '@huolala/page-spy-harmony';
    onWindowStageCreate(windowStage: window.WindowStage): void {
      new PageSpy({
        context: this.context,
        api: "192.168.1.25:6752",
        enableSSL: false,
        axios: axiosInstance
      })
      windowStage.loadContent('pages/Index', (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.如果是在自己的电脑上进行测试(服务端装在你自己的本地电脑上),需要手机和电脑连接同一个 Wi-Fi,api 就写电脑的 ip 地址 + 6752 端口号。本地部署没有 https 证书,所以 enableSSL 必须设置为 false。axios 为我们前面创建的 axios 单例对象。

2.如果服务是部署在云服务器上,则这里的ip需要填写你部署的PageSpy 的服务地址和端口。如果不是https 连接,则必须把enableSSL 设置为false.

3.当前PageSpy的HarmonyOS的sdk只支持axios客户端。如果嫌axios客户端的使用太繁琐,推荐坚果派的nutpi/axios三方库,该库极大简化了axios网络库的使用,且也是支持PageSpy。

nutpi/axios三方库地址: https://ohpm.openharmony.cn/#/cn/detail/@nutpi%2Faxios

测试连接:将应用运行到手机上,在 log 日志中看到下面两条日志:

bash 复制代码
 [PageSpy] [LOG] Device ID: f764
 [PageSpy] [LOG] Connect successful.

然后点击网页右上角的开始调试,选择在线实时即可开始调试。此时在新页面上可以看到我们的设备。

使用 PageSpy 调试

当环境搭建好后,可以通过点击调试按钮进入调试页面。可以看到左侧有输出、网络、存储、系统四个选项。

  • 输出:展示 console 打印的日志,不能展示 hilog 输出的日志。
  • 网络请求:PageSpy 将网络请求的详细信息保存下来,对远程协同办公和内部测试特别有用。
  • AppStorage 存储:PageSpy 可以实时查看 AppStorage 中保存的数据,无需手动刷新网页。
  • 系统:展示系统概览信息,如 Platform 和 User Agent。
生产环境部署

确认测试无误后,可以将 PageSpy 部署到生产环境中。PageSpy 服务端会自动读取当前运行目录下的 config.json 配置文件,支持配置运行端口、多实例部署、跨域配置、日志数据配置等。

版本兼容性
  • @huolala/page-spy-harmony@1.x:基于 API 9 版本开发。
  • @huolala/page-spy-harmony@2.0.x:基于 API 11 版本开发。
  • @huolala/page-spy-harmony@2.1.x:基于 API 16 版本开发。
能力概览
  • console 打印日志
  • 项目报错信息输出
  • 网络请求信息输出
  • 应用数据查看
开始正式使用

在引入 SDK 之前,请先部署 PageSpy 服务,部署方式参见上文或者可参考官方文档的部署教程。

部署成功后,在项目中引入 HAR SDK:

bash 复制代码
ohpm install @huolala/page-spy-harmony

然后在项目中引入 SDK 并实例化:

javascript 复制代码
import { PageSpy } from '@huolala/page-spy-harmony';
import axiosInstance from 'path/to/your/axios';

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage: window.WindowStage) {
    const $pageSpy = new PageSpy({
      context: this.context,
      api: '<your-pagespy-server-host>',
      enableSSL: true,
      axios: axiosInstance,
    });
  }
}

PageSpy 默认会在 DevEcho Studio 的 Log 面板中输出调试的连接信息;也可以使用 $pageSpy.showPanel(); 方法手动弹窗查看。

如果使用的是坚果派的nutpi/axios的三方库,则使用方式如下:

javascript 复制代码
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import PreferencesUtils from '../utils/PreferencesUtils';
import { PageSpy } from '@huolala/page-spy-harmony';
import { axiosClient } from '../utils/axiosClient';

export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
  }

  onDestroy(): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    // Main window is created, set main page for this ability
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
    new PageSpy({
      context: this.context,
      api: "120.27.146.2xx:6752", //你的PageSpy后台服务地址
      enableSSL: false,
      axios: axiosClient.instance,
    })
    windowStage.loadContent('pages/StartPage', (err) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
    });
  }
}

axiosClient从哪来?axiosClient是使用nutpi/axios三方库封装的一个网络请求工具类。

源码如下:

javascript 复制代码
//axiosClient.ets
import {
  AxiosError,
  AxiosHeaders,
  AxiosHttpRequest,
  AxiosRequestHeaders,
  FileUploadConfig,
  HttpPromise,
  UploadFile
} from '@nutpi/axios';
import { Log } from './logutil';
import { promptAction, router } from '@kit.ArkUI';
import { AppAuthManager } from './AppAuthManager';

function showToast(msg: string) {
  Log.debug(msg)
  promptAction.showToast({ message: msg })
}

function showLoadingDialog(msg: string) {
  Log.debug(msg)
  // promptAction.showToast({ message: msg })
}

function hideLoadingDialog() {

}

// 移除硬编码的token,改为动态获取
// AppStorage.SetOrCreate('Authorization', "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Miwibmlja25hbWUiOm51bGwsImF2YXRhciI6bnVsbCwiaWF0IjoxNzU0NjY2ODk3LCJleHAiOjE3NTQ3NTMyOTd9.32Kp1pCt-HIqKglOpLrRhOMdSjyonutEDkifQRaoZCo");

// 图片地址的前缀
export const IMAGE_BASE_URL = 'http://xxx.felin.xxxx.com'

/**
 * axios请求客户端创建
 */
const axiosClient = new AxiosHttpRequest({
  baseURL: "http://xxx.maotuai.com/api",
  timeout: 10 * 1000,
  checkResultCode: false,
  showLoading: true,
  headers: new AxiosHeaders({
    'Content-Type': 'application/json'
  }) as AxiosRequestHeaders,
  interceptorHooks: {
    requestInterceptor: async (config) => {
      // 在发送请求之前做一些处理,例如打印请求信息
      Log.debug('网络请求Request 请求方法:', `${config.method}`);
      Log.debug('网络请求Request 请求链接:', `${config.url}`);
      Log.debug('网络请求Request Params:', `\n${JSON.stringify(config.params)}`);
      Log.debug('网络请求Request Data:', `${JSON.stringify(config.data)}`);
      // 动态获取token
      const token = AppAuthManager.getToken();
      if (token) {
        config.headers['Authorization'] = 'Bearer ' + token;
      }

      axiosClient.config.showLoading = config.showLoading
      if (config.showLoading) {
        showLoadingDialog("加载中...")
      }
      if (config.checkLoginState) {
        // 检查登录状态
        if (!AppAuthManager.getToken()) {
          if (config.needJumpToLogin) {
            // 可以在这里跳转到登录页面
            // router.replaceUrl({ url: 'pages/LoginPage' });
          }
          throw new AxiosError("请登录")
        }
      }
      return config;
    },
    requestInterceptorCatch: (err) => {
      Log.error("网络请求RequestError", err.toString())
      if (axiosClient.config.showLoading) {
        hideLoadingDialog()
      }
      return err;
    },
    responseInterceptor: (response) => {
      //优先执行自己的请求响应拦截器,在执行通用请求request的
      if (axiosClient.config.showLoading) {
        hideLoadingDialog()
      }
      if (response.status === 200) {
        if (response.data.code != 0) {
          errorHandle(response)
          // 处理业务错误
          return Promise.reject(response)
        }
        return Promise.resolve(response);
      } else {
        return Promise.reject(response);
      }
    },
    responseInterceptorCatch: (error) => {
      if (axiosClient.config.showLoading) {
        hideLoadingDialog()
      }
      Log.error("网络请求响应异常", error.toString());
      errorHandler(error);
      return Promise.reject(error);
    },
  }
});

// 处理业务错误
function errorHandle(response) {
  switch (response.data.code) {
    case 401:
      AppAuthManager.clearToken();
      router.replaceUrl({ url: 'pages/LoginPage' });
      break;
    // 403 token过期
    // 登录过期对用户进行提示
    // 清除本地token和清空vuex中token对象
    // 跳转登录页面
    case 403:
      showToast("登录过期,请重新登录")
      AppAuthManager.clearToken();
      router.replaceUrl({ url: 'pages/LoginPage' });
      break;
    // 404请求不存在
    case 404:
      showToast("网络请求不存在")
      break;
    default:
      showToast(response.data.message)
      break
  }
}

function errorHandler(error: any) {
  if (error instanceof AxiosError) {
    //showToast(error.message)
  } else if (error != undefined && error.response != undefined && error.response.status) {
    switch (error.response.status) {
      // 401: 未登录
      // 未登录则跳转登录页面,并携带当前页面的路径
      // 在登录成功后返回当前页面,这一步需要在登录页操作。
      case 401:
        AppAuthManager.clearToken();
        router.replaceUrl({ url: 'pages/LoginPage' });
        break;
      // 403 token过期
      // 登录过期对用户进行提示
      // 清除本地token和清空vuex中token对象
      // 跳转登录页面
      case 403:
        showToast("登录过期,请重新登录")
        AppAuthManager.clearToken();
        router.replaceUrl({ url: 'pages/LoginPage' });
        break;
      // 404请求不存在
      case 404:
        showToast("网络请求不存在")
        break;

      // 其他错误,直接抛出错误提示
      default:
        if (error.response.data && error.response.data.message) {
          showToast(error.response.data.message)
        }
    }
  }
}

// 导出AppAuthManager供其他模块使用
export { AppAuthManager as AppAuthManager, axiosClient, HttpPromise, UploadFile, FileUploadConfig };

有了上述封装后,写网络接口变得超级简单了,如以下使用方式,清晰明了:

javascript 复制代码
/**
 * 照片管理相关api实现
 */

import { axiosClient, HttpPromise } from "../../../utils/axiosClient";
import { GetPhotoDetailResp } from "../bean/photo/getPhotoDetailResp";
import { GetPhotoListReq } from "../bean/photo/getPhotoListReq";
import { GetPhotoListResp } from "../bean/photo/getPhotoListResp";
import { PhotoAddReq } from "../bean/photo/photoAddReq";
import { PhotoAddResp } from "../bean/photo/photoAddResp";

// 创建照片
export const photoAdd = (req:PhotoAddReq): HttpPromise<PhotoAddResp> => axiosClient.post({url:'/app/photo/add',data: req});

// 获取照片列表
export const getPhotoList = (req:GetPhotoListReq): HttpPromise<GetPhotoListResp> => axiosClient.get({url:'/app/photo/',params: req});

// 获取照片详情
export const getPhotoDetail = (photoId:string): HttpPromise<GetPhotoDetailResp> => axiosClient.get({url:'/app/photo/'+photoId});
PageSpy 后台界面调试

进入调试后,可以清晰看到后台的网络接口访问,请求的数据和响应的数据清晰明了。

在左侧的输出选项页面中,则可以看到应用console.log打印的日志:

在左侧的存储选项中,则可以查看应用的AppStorage中存放的数据。

总结

通过以上介绍,可以看出 PageSpy 是一款强大的调试工具,适用于多种开发场景,特别是对于移动端开发调试提供了极大的便利性。推荐大家尝试并体验下这一网络调试神器。

相关推荐
ChinaDragon2 小时前
HarmonyOS:在NDK工程中使用预构建库
harmonyos
彬彬醤2 小时前
TikTok矩阵有哪些运营支撑方案?
大数据·网络·网络协议·tcp/ip·矩阵·udp·产品运营
楠枬2 小时前
DNS 域名解析
服务器·网络·网络协议
XMZH030422 小时前
网络编程;TCP多进程并发服务器;TCP多线程并发服务器;TCP网络聊天室和UDP网络聊天室;后面两个还没写出来;0911
服务器·网络·tcp/ip·udp·tcp
evo-master3 小时前
网络编程-HTTP
linux·网络
bug道一3 小时前
路由 下一跳 网关 两个不同网段的ip如何通过路由器互通
网络
189228048613 小时前
NW622NW623美光固态闪存NW624NW635
大数据·网络·数据库·人工智能·microsoft·性能优化
云飞云共享云桌面3 小时前
1台电脑10个画图设计用怎么实现
linux·运维·服务器·网络·数据库·自动化·电脑
心 一4 小时前
Web安全基石:深入理解与防御越权问题
网络·安全·web安全