完成环境配置并成功导出 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/

DevEco Studio 日志查看

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

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

快速拷贝代码

直接重新导出会覆盖手动修改过的 OpenHarmony 工程。更稳妥的做法是保留一份已经完成原生适配的工程,每次导出后只同步团结引擎生成的代码和原生库。

下面的批处理脚本会同步 Managedlibs 两个目录。执行前请将 {导出目录}{兼容目录} 替换为真实路径:

@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

robocopy /MIR 会删除目标目录中源目录不存在的文件。请确保目标仅用于保存导出产物,不要在这两个目录中存放需要长期保留的手工文件。

处理平台相关代码

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

RuntimePlatform

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

建议同时搜索以下内容:

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

远程调试真机

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

打开无线调试

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

在 DevEco Studio 终端中执行:

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

连接成功后,可通过 hdc list targets 确认设备是否已经被识别。

配置真机调试签名

在设备列表中选择刚刚连接的设备并运行项目,即可进行远程安装。

首次安装时如果出现签名错误,可根据 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 等敏感信息写入日志。

注册并导出 LogBridge

首先在 tuanjieLib/build-profile.json5arkOptions.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 日志记录异常,否则可能造成递归调用。

小结

这一阶段的核心是建立一条稳定的排障链路:先用 Device File Explorer 检查应用文件,再用 HiLog 定位运行异常;遇到设备相关问题时切换真机,并通过 HDC 远程连接提高调试效率。完成日志桥接后,C# 与 ArkTS 的问题可以在同一日志窗口中关联分析,也为后续接入第三方 SDK 打下基础。