软件版本
Tuanjie Editor 1.9.2
DevEco Studio 6.1.0 Release
第一、打包运行步骤
Build Settings
Install with Tuanjie Hub
安装鸿蒙开发包OpenHarmony
Switch Platform 切换到 OpenHarmony
勾选 Export Project
导出工程 Export
用鸿蒙IDE打开工程
用DevEcoStudio打开刚刚导出的工程
自动设置相同的sdk版本
在DevEcoStudio中运行程序
可能需要配置一下签名
文件 -> 项目结构 -> 项目 -> 签名配置
勾选自动生成签名文件
第二、完成自动打包运行于手机遇到的问题
1.每次都提示"当前项目没有配置targetSdkVersion,请配置以确保应用兼容性。"
Project Settings -> Other Settings -> Identfication -> Compatible API Level (6.1.0(23))
仍然提示
build-profile.json5 文件中缺少targetSdkVersion字段,因为添加的鸿蒙打包模块没有适配
可以在Unity编辑器打包后调用接口IPostprocessBuildWithReport的OnPostprocessBuild方法中向build-profile.json5中写入 "targetSdkVersion": "6.1.0(23)", 字段
2.每次都要设置签名密码
设置包名【注意:包名的大小写 】
Project Settings -> Player -> 鸿蒙选卡 -> Other Settings -> Bundle Identifier
创建Key
Project Settings -> Player -> 鸿蒙选卡 -> Publishing Settings -> Keystore Manager创建一个新的key也就是.csr文件
AppGallery Connect
在"证书 APP ID Profile"选卡
申请 证书 .cer文件
申请 Profile .p7b文件
获取手机的udid
hdc shell bm get --udid
步骤
1.创建项目(位置:开发与服务)
2.创建一个APP(位置:证书、APP ID和Profile > APP ID)
绑定到项目上
3.申请证书(位置:证书、APP ID和Profile > 证书)
证书请求文件(CSR):本机生成的 .csr文件
生成.cer文件
4.生成Profile(位置:证书、APP ID和Profile > Profile)
1)选择应用
2)选择证书
3)调试情况下 选择设备
生成.p7b文件
5.如果指定设备(位置:证书、APP ID和Profile > 设备)
获取手机的udid
hdc shell bm get --udid
- Unity 的安全策略 :Unity 在导出鸿蒙工程时,为了避免密码以明文形式硬编码在项目文件中,有意将
storePassword和keyPassword这两个字段留空,不会自动带出。这主要是出于安全考虑,防止密码泄露。
可以在Unity编辑器打包后调用接口IPostprocessBuildWithReport的OnPostprocessBuild方法中向build-profile.json5中写入 "storePassword"和"keyPassword", 字段 密码是经过密钥加密是固定的
3.每次都要打开DevEco studio编译
用hvigorw.bat实现自动打包
build_ohos_app.bat
@echo off
chcp 65001 >nul
set DEVECO_SDK_HOME=D:\Program Files\Huawei\DevEco Studio\sdk
set OHPM_HOME=D:\Program Files\Huawei\DevEco Studio\tools\ohpm
set NODE_HOME=D:\Program Files\Huawei\DevEco Studio\tools\node
set PATH=%OHPM_HOME%\bin;%NODE_HOME%\bin;%PATH%
cd /d D:\Project\Unity\AllInOne\Harmony
echo ========================================
echo 1. 清理node_modules
echo ========================================
if exist "node_modules" (
rmdir /s /q node_modules
)
if exist "entry\node_modules" (
rmdir /s /q entry\node_modules
)
echo 当前路径是:%cd%
echo ========================================
echo 2. 执行ohpm install
echo ========================================
call ohpm install
echo 当前路径是:%cd%
echo ========================================
echo 3. 执行hvigor打包
echo ========================================
set hvigorw="D:/Program Files/Huawei/DevEco Studio/tools/hvigor/bin/hvigorw.bat"
call %hvigorw% clean
call %hvigorw% assembleApp --mode project -p product=default
用hdc实现安装到手机和在手机上打开
install_hap.bat
@echo off
chcp 65001 >nul
setlocal enabledelayedexpansion
:: ========== 配置区(已适配你的项目) ==========
set "ROOT=%~dp0"
set "HAP_DIR=%ROOT%entry\build\default\outputs\default"
set "HAP_FILE=entry-default-signed.hap"
set "HAP_FULL=%HAP_DIR%\%HAP_FILE%"
set "PACKAGE_NAME=com.scw.allinone"
:: 关键:替换为 module.json5 里的 ability name
set "MAIN_ABILITY=TuanjiePlayerAbility"
:: ==============================================
echo ==============================================
echo HDC 安装HAP + 鸿蒙自动启动APP
echo ==============================================
echo HAP路径:%HAP_FULL%
echo 应用包名:%PACKAGE_NAME%
echo 主Ability:%MAIN_ABILITY%
echo.
:: 校验安装包是否存在
if not exist "%HAP_FULL%" (
echo [错误] 未找到 hap 安装包,请先编译项目!
pause
exit /b 1
)
:: 查看连接设备
echo 1. 已连接设备:
hdc list targets
echo.
:: 先卸载旧版本(避免签名冲突)
echo 2. 卸载旧应用 %PACKAGE_NAME%
hdc uninstall %PACKAGE_NAME%
echo.
:: 安装新包
echo 3. 开始安装应用...
hdc install "%HAP_FULL%"
set INSTALL_RET=%errorlevel%
if %INSTALL_RET% neq 0 (
echo.
echo [失败] 安装出错,请检查上方日志!
pause
exit /b %INSTALL_RET%
)
echo.
echo [成功] 应用安装完成,正在启动APP
echo.
:: 鸿蒙 aa start 正确命令 -a 填你的Ability名称
hdc shell aa start -a %MAIN_ABILITY% -b %PACKAGE_NAME%
echo.
echo ==============================================
echo 全部操作执行完毕
echo ==============================================
endlocal
第三、终极一键运行于手机指南
只是本地测试不需要到官网申请证书,用本地生成的证书就可以。
第一步 打包项目
Unity打包出一个鸿蒙项目
第二步 获取证书
用DevEcoStudio运行一次,通过下面方式获取签名证书
文件 -> 项目结构 -> 项目 -> 签名配置
勾选自动生成签名文件
证书路径C:\Users\用户名\.ohos\config
第三步 记录变量
打开项目路径下build-profile.json5文件
保存以下几个字段的内容,这些是Unity打包不会自动写入的内容
"storePassword"
"keyPassword"
"keyAlias"
额外要写入的
"targetSdkVersion": "6.1.0(23)",
第四步 整理资源
在Unity项目中创建两个文件夹
一个用于存放证书("签名证书")
复制C:\Users\用户名\.ohos\config中的内容,避免其他项目导致覆盖
一个用于存放自动打包和运行的.bat文件("打包运行")
上面写过的两个文件
build_ohos_app.bat
install_hap.bat
第五步 Unity设置
Unity中Player Settings -> Publshing Settings
设置
Store file
Profile file
Certificate file
第六步 代码
代码功能
1.工具栏中可以勾选是否导出项目后直接打包运行
2.重写build-profile.json5文件内容,包括以下字段
"storePassword"
"keyPassword"
"keyAlias"
"targetSdkVersion": "6.1.0(23)",
3.复制build_ohos_app.bat和install_hap.bat到项目中
4.运行build_ohos_app.bat和install_hap.bat
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
using UnityEngine;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using UnityEditor;
public class HarmonyBuildProcessor : IPostprocessBuildWithReport
{
public int callbackOrder => 0;
// 定义要写入的密码密文
private const string STORE_PASSWORD = "0000001A6472324C71000390BADF1A442534003741E5B4C470062D21A4DACEA13AA94AE04BF314E7E9C1";
private const string KEY_PASSWORD = "0000001A5FB17BB3DD908CF7FCB94CBDAF3E0591BFF6ADB8901A487B75F1E65CBF0E32587D4C5B3C74CC";
// 新增:keyAlias 全局变量
private const string KEY_ALIAS = "debugKey";
// 静态变量保存勾选状态
public static bool EnableAutoBuildAndInstall = true;
public void OnPostprocessBuild(BuildReport report)
{
Debug.Log($"导出鸿蒙工程");
string outputFilePath = report.summary.outputPath;
Debug.Log($"📄 输出文件夹路径:{outputFilePath}");
string targetBuildProfilePath = string.Empty;
if (Directory.Exists(outputFilePath))
{
var files = Directory.GetFiles(outputFilePath, "build-profile.json5", SearchOption.AllDirectories);
if (files.Length > 0)
{
targetBuildProfilePath = files[0];
Debug.Log($"✅ 找到鸿蒙工程:{outputFilePath}");
Debug.Log($"📄 配置文件:{files[0]}");
ModifyBuildProfile(targetBuildProfilePath);
// 根据勾选状态决定是否执行批处理
if (EnableAutoBuildAndInstall)
{
Debug.Log($"✅ 自动构建和安装已启用,执行批处理脚本");
RunBatchScriptAfterModifyProfile(outputFilePath, outputFilePath);
}
else
{
Debug.Log($"⏸️ 自动构建和安装已禁用,跳过批处理脚本执行");
}
}
}
else
{
Debug.LogWarning($"❌ 未找到鸿蒙工程");
}
}
private void ModifyBuildProfile(string filePath)
{
try
{
string content = File.ReadAllText(filePath);
bool modified = false;
if (!content.Contains("\"targetSdkVersion\""))
{
string pattern = @"(""products""\s*:\s*\[\s*\{)";
string replacement = "$1\n \"targetSdkVersion\": \"6.1.0(23)\",";
string newContent = Regex.Replace(content, pattern, replacement, RegexOptions.Singleline);
if (content != newContent)
{
content = newContent;
modified = true;
Debug.Log($"✅ 已添加 targetSdkVersion");
}
else
{
int idx = content.IndexOf("\"products\"");
if (idx != -1)
{
int braceIdx = content.IndexOf("{", idx);
if (braceIdx != -1)
{
content = content.Insert(braceIdx + 1,
"\n \"targetSdkVersion\": \"6.1.0(23)\",");
modified = true;
Debug.Log($"✅ 备用方案添加 targetSdkVersion 成功");
}
}
}
}
else
{
Debug.Log($"ℹ️ targetSdkVersion 已存在,跳过");
}
// ===================== 新增 keyAlias 替换逻辑 =====================
string aliasPattern = @"""keyAlias""\s*:\s*""[^""]*""";
string aliasReplacement = $"\"keyAlias\": \"{KEY_ALIAS}\"";
if (Regex.IsMatch(content, aliasPattern))
{
string newContent = Regex.Replace(content, aliasPattern, aliasReplacement);
if (content != newContent)
{
content = newContent;
modified = true;
Debug.Log($"✅ 已更新 keyAlias 为 {KEY_ALIAS}");
}
}
else
{
Debug.LogWarning($"⚠️ 未找到 keyAlias 字段,跳过");
}
// ==================================================================
string storePattern = @"""storePassword""\s*:\s*""[^""]*""";
string storeReplacement = $"\"storePassword\": \"{STORE_PASSWORD}\"";
if (Regex.IsMatch(content, storePattern))
{
string newContent = Regex.Replace(content, storePattern, storeReplacement);
if (content != newContent)
{
content = newContent;
modified = true;
Debug.Log($"✅ 已更新 storePassword");
}
}
else
{
Debug.LogWarning($"⚠️ 未找到 storePassword 字段,跳过");
}
string keyPattern = @"""keyPassword""\s*:\s*""[^""]*""";
string keyReplacement = $"\"keyPassword\": \"{KEY_PASSWORD}\"";
if (Regex.IsMatch(content, keyPattern))
{
string newContent = Regex.Replace(content, keyPattern, keyReplacement);
if (content != newContent)
{
content = newContent;
modified = true;
Debug.Log($"✅ 已更新 keyPassword");
}
}
else
{
Debug.LogWarning($"⚠️ 未找到 keyPassword 字段,跳过");
}
if (modified)
{
File.SetAttributes(filePath, FileAttributes.Normal);
File.WriteAllText(filePath, content);
Debug.Log($"✅ 修改已保存到:{filePath}");
}
else
{
Debug.Log($"ℹ️ 无需修改:{filePath}");
}
}
catch (System.Exception e)
{
Debug.LogError($"❌ 修改失败:{filePath}\n{e.Message}");
}
}
/// <summary>
/// 独立批处理流程
/// batCopyRoot = Harmony文件夹 D:/Project/Unity/AllInOne/Harmony(bat复制存放目标)
/// bat源目录 = Harmony上级目录 D:/Project/Unity/AllInOne/打包运行
/// </summary>
private void RunBatchScriptAfterModifyProfile(string batCopyRoot, string harmonyProjectRoot)
{
try
{
// 【核心修改】源文件夹在 Harmony 的上级目录
string parentDir = Directory.GetParent(batCopyRoot).FullName;
string batSourceFolder = Path.Combine(parentDir, "打包运行");
// 目标bat存放在 Harmony 根目录
string batBuildDst = Path.Combine(batCopyRoot, "build_ohos_app.bat");
string batInstallDst = Path.Combine(batCopyRoot, "install_hap.bat");
string batBuildSrc = Path.Combine(batSourceFolder, "build_ohos_app.bat");
string batInstallSrc = Path.Combine(batSourceFolder, "install_hap.bat");
Debug.Log($"📁 bat脚本存放目标目录(Harmony):{batCopyRoot}");
Debug.Log($"📁 bat脚本源文件夹(上级目录):{batSourceFolder}");
System.Diagnostics.Debug.Flush();
if (!Directory.Exists(batSourceFolder))
{
Debug.LogError($"❌ 未找到【打包运行】文件夹:{batSourceFolder}");
return;
}
if (!File.Exists(batBuildSrc) || !File.Exists(batInstallSrc))
{
Debug.LogError($"❌ 缺少bat脚本:{batBuildSrc} / {batInstallSrc}");
return;
}
// 复制bat到 Harmony 根目录
File.Copy(batBuildSrc, batBuildDst, true);
File.Copy(batInstallSrc, batInstallDst, true);
Debug.Log($"✅ bat脚本已复制至:{batCopyRoot}");
// 执行打包bat,工作目录设为Harmony根目录
Debug.Log($"▶️ 执行打包脚本:{batBuildDst}");
RunBatFile(batBuildDst, batCopyRoot);
Debug.Log($"✅ build_ohos_app.bat 执行完毕");
System.Diagnostics.Debug.Flush();
// 等待签名hap生成
string hapCheckPath = Path.Combine(harmonyProjectRoot, @"entry\build\default\outputs\default\entry-default-signed.hap");
Debug.Log($"⏳ 监控hap文件:{hapCheckPath}");
int waitMaxSecond = 20;
int waited = 0;
bool hapExist = false;
while (waited < waitMaxSecond)
{
if (File.Exists(hapCheckPath))
{
hapExist = true;
break;
}
Thread.Sleep(1000);
waited++;
}
if (!hapExist)
{
Debug.LogError($"❌ 超时{waitMaxSecond}秒未生成 entry-default-signed.hap");
return;
}
Debug.Log($"✅ 检测到hap文件,开始安装");
// 执行安装bat
Debug.Log($"▶️ 执行安装脚本:{batInstallDst}");
RunBatFile(batInstallDst, batCopyRoot);
Debug.Log($"✅ install_hap.bat 执行完成,全流程结束");
}
catch (System.Exception ex)
{
Debug.LogError($"❌ 批处理流程异常:{ex.Message}\n{ex.StackTrace}");
}
}
private void RunBatFile(string batPath, string workDir)
{
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo()
{
FileName = batPath,
WorkingDirectory = workDir,
UseShellExecute = true,
CreateNoWindow = false
};
using (System.Diagnostics.Process proc = System.Diagnostics.Process.Start(startInfo))
{
proc.WaitForExit();
int exitCode = proc.ExitCode;
if (exitCode != 0)
{
Debug.LogWarning($"⚠️ 脚本异常退出,ExitCode:{exitCode} 脚本:{batPath}");
}
}
}
}
// 在Unity工具栏中添加勾选选项
[InitializeOnLoad]
public static class HarmonyBuildMenu
{
static HarmonyBuildMenu()
{
// 从EditorPrefs中读取保存的状态
HarmonyBuildProcessor.EnableAutoBuildAndInstall = EditorPrefs.GetBool("HarmonyAutoBuildAndInstall", true);
}
[MenuItem("Harmony/自动构建并安装HAP")]
public static void ToggleAutoBuildAndInstall()
{
HarmonyBuildProcessor.EnableAutoBuildAndInstall = !HarmonyBuildProcessor.EnableAutoBuildAndInstall;
EditorPrefs.SetBool("HarmonyAutoBuildAndInstall", HarmonyBuildProcessor.EnableAutoBuildAndInstall);
// 更新菜单项的勾选状态
Menu.SetChecked("Harmony/自动构建并安装HAP", HarmonyBuildProcessor.EnableAutoBuildAndInstall);
Debug.Log($"🔄 自动构建并安装HAP: {(HarmonyBuildProcessor.EnableAutoBuildAndInstall ? "启用" : "禁用")}");
}
[MenuItem("Harmony/自动构建并安装HAP", true)]
public static bool ValidateToggleAutoBuildAndInstall()
{
// 设置菜单项的勾选状态
Menu.SetChecked("Harmony/自动构建并安装HAP", HarmonyBuildProcessor.EnableAutoBuildAndInstall);
return true;
}
}
第七步 注意
遇到过失败的情况
1.包名大小写不对
2.证书和密码不匹配
3.build-profile.json5配置有遗漏