【m】打包修改

This commit is contained in:
zhangzheng
2026-03-18 14:31:01 +08:00
parent 096fb2f53d
commit 1c4f810aab
2 changed files with 577 additions and 146 deletions

View File

@@ -1,202 +1,523 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Stary.Evo.Editor
{
/// <summary>
/// Android APK打包工具窗口
/// </summary>
public class BuildApkWindow : OdinEditorWindow
{
public static OdinEditorWindow window;
#region
private static OdinEditorWindow window;
private bool _isBuilding;
private UnityEditor.Build.Reporting.BuildReport _lastBuildReport;
private string[] _scenePaths;
[Title("设备类型选择", titleAlignment: TitleAlignments.Centered)] [EnumToggleButtons, HideLabel]
public DeviceType deviceType = DeviceType.Xreal;
[Title("包裹列表", titleAlignment: TitleAlignments.Centered)]
[LabelText("Domain子包")]
[ValueDropdown("GetAvailablePackageNames")]
[OnValueChanged("OnPackageNameChanged")]
public string selectedPackageName;
[Title("打包配置", titleAlignment: TitleAlignments.Centered)] [ToggleLeft] [LabelText("自动递增版本号")]
public bool autoIncrementVersion = true;
[Title("打包配置", titleAlignment: TitleAlignments.Centered)] [ToggleLeft] [LabelText("是否添加水印")]
public bool isWatermark = false;
[ToggleLeft] [LabelText("开发构建")] public bool developmentBuild = false;
[ToggleLeft] [LabelText("构建完成后打开文件夹")] public bool openFolderOnComplete = true;
[BoxGroup("缓存管理", centerLabel: true)]
[Button("清空打包缓存", ButtonSizes.Large)]
private void ClearBuildCache()
{
ClearCache();
}
[ButtonGroup]
[Button("本地普通包", ButtonSizes.Large, ButtonStyle.FoldoutButton)]
private void BuildNormalPackage()
{
StartBuild(PLayerMode.LOCAL_PLAYMODE);
}
[Button("服务器热更包", ButtonSizes.Large, ButtonStyle.FoldoutButton)]
private void BuildServerPackage()
{
StartBuild(PLayerMode.HOST_PLAYMODE);
}
[Title("打包状态", titleAlignment: TitleAlignments.Centered)] [ReadOnly] [LabelText("当前状态")]
public string buildStatus = "就绪";
[ProgressBar(0, 100)] [ReadOnly] [LabelText("打包进度")]
public int buildProgress;
#endregion
#region
[MenuItem("Evo/Dev/Apk打包工具", false, 4)]
static void ShowWindows()
private static void ShowWindow()
{
window = (BuildApkWindow)EditorWindow.GetWindow(typeof(BuildApkWindow));
window.maxSize = new Vector2(400, 500);
window = GetWindow<BuildApkWindow>(false, "APK打包工具", true);
window.maxSize = new Vector2(500, 700);
window.minSize = new Vector2(400, 600);
window.Show();
}
protected override void Initialize()
{
base.Initialize();
buildApkLocalEntity = new BuildApkEntity(() => { BuildAndroid(false); }, null);
buildApkServerEntity = new BuildApkEntity(null, null);
buildApkWatermarkEntity = new BuildApkEntity(() => { BuildAndroid(true); }, null);
FindScenesWithLoadDll();
UpdateAvailablePackages();
}
[EnumToggleButtons, HideLabel] public DeviceType deviceType;
#endregion
#region
[HideLabel] [Title("包裹列表", titleAlignment: TitleAlignments.Centered)] [ValueDropdown("GetBuildPackageNames")]
public string selectedPackageNames;
[BoxGroup("Build(清空打包缓存)", centerLabel: true, order: 1)]
[Button("清空打包缓存")]
void OnClearCache()
/// <summary>
/// 开始打包流程
/// </summary>
/// <param name="isWatermark">是否为水印包</param>
private void StartBuild(PLayerMode playMode)
{
// 清理Library缓存目录
// 使用System.IO删除
// try {
// System.IO.Directory.Delete(Application.dataPath + "/../Library/AssetImportState", true);
// Debug.Log("成功删除AssetImportState");
// } catch (System.Exception e) {
// Debug.LogError($"删除失败: {e.Message}");
// }
// 使用System.IO删除
try
if (_isBuilding)
{
System.IO.Directory.Delete(Application.dataPath + "/../Library/BuildCache", true);
Debug.Log("成功删除BuildCache");
}
catch (System.Exception e)
{
Debug.LogError($"删除失败: {e.Message}");
}
// 使用System.IO删除
try
{
System.IO.Directory.Delete(Application.dataPath + "/../Library/ScriptAssemblies", true);
Debug.Log("成功删除ScriptAssemblies");
}
catch (System.Exception e)
{
Debug.LogError($"删除失败: {e.Message}");
}
AssetDatabase.Refresh();
EditorUtility.DisplayDialog("缓存清理", "已成功清除所有打包缓存文件", "确定");
}
[BoxGroup("Build(普通包\\服务器本地包)", centerLabel: true, order: 2), HideLabel]
public BuildApkEntity buildApkLocalEntity;
[BoxGroup("Build(服务器热更包)", centerLabel: true, order: 3), HideLabel]
public BuildApkEntity buildApkServerEntity;
[BoxGroup("Build(水印包)", centerLabel: true, order: 4), HideLabel]
public BuildApkEntity buildApkWatermarkEntity;
public void BuildAndroid(bool isWatermark)
{
HotfixMainResDomain hotfixMainResDomain = Resources.Load<HotfixMainResDomain>("HotfixMainResDomain");
if (hotfixMainResDomain == null)
{
Debug.LogError("HotfixMainResDomain 资源在Resources下不存在请检查");
Debug.LogWarning("打包正在进行中,请稍候...");
return;
}
_isBuilding = true;
buildStatus = "准备打包...";
buildProgress = 0;
Repaint();
try
{
// 执行打包
BuildAndroid(playMode);
}
catch (Exception e)
{
buildStatus = $"打包失败: {e.Message}";
Debug.LogError($"打包过程中发生错误: {e}");
ShowNotification(new GUIContent("APK打包失败"), 3f);
}
finally
{
_isBuilding = false;
buildProgress = 100;
Repaint();
}
}
/// <summary>
/// Android打包核心逻辑
/// </summary>
private void BuildAndroid(PLayerMode pLayerMode)
{
buildStatus = "加载配置文件...";
Repaint();
buildStatus = "设置打包模式...";
Repaint();
ChangePlayerSchema.SetPlayerMode(PLayerMode.HOST_PLAYMODE);
string packageName = "";
// 检测包名格式
if (!IsValidPackageName(selectedPackageNames))
{
// 不是有效包名格式,添加前缀 com.xosmo.
packageName = "com.xosmo." + selectedPackageNames;
// 设置包名和项目名
PlayerSettings.productName = selectedPackageNames;
PlayerSettings.applicationIdentifier = packageName;
}
else
{
// 分割包名字符串
string[] segments = selectedPackageNames.Split('.');
// 返回最后一个部分
packageName = segments[segments.Length - 1];
PlayerSettings.productName = packageName;
PlayerSettings.applicationIdentifier = selectedPackageNames;
}
buildStatus = "配置包名...";
Repaint();
ConfigurePackageInfo();
string[] scenelist = default;
buildStatus = "配置场景列表...";
Repaint();
string[] sceneList = ConfigureScenes();
buildStatus = "配置水印信息...";
Repaint();
if (isWatermark)
{
scenelist = new[]
{
hotfixMainResDomain.projectInfo.loadingScenePath,
$"Assets/Main/main_{deviceType.ToString()}.unity"
};
PlayerSettings.productName += "_watermark";
PlayerSettings.applicationIdentifier += "_watermark";
Sprite xosmo = Resources.Load<Sprite>("logo");
PlayerSettings.virtualRealitySplashScreen = xosmo.texture;
PlayerSettings.SplashScreen.showUnityLogo = false;
PlayerSettings.SplashScreen.logos = new[]
{
PlayerSettings.SplashScreenLogo.Create(3f, xosmo),
};
ConfigureWatermark();
}
else
buildStatus = "执行打包...";
Repaint();
// 打包前检查
if (!PreBuildCheck())
{
scenelist = new[]
{
$"Assets/Main/main_{deviceType.ToString()}.unity"
};
PlayerSettings.virtualRealitySplashScreen = null;
PlayerSettings.SplashScreen.showUnityLogo = false;
PlayerSettings.SplashScreen.logos = null;
return;
}
// 配置构建选项
// BuildPlayerOptions buildOptions = new BuildPlayerOptions
// {
// scenes = scenelist,
// locationPathName = $"Builds/Android/{PlayerSettings.productName}.apk",
// target = BuildTarget.Android,
// options = BuildOptions.None
// };
//
// // 执行打包
// var report = BuildPipeline.BuildPlayer(buildOptions);
//
// BuildSummary summary = report.summary;
// if (summary.result == BuildResult.Succeeded)
// {
// Debug.Log("Build succeeded: " + summary.totalSize + " bytes");
// }
//
// if (summary.result == BuildResult.Failed)
// {
// Debug.LogError("Build failed");
// }
BuildPlayerOptions buildOptions = ConfigureBuildOptions(sceneList, isWatermark);
// 执行打包
var report = BuildPipeline.BuildPlayer(buildOptions);
_lastBuildReport = report;
// 处理打包结果
HandleBuildResult(report, buildOptions.locationPathName);
}
public enum DeviceType
{
Xreal,
Rokid
}
/// <summary>
/// 检测当前所有包裹
/// 配置包名和产品名称
/// </summary>
/// <returns></returns>
private List<string> GetBuildPackageNames()
private void ConfigurePackageInfo()
{
List<string> result = new List<string>();
foreach (var name in CreatAssetWindow.GetCreatDomainAllName())
string productName;
string packageName;
if (!IsValidPackageName(selectedPackageName))
{
result.Add(name);
// 不是有效包名格式,添加前缀 com.xosmo.
productName = selectedPackageName;
packageName = "com.xosmo." + selectedPackageName;
}
else
{
// 分割包名字符串,使用最后一个部分作为产品名称
string[] segments = selectedPackageName.Split('.');
productName = segments.Last();
packageName = selectedPackageName;
}
if (selectedPackageNames.IsNullOrEmpty())
PlayerSettings.productName = productName;
PlayerSettings.applicationIdentifier = packageName;
// 自动递增版本号
if (autoIncrementVersion)
{
if (result.Count > 0)
IncrementBuildVersion();
}
}
/// <summary>
/// 配置场景列表
/// </summary>
/// <param name="isWatermark">是否为水印包</param>
/// <param name="hotfixMainResDomain">资源配置</param>
/// <returns>场景路径数组</returns>
private string[] ConfigureScenes()
{
List<string> scenes = new List<string>();
string mainScenePath = $"Assets/Main/main_{deviceType.ToString()}.unity";
// 验证主场景是否存在
if (!File.Exists(mainScenePath))
{
throw new Exception($"主场景文件不存在: {mainScenePath}");
}
if (deviceType == DeviceType.Xreal && isWatermark)
{
// 加载资源配置
HotfixMainResDomain hotfixMainResDomain = Resources.Load<HotfixMainResDomain>("HotfixMainResDomain");
if (hotfixMainResDomain == null)
{
selectedPackageNames = result[0];
throw new Exception("HotfixMainResDomain资源在Resources下不存在请检查");
}
// 水印包包含水印场景
string loadingScenePath = hotfixMainResDomain.projectInfo.loadingScenePath;
if (!string.IsNullOrEmpty(loadingScenePath) && File.Exists(loadingScenePath))
{
scenes.Add(loadingScenePath);
}
else
{
selectedPackageNames = "";
Debug.LogWarning($"加载场景不存在或未配置,仅打包主场景: {loadingScenePath}");
}
}
return result;
foreach (var scenePath in _scenePaths)
{
if (mainScenePath == scenePath)
{
LoadSceneForEditing(mainScenePath);
}
}
scenes.Add(mainScenePath);
return scenes.ToArray();
}
/// <summary>
/// 配置水印信息
/// </summary>
private void ConfigureWatermark()
{
PlayerSettings.productName += "_watermark";
PlayerSettings.applicationIdentifier += "_watermark";
Sprite logoSprite = Resources.Load<Sprite>("logo");
if (logoSprite != null)
{
PlayerSettings.virtualRealitySplashScreen = logoSprite.texture;
PlayerSettings.SplashScreen.showUnityLogo = false;
PlayerSettings.SplashScreen.logos = new[]
{
PlayerSettings.SplashScreenLogo.Create(3f, logoSprite)
};
}
else
{
Debug.LogWarning("Resources下未找到logo图片水印包将不显示自定义logo");
}
}
/// <summary>
/// 配置构建选项
/// </summary>
/// <param name="scenes">场景列表</param>
/// <param name="isWatermark">是否为水印包</param>
/// <returns>构建选项</returns>
private BuildPlayerOptions ConfigureBuildOptions(string[] scenes, bool isWatermark)
{
string buildPath = GetBuildPath(isWatermark);
// 确保构建目录存在
string buildDir = Path.GetDirectoryName(buildPath);
if (!Directory.Exists(buildDir))
{
Directory.CreateDirectory(buildDir);
}
BuildOptions buildOptions = BuildOptions.None;
if (developmentBuild)
{
buildOptions |= BuildOptions.Development;
buildOptions |= BuildOptions.AllowDebugging;
buildOptions |= BuildOptions.ConnectWithProfiler;
}
return new BuildPlayerOptions
{
scenes = scenes,
locationPathName = buildPath,
target = BuildTarget.Android,
options = buildOptions
};
}
/// <summary>
/// 获取构建路径
/// </summary>
/// <param name="isWatermark">是否为水印包</param>
/// <returns>构建路径</returns>
private string GetBuildPath(bool isWatermark)
{
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string packageType = isWatermark ? "watermark" : "normal";
string buildFileName = $"{PlayerSettings.productName}_{packageType}_{timestamp}.apk";
return Path.Combine("Builds", "Android", buildFileName);
}
/// <summary>
/// 处理打包结果
/// </summary>
/// <param name="report">构建报告</param>
/// <param name="buildPath">构建路径</param>
private void HandleBuildResult(UnityEditor.Build.Reporting.BuildReport report, string buildPath)
{
BuildSummary summary = report.summary;
if (summary.result == BuildResult.Succeeded)
{
buildStatus = $"打包成功!大小: {FormatBytes(summary.totalSize)}";
Debug.Log($"构建成功: {buildPath},大小: {FormatBytes(summary.totalSize)}");
ShowNotification(new GUIContent("APK打包成功"), 3f);
// 构建完成后打开文件夹
if (openFolderOnComplete)
{
OpenBuildFolder(Path.GetDirectoryName(buildPath));
}
}
else
{
buildStatus = "打包失败!";
Debug.LogError("构建失败");
ShowNotification(new GUIContent("APK打包失败"), 3f);
// 显示详细错误信息
foreach (var step in report.steps)
{
foreach (var message in step.messages)
{
if (message.type == LogType.Error)
{
Debug.LogError($"构建错误: {message.content}");
}
}
}
}
}
#endregion
#region
/// <summary>
/// 打包前检查
/// </summary>
/// <returns>检查是否通过</returns>
private bool PreBuildCheck()
{
// 检查是否选择了包裹
if (string.IsNullOrEmpty(selectedPackageName))
{
ShowNotification(new GUIContent("请先选择一个包裹!"), 2f);
return false;
}
// 检查当前平台
if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android)
{
if (EditorUtility.DisplayDialog("平台提示", "当前平台不是Android是否切换到Android平台", "是", "否"))
{
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android);
return false; // 切换平台后需要重新检查
}
return false;
}
// 检查场景是否保存
if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
{
return false;
}
return true;
}
/// <summary>
/// 清空打包缓存
/// </summary>
private void ClearCache()
{
try
{
buildStatus = "正在清理缓存...";
Repaint();
string projectPath = Application.dataPath;
string libraryPath = Path.GetFullPath(Path.Combine(projectPath, "../Library"));
// 要清理的缓存目录
string[] cacheDirectories =
{
Path.Combine(libraryPath, "BuildCache"),
Path.Combine(libraryPath, "ScriptAssemblies"),
Path.Combine(libraryPath, "ArtifactDB")
};
int clearedCount = 0;
foreach (string cacheDir in cacheDirectories)
{
if (Directory.Exists(cacheDir))
{
Directory.Delete(cacheDir, true);
clearedCount++;
Debug.Log($"成功删除缓存目录: {cacheDir}");
}
}
// 清理构建输出目录
string buildOutputPath = Path.Combine(Application.dataPath, "../Builds/Android");
if (Directory.Exists(buildOutputPath))
{
// 只清理旧的APK文件保留目录结构
string[] oldApks = Directory.GetFiles(buildOutputPath, "*.apk", SearchOption.AllDirectories);
foreach (string apkFile in oldApks)
{
File.Delete(apkFile);
}
Debug.Log("成功清理旧的APK文件");
}
AssetDatabase.Refresh();
buildStatus = "缓存清理完成!";
ShowNotification(new GUIContent("缓存清理完成!"), 2f);
Debug.Log($"缓存清理完成,共清理 {clearedCount} 个缓存目录");
}
catch (Exception e)
{
buildStatus = "缓存清理失败!";
Debug.LogError($"缓存清理失败: {e.Message}");
ShowNotification(new GUIContent("缓存清理失败!"), 2f);
}
}
/// <summary>
/// 获取可用的包裹名称列表
/// </summary>
/// <returns>包裹名称列表</returns>
private List<string> GetAvailablePackageNames()
{
UpdateAvailablePackages();
return CreatAssetWindow.GetCreatDomainAllName().ToList();
}
/// <summary>
/// 更新可用包裹列表
/// </summary>
private void UpdateAvailablePackages()
{
var availablePackages = CreatAssetWindow.GetCreatDomainAllName().ToList();
if (availablePackages.Any() && string.IsNullOrEmpty(selectedPackageName))
{
selectedPackageName = availablePackages.First();
}
}
/// <summary>
/// 当包裹名称改变时的回调
/// </summary>
private void OnPackageNameChanged()
{
buildStatus = "就绪";
}
/// <summary>
/// 自动递增版本号
/// </summary>
private void IncrementBuildVersion()
{
Version version = new Version(PlayerSettings.bundleVersion);
Version newVersion = new Version(version.Major, version.Minor, version.Build + 1);
PlayerSettings.bundleVersion = newVersion.ToString();
Debug.Log($"版本号已递增: {newVersion}");
}
/// <summary>
/// 打开构建文件夹
/// </summary>
/// <param name="folderPath">文件夹路径</param>
private void OpenBuildFolder(string folderPath)
{
if (Directory.Exists(folderPath))
{
EditorUtility.RevealInFinder(folderPath);
}
}
/// <summary>
@@ -230,5 +551,115 @@ namespace Stary.Evo.Editor
return true;
}
/// <summary>
/// 格式化字节大小
/// </summary>
/// <param name="bytes">字节数</param>
/// <returns>格式化后的字符串</returns>
private string FormatBytes(ulong bytes)
{
string[] suffixes = { "B", "KB", "MB", "GB" };
int index = 0;
double size = bytes;
while (size >= 1024 && index < suffixes.Length - 1)
{
size /= 1024;
index++;
}
return $"{size:F2} {suffixes[index]}";
}
#endregion
#region
/// <summary>
/// 查找所有包含LoadDll的场景文件
/// </summary>
private void FindScenesWithLoadDll()
{
List<string> sceneList = new List<string>();
// 搜索所有unity场景文件
string[] allScenes = Directory.GetFiles(Application.dataPath, "*.unity", SearchOption.AllDirectories);
foreach (string scenePath in allScenes)
{
// 读取场景文本内容检查是否包含LoadDll
try
{
string sceneContent = File.ReadAllText(scenePath);
if (sceneContent.Contains("m_Name: LoadDll"))
{
// 转换为Assets相对路径
string assetsPath = scenePath.Replace(Application.dataPath, "Assets");
sceneList.Add(assetsPath);
}
}
catch (Exception e)
{
Debug.LogError($"读取场景文件失败: {scenePath}, 错误: {e.Message}");
}
}
_scenePaths = sceneList.ToArray();
}
/// <summary>
/// 加载场景进行编辑
/// </summary>
private void LoadSceneForEditing(string scenePath)
{
try
{
// 加载场景
EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Single);
// 查找所有LoadDll物体
GameObject[] loadDllObjects = GameObject.FindGameObjectsWithTag("LoadDll");
// 如果没有找到,尝试按名称查找
if (loadDllObjects.Length == 0)
{
GameObject loadDllObj = GameObject.Find("LoadDll");
if (loadDllObj != null)
{
loadDllObjects = new GameObject[] { loadDllObj };
}
}
// 收集HybridClREntrance组件
HybridClREntrance entrance = null;
foreach (GameObject obj in loadDllObjects)
{
entrance = obj.GetComponent<HybridClREntrance>();
entrance.loadDomain = selectedPackageName;
}
Debug.Log($"成功加载场景: {scenePath}");
}
catch (Exception e)
{
Debug.LogError($"加载场景失败: {scenePath}, 错误: {e.Message}");
}
}
#endregion
#region
/// <summary>
/// 设备类型
/// </summary>
public enum DeviceType
{
Xreal,
Rokid
}
#endregion
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "com.staryevo.tools",
"version": "1.3.16",
"version": "1.3.17",
"displayName": "00.StaryEvo.Tools",
"description": "This is an Framework package(后台服务器版本端口9527)",
"unity": "2021.3",