强大的鸿蒙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
常见调试场景
-
本地调试 H5 与 Webview 应用:传统 H5 信息查看面板在移动端存在屏幕尺寸限制、操作不便、显示效果差、信息被截断等问题。PageSpy 则解决了这些问题,提供了更为直观和高效的方式。
-
远程办公与跨地域协作:传统沟通方式如邮件、电话或视频会议,不仅效率低下,且信息传递不完整,容易引发误解和误判。PageSpy 可以帮助开发者通过一个网页界面实时查看调试信息,提高了远程调试的效率。
-
用户终端出现白屏问题:面对用户终端异常问题,通常依赖数据监控和日志分析定位问题,耗时且需要深入了解业务和技术实现。PageSpy 的介入可以简化这一流程,帮助快速定位问题。
搭建后台服务环境
在引入 鸿蒙客户端SDK 前,需先部署 PageSpy 服务,因为该方案是需要在网页上查看调试信息的,需要部署个后台服务。PageSpy 后台服务提供多种部署方案,可以根据自身情况选择任意一种进行部署:
方式一: 使用 Node 部署:通过 yarn 或 npm 安装。
yarn global add @huolala-tech/page-spy-api@latest # 如果你使用 npmnpm install -g @huolala-tech/page-spy-api@latest
安装完成后,可以在命令行中执行 page-spy-api
启动服务。启动后,打开浏览器访问 http://localhost:6752
体验,测试完成后部署到服务器上。
方式二:使用 Docker 部署:
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页面左上角的开始调试按钮。如果有客户端接入进来了,则能看到以下页面:
鸿蒙应用集成使用方法
鸿蒙应用的集成方法很简单,只需几行代码,无侵入的完成集成。
-
安装依赖:由于鸿蒙应用商店最低要求使用 API 12,建议使用 API 17。我们的应用最低也是在 API 17,可以使用基于 API 16 版本开发的
@huolala/page-spy-harmony@2.1.x
版本。ohpm install @huolala/page-spy-harmony
这将安装
^2.1.0
版本。 -
连接服务:首先创建一个 axios 的单例对象并导出,项目中所有网络请求均使用该对象。
import axios from '@ohos/axios'; const axiosInstance = axios.create({baseURL: 'https://api.github.com',timeout: 1000, }); export {axiosInstance};
然后在 EntryAbility 的
onWindowStageCreate
生命周期方法中创建 PageSpy 对象。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 日志中看到下面两条日志:
[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:
ohpm install @huolala/page-spy-harmony
然后在项目中引入 SDK 并实例化:
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的三方库,则使用方式如下:
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 abilityhilog.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三方库封装的一个网络请求工具类。
源码如下:
//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)}`);// 动态获取tokenconst token = AppAuthManager.getToken();if (token) {config.headers['Authorization'] = 'Bearer ' + token;}axiosClient.config.showLoading = config.showLoadingif (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 };
有了上述封装后,写网络接口变得超级简单了,如以下使用方式,清晰明了:
/*** 照片管理相关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 是一款强大的调试工具,适用于多种开发场景,特别是对于移动端开发调试提供了极大的便利性。推荐大家尝试并体验下这一网络调试神器。