完成环境配置并成功导出 OpenHarmony 工程后,下一步是让游戏在模拟器或真机上稳定运行。本文主要围绕 DevEco Studio 调试展开,介绍设备文件与 HiLog 的查看方法、平台代码兼容、远程真机连接、Jenkins 构建注意事项,以及 Unity 日志转发到 HarmonyOS 原生层的实现。

本篇目标

经过上一篇的基础适配,项目已经可以导出 HAP,并在模拟器或真机中启动。不过,一些平台相关逻辑只会在运行阶段暴露问题,需要结合 DevEco Studio 日志与游戏内日志继续排查。

模拟器适合验证安装、启动和基础流程。因为目前在渲染层面上,鸿蒙模拟器没有对游戏有很好的支持。游戏的渲染效果、性能、设备能力及第三方 SDK 行为应以真机上的测试结果为准。

使用 DevEco Studio 定位问题

应用文件查看

Android 调试中可以通过设备文件管理器查看应用内的文件,HarmonyOS 应用不能,需要借助 DevEco Studio 的设备文件浏览功能访问和修改沙箱内的文件。

依次打开:View > Tool Windows > Device File Explorer。

然后在文件树中导航到:

/data/app/el2/100/base/<你的包名>/haps/entry/files/

应用文件替换

Device File Explorer 允许拉取文件,无法完成文件的推送。如果要传入某个文件,需要使用 HDC 命令。

以应用沙箱下的 res 目录为例:

hdc file send -b your.package.name your_file_path "./data/storage/el2/base/haps/entry/files/res"

如果有多台设备在连接,可以指定 ip 和端口号:
hdc -t <设备 IP>:<端口号> file send -b your.package.name your_file_path "./data/storage/el2/base/haps/entry/files/res"

DevEco Studio 日志查看

如果日志窗口中只有少量输出,先检查当前是否停留在 FaultLog。它主要用于查看故障信息,并不等同于 Android Studio 中的完整 Logcat。

在日志面板中切换到 HiLog,即可查看更完整的运行日志。排查时可优先按包名、进程或自定义日志域过滤,减少系统日志干扰。

快速拷贝代码

直接重新导出会覆盖手动修改过的 OpenHarmony 工程。在业务环境的一般做法是:保留一份已经完成适配的原生工程,每次导出后只同步代码、资源和原生库。

下面的批处理脚本会同步 Managed 和 libs 两个目录,便于在接入调试时快速实现代码逻辑的更新。

执行前应将 {导出目录} 与 {兼容目录} 替换为真实路径:

@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion

:: ============================================
:: 路径配置
:: ============================================
set "SOURCE_ROOT={导出目录}"
set "TARGET_ROOT={兼容目录}"

:: 源路径
set "SRC_MANAGED=%SOURCE_ROOT%\tuanjieLib\src\main\resources\rawfile\Data\Managed"
set "SRC_LIBS=%SOURCE_ROOT%\tuanjieLib\libs"

:: 目标路径
set "DST_MANAGED=%TARGET_ROOT%\tuanjieLib\src\main\resources\rawfile\Data\Managed"
set "DST_LIBS=%TARGET_ROOT%\tuanjieLib\libs"

:: ============================================
:: 检查源目录是否存在
:: ============================================
if not exist "%SRC_MANAGED%" (
echo [错误] 源目录不存在: %SRC_MANAGED%
exit /b 1
)

if not exist "%SRC_LIBS%" (
echo [错误] 源目录不存在: %SRC_LIBS%
exit /b 1
)

echo ============================================
echo 开始拷贝团结引擎鸿蒙工程文件
echo ============================================
echo.

:: ============================================
:: 1. 拷贝 Managed 目录(C# 编译产物)
:: ============================================
echo [1/2] 正在拷贝 Managed 目录...
echo 源: %SRC_MANAGED%
echo 目标: %DST_MANAGED%

if not exist "%DST_MANAGED%" mkdir "%DST_MANAGED%"

robocopy "%SRC_MANAGED%" "%DST_MANAGED%" *.* /E /MIR /R:3 /W:2 /NP /NDL /NFL
if %errorlevel% geq 8 (
echo [失败] Managed 目录拷贝出错,错误码: %errorlevel%
exit /b 1
) else (
echo [成功] Managed 目录拷贝完成
)

echo.

:: ============================================
:: 2. 拷贝 libs 目录(SO 库 / 依赖库)
:: ============================================
echo [2/2] 正在拷贝 libs 目录...
echo 源: %SRC_LIBS%
echo 目标: %DST_LIBS%

if not exist "%DST_LIBS%" mkdir "%DST_LIBS%"

robocopy "%SRC_LIBS%" "%DST_LIBS%" *.* /E /MIR /R:3 /W:2 /NP /NDL /NFL
if %errorlevel% geq 8 (
echo [失败] libs 目录拷贝出错,错误码: %errorlevel%
exit /b 1
) else (
echo [成功] libs 目录拷贝完成
)

echo.
echo ============================================
echo 所有文件拷贝完成!
echo ============================================

pause

处理平台相关代码

除了上一篇提到的 #if UNITY_ANDROID 条件编译,项目中还可能存在运行时的平台判断,自定义的平台枚举、路径规则和 SDK 处理逻辑。这类逻辑不一定触发编译错误,通常需要结合运行表现和日志定位。

RuntimePlatform

RuntimePlatform 是 Unity 用于标识当前运行平台的枚举。迁移时应全局搜索它,检查原有 Android 分支是否需要补充 OpenHarmony 处理逻辑。

除此之外,还会有以下区分平台的内容:

  • UNITY_ANDROID、UNITY_IOS 等平台宏;
  • RuntimePlatform 和项目自定义平台枚举;
  • Application.platform、Application.persistentDataPath 等平台相关 API;
  • Java、JNI、Android Activity 或 Intent 等原生调用。

远程调试真机

当测试设备不方便通过 USB 连接线进行连结时,可以使用 HDC 的无线连接功能进行安装和调试。开发机与设备应处于可互通的同一内网中。

打开无线调试

在设备的 设置 > 系统 > 开发者选项 中打开无线调试,并记录设备的 IP 地址和端口号。

在 DevEco Studio 终端中执行:

hdc tconn <设备 IP>:<端口号>

可通过 hdc list targets 命令确认设备是否已经连接成功。

配置真机调试签名

在 IDE 顶部的设备列表中选择刚刚连接的设备,然后点击编译运行项目,即可进行远程安装。

首次安装时如果出现签名错误,可根据 DevEco Studio 的提示创建调试用的签名,这一步需要登录华为开发者账号。

Jenkins 自动化构建

Jenkins 与 C# 构建入口

团结引擎仍可通过编辑器命令行参数调用 C# 构建方法,整体流程与 Unity 类似。

需要注意的是,团结引擎产品版本与编辑器版本采用不同的编号规则。例如,团结引擎 1.9.3 对应的编辑器版本应为 2022.3.62t11。Jenkins 任务应以实际安装的编辑器路径和版本为准,不要直接使用产品版本号拼接路径。

BuildPlayer 参数差异

Unity 导出 Android Studio 或 Xcode 工程时,常会使用 BuildOptions.AcceptExternalModificationsToPlayer 这个枚举。

团结引擎导出 DevEco Studio 工程时不需要指定该参数。迁移现有构建脚本时,应将它从 OpenHarmony 分支中移除,避免沿用 Android/iOS 的导出配置。

将 Unity 日志转发到 HiLog

如果 Unity 日志在 HiLog 中无法打印,或者为了集中管理日志,可以在团结引擎中注册 Application.logMessageReceived 消息,再通过原生桥接将日志转发到 HarmonyOS 的 hilog。

这个示例也展示了团结引擎 C# 与 ArkTS 代码之间最基础的调用方式。

创建 LogBridge

在 tuanjieLib/src/main/ets 下创建 bridge 目录,并新建 LogBridge.ets:

import hilog from '@ohos.hilog';

export class LogBridge {
public static ForwardLog(level: string, logString: string): void {
switch (level) {
case "D":
hilog.debug(0x0000, 'GameLog', '%{public}s', logString);
break;

case "I":
hilog.info(0x0000, 'GameLog', '%{public}s', logString);
break;

case "W":
hilog.warn(0x0000, 'GameLog', '%{public}s', logString);
break;

case "E":
hilog.error(0x0000, 'GameLog', '%{public}s', logString);
break;

default:
hilog.info(0x0000, 'GameLog', '%{public}s', logString);
break;
}
}
}

export function RegisterLogBridge(): Record<string, Object> {
let register: Record<string, Object> = {};
register["LogBridge"] = LogBridge;
return register;
}

%{public}s 表示日志内容可以公开显示,便于调试时查看完整文本。发布时应避免把账号、Token 等敏感信息写入日志,可以用 %{private}s 来隐藏这些信息。

注册并导出 LogBridge

首先在 tuanjieLib/build-profile.json5 的 arkOptions.runtimeOnly.sources 中加入:

"./src/main/ets/bridge/LogBridge.ets"

这一步是避免代码在编译时被裁剪掉,确保桥接类在运行时可用。

然后在 tuanjieLib/exported.ets 中导出 LogBridge:

export { LogBridge } from './src/main/ets/bridge/LogBridge';

最后,在 tuanjieLib/src/main/ets/gen/TuanjieJSScriptRegister.ets 中引入注册函数,并在 RegisterJSScriptToCSharp 中完成注册:

import { RegisterLogBridge } from '../bridge/LogBridge';

// 在 RegisterJSScriptToCSharp 中注册
register(tuanjieJSClasses, RegisterLogBridge);

在 C# 中转发日志

原生工程注册完成后,在 Unity C# 代码中订阅日志回调并调用桥接类:

Application.logMessageReceived += OnLogMessage;

string level = type switch
{
LogType.Log => "I",
LogType.Warning => "W",
LogType.Error => "E",
LogType.Exception => "E",
LogType.Assert => "E",
_ => "I"
};

try
{
var bridge = new OpenHarmonyJSClass("LogBridge");
bridge.CallStatic("ForwardLog", level, logString);
}
catch
{
// 防止桥接失败时递归打日志
}

建议在 Awake 等初始化阶段创建并缓存 OpenHarmonyJSClass,不要在每条日志到来时重复实例化。这样既能避免桥接对象尚未完成初始化的问题,也能减少频繁创建对象带来的开销。

还需要注意两点:

  • 在对象销毁或应用退出时取消 Application.logMessageReceived 订阅,避免重复注册;
  • 桥接调用失败时不要再通过 Unity 日志记录异常,否则可能造成递归调用。