2025-11-03 15:00:43 +08:00

582 lines
21 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using UnityEngine;
using UnityEditor;
using System.IO;
using UnityEditor.Build.Reporting;
using System.Text.RegularExpressions;
using System;
using System.Threading;
using System.Collections.Generic;
namespace BFEditor.Build
{
public static class BuildAndroidUtils
{
const string JAVA_HOME_ENVIRONMENT = "JAVA_HOME";
const string JAVA_HOME_ENVIRONMENT_PATH = "/Applications/Unity/PlaybackEngines/AndroidPlayer/Tools/OpenJDK/MacOS";
const string ANDROID_DEFINE_SYMBOLS = "THREAD_SAFE;USE_AB";
const string AS_PROJECT_PATH = "BFVersions/android";
const string GRADLE_PATH = "/Users/aoddabao/.gradle/wrapper/dists/gradle-5.6.4-all/ankdp27end7byghfw1q2sw75f/gradle-5.6.4/bin/gradle";
const string BUGLY_APP_ID = "1eb4e5e560";
const string BUGLY_APP_KEY = "99e249db-4eeb-440e-83e7-c2bd80e8b5e4";
static Thread buglyUploadThread;
static string ReleaseSOPath = Application.dataPath + "/../BFVersions/android/ub-release/src/main/jniLibs";
static string BuglyPath = Application.dataPath + "/../Bugly";
static string BuglySOPath = Application.dataPath + "/../Bugly/so";
static string GradleExcuteProjectPath = Application.dataPath + "/../BFVersions/android/ub-release";
static string GoogleAsProjectPath = Application.dataPath + "/../BFVersions/android/dz_google_apk";
static string GoogleCommonProjectPath = Application.dataPath + "/../BFVersions/android/google_common";
static string RuStoreProjectPath = Application.dataPath + "/../BFVersions/android/ru_store";
static string RuProjectPath = Application.dataPath + "/../BFVersions/android/ru";
static string GoogleServicesProjectPath = Application.dataPath + "/../BFVersions/android/google-services";
static string PublishAsProjectPath = Application.dataPath + "/../BFVersions/android/publish_release";
static string KeystoreFilePath = Application.dataPath + "/../BFVersions/android/keystore/dz_keystore.txt";
static string SignShellPath = Application.dataPath + "/../BFFiles/androidkey";
static string GpAlginShellPath = Application.dataPath + "/../BFFiles/androidkey";
static HashSet<string> AABInPackageFileHashSet = new HashSet<string>()
{
"bin",
"UnityServicesProjectConfiguration.json"
};
static BuildAndroidUtils()
{
EnsureJavaHome();
}
public static void EnsureJavaHome()
{
// var javaHomeEnvironment = Environment.GetEnvironmentVariable(JAVA_HOME_ENVIRONMENT);
// if (string.IsNullOrEmpty(javaHomeEnvironment))
// {
// Environment.SetEnvironmentVariable(JAVA_HOME_ENVIRONMENT, JAVA_HOME_ENVIRONMENT_PATH);
// }
}
/// <summary>
/// 打包android
/// </summary>
public static bool BuildAndroidPlayer(BuildInfo buildInfo)
{
var buildTarget = BuildTarget.Android;
// 检查平台
if (EditorUserBuildSettings.activeBuildTarget != buildTarget)
{
Debug.LogError("[bferror]当前没有在对应平台");
return false;
}
// 重新生成XLua
CompileScriptsUtils.RegenerateXLuaCode(true);
// 打包设置
BuildSettings(buildInfo);
// 开始打包
var bpOptions = GetBuildOptions(buildInfo);
var report = BuildPipeline.BuildPlayer(bpOptions);
// 导出工程成功
if (report.summary.result == BuildResult.Succeeded)
{
MergeProject(buildInfo, GoogleAsProjectPath);
FixGradleVersion(buildInfo.versionCode, buildInfo.version);
if (buildInfo.exportProject)
{
return true;
}
else
{
return BuildAPK(buildInfo);
}
}
else
{
Debug.LogError("[bferror]unity打包失败");
return false;
}
}
/// <summary>
/// 打包设置
/// </summary>
static void BuildSettings(BuildInfo buildInfo)
{
// 设置bundleVersion
PlayerSettings.bundleVersion = buildInfo.version;
// 设置VersionCode
PlayerSettings.Android.bundleVersionCode = buildInfo.versionCode;
// 设置竖屏
PlayerSettings.defaultInterfaceOrientation = UIOrientation.Portrait;
PlayerSettings.allowedAutorotateToPortrait = false;
PlayerSettings.allowedAutorotateToPortraitUpsideDown = false;
PlayerSettings.allowedAutorotateToLandscapeLeft = false;
PlayerSettings.allowedAutorotateToLandscapeRight = false;
// 安卓构建目标CPU架构
PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARMv7 | AndroidArchitecture.ARM64;
// 强加Internet权限
PlayerSettings.Android.forceInternetPermission = true;
// 关闭启动动画
PlayerSettings.SplashScreen.show = false;
// 设置包名
PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, buildInfo.bundleName);
// 跳过版本控制
var symbols = ANDROID_DEFINE_SYMBOLS;
if (buildInfo.skipVersion)
{
symbols = symbols + ";SKIP_VERSION";
}
if (buildInfo.mode == BuildMode.DEV)
{
symbols = symbols + ";BF_APP_DEV";
}
else if (buildInfo.mode == BuildMode.TEST)
{
symbols = symbols + ";BF_APP_TEST";
}
PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, symbols);
Debug.Log("[bfinfo]设置defineSymbols: " + symbols);
PlayerSettings.productName = "C1";
EditorUserBuildSettings.development = false;
EditorUserBuildSettings.androidBuildType = AndroidBuildType.Release;
// 是否导出as工程
EditorUserBuildSettings.exportAsGoogleAndroidProject = true;
var scriptImp = ScriptingImplementation.IL2CPP;
PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, scriptImp);
}
/// <summary>
/// 获取打包参数
/// </summary>
static BuildPlayerOptions GetBuildOptions(BuildInfo buildInfo)
{
var bpOptions = new BuildPlayerOptions();
bpOptions.scenes = AssetBundleUtils.GetBuildScenes();
bpOptions.target = BuildTarget.Android;
bpOptions.locationPathName = GetASProjectPathName(buildInfo);
BuildOptions options = BuildOptions.None;
bpOptions.options = options;
return bpOptions;
}
static string GetDevApkPathName(BuildInfo buildInfo)
{
var apkName = buildInfo.skipVersion ? "dz_dev_skip_version.apk" : "dz_dev_debug.apk";
var path = Path.Combine(AS_PROJECT_PATH, apkName);
return path;
}
static string GetASProjectPathName(BuildInfo buildInfo)
{
var dir = Path.Combine(AS_PROJECT_PATH, "publish_release");
if (Directory.Exists(dir))
{
Directory.Delete(dir, true);
}
return dir;
}
static bool BuildAPK(BuildInfo buildInfo, bool isAAB = false)
{
// 设置jdk环境变量
string javaHomePath = System.Environment.GetEnvironmentVariable("JAVE_HOME");
if (string.IsNullOrEmpty(javaHomePath))
{
Debug.LogError("[bferror] 找不到环境变量JAVE_HOME");
return false;
}
var gradleFilePath = Path.Combine(PublishAsProjectPath, "gradle.properties");
var gradleFileText = File.ReadAllText(gradleFilePath);
if (!gradleFileText.Contains("org.gradle.java.home"))
{
gradleFileText = gradleFileText + "\norg.gradle.java.home=" + javaHomePath.Replace("\\", "/");
}
File.WriteAllText(gradleFilePath, gradleFileText);
// 设置密钥密码
if (File.Exists(KeystoreFilePath))
{
var password = File.ReadAllText(KeystoreFilePath).Replace("\n", "").Replace("\r", "");
var buildGradlePath = Path.Combine(PublishAsProjectPath, "launcher/build.gradle");
var text = File.ReadAllText(buildGradlePath);
var regex = new Regex("REPLACE_PASSWORD");
text = regex.Replace(text, password);
File.WriteAllText(buildGradlePath, text);
}
Debug.Log("[bfinfo]正在buildApk...");
var success = true;
var args = "assembleRelease";
if (isAAB)
{
args = "bundleRelease";
}
string gradleHomePath = System.Environment.GetEnvironmentVariable("GRADLE_HOME");
if (string.IsNullOrEmpty(gradleHomePath))
{
Debug.LogError("[bferror] 找不到环境变量GRADLE_HOME");
return false;
}
#if UNITY_EDITOR_OSX
var gradleCommondPath = Path.Combine(gradleHomePath, "gradle");
#else
var gradleCommondPath = Path.Combine(gradleHomePath, "gradle.bat");
#endif
BFEditorUtils.RunCommond(gradleCommondPath, args, PublishAsProjectPath,
(msg) =>
{
},
(errorMsg) =>
{
if (errorMsg.Contains("BUILD FAILED") || errorMsg.Contains("FAILURE") || errorMsg.Contains("ERROR"))
{
success = false;
Debug.LogError("[bferror] " + errorMsg);
}
});
return success;
}
/// <summary>
/// 合并工程
/// </summary>
static void MergeProject(BuildInfo buildInfo, String asProjectPath)
{
Debug.Log("[bfinfo]正在整合 project...");
var dir = Path.Combine(Application.dataPath, "../", AS_PROJECT_PATH, "publish_release");
var javaPath = Path.Combine(dir, "unityLibrary/src/main/java");
var manifestPath = Path.Combine(dir, "unityLibrary/src/main/AndroidManifest.xml");
// 老版本unity需要替换这个2021之后的新版本不需要
// 获取到unity打出的build-id
// var reader = new StreamReader(new FileStream(manifestPath, FileMode.Open));
// string buildIdLine;
// while ((buildIdLine = reader.ReadLine()) != null)
// {
// if (buildIdLine.Contains("unity.build-id"))
// {
// Debug.Log("[bfinfo]修正build-id: " + buildIdLine);
// break;
// }
// }
// reader.Close();
// reader.Dispose();
if (Directory.Exists(javaPath))
{
var di = new DirectoryInfo(javaPath);
di.Delete(true);
}
// 如果是俄罗斯支付 则需要覆盖为俄罗斯相关文件
// if (buildInfo.bundleName == BF.BFPlatform.ANDROID_GP_PACKAGE_NAME_RU)
// {
// BFEditorUtils.CopyDir(RuProjectPath, dir);
// // 获取到build.gradle和AndroidManifest
// var buildGradlePath = Path.Combine(dir, "launcher/build.gradle");
// var text = File.ReadAllText(buildGradlePath);
// var regex = new Regex("REPLACE_APPLICATION_ID");
// text = regex.Replace(text, buildInfo.bundleName);
// File.WriteAllText(buildGradlePath, text);
// var androidManifestPath = Path.Combine(dir, "launcher/src/main/AndroidManifest.xml");
// text = File.ReadAllText(androidManifestPath);
// regex = new Regex("REPLACE_APPLICATION_ID");
// text = regex.Replace(text, buildInfo.bundleName);
// File.WriteAllText(androidManifestPath, text);
// // 还有另一个AndroidManifest
// androidManifestPath = Path.Combine(dir, "unityLibrary/src/main/AndroidManifest.xml");
// text = File.ReadAllText(androidManifestPath);
// regex = new Regex("REPLACE_APPLICATION_ID");
// text = regex.Replace(text, buildInfo.bundleName);
// File.WriteAllText(androidManifestPath, text);
// }
// // 否则使用通用谷歌
// else
// {
BFEditorUtils.CopyDir(GoogleCommonProjectPath, dir);
// 获取到google_common复制过去的build.gradle和AndroidManifest
var buildGradlePath = Path.Combine(dir, "launcher/build.gradle");
var text = File.ReadAllText(buildGradlePath);
var regex = new Regex("REPLACE_APPLICATION_ID");
text = regex.Replace(text, buildInfo.bundleName);
File.WriteAllText(buildGradlePath, text);
var androidManifestPath = Path.Combine(dir, "launcher/src/main/AndroidManifest.xml");
text = File.ReadAllText(androidManifestPath);
regex = new Regex("REPLACE_APPLICATION_ID");
text = regex.Replace(text, buildInfo.bundleName);
File.WriteAllText(androidManifestPath, text);
BFEditorUtils.CopyDir(asProjectPath, dir);
// 还有dz_google_apk下的AndroidManifest
androidManifestPath = Path.Combine(dir, "unityLibrary/src/main/AndroidManifest.xml");
text = File.ReadAllText(androidManifestPath);
regex = new Regex("REPLACE_APPLICATION_ID");
text = regex.Replace(text, buildInfo.bundleName);
File.WriteAllText(androidManifestPath, text);
// }
// 统一替换google-services文件
var gsPath = Path.Combine(GoogleServicesProjectPath, Path.Combine(buildInfo.bundleName, "google-services.json"));
if (File.Exists(gsPath))
{
var destFilePath = Path.Combine(dir, "launcher/google-services.json");
File.Copy(gsPath, destFilePath, true);
}
}
/// <summary>
/// fix versionCode versionName
/// </summary>
static void FixGradleVersion(int versionCode, string versionName)
{
Debug.Log("[bfinfo]修正build.gradle: VersionCode " + versionCode + " VersionName " + versionName);
var gradleFilePath = Path.Combine(PublishAsProjectPath, "launcher", "build.gradle");
var text = File.ReadAllText(gradleFilePath);
var regex = new Regex("versionCode 1");
text = regex.Replace(text, string.Format("versionCode {0}", versionCode));
var regex2 = new Regex("versionName '0.1.0'");
text = regex2.Replace(text, string.Format("versionName '{0}'", versionName));
File.WriteAllText(gradleFilePath, text);
var gradleFilePath2 = Path.Combine(PublishAsProjectPath, "unityLibrary", "build.gradle");
var text2 = File.ReadAllText(gradleFilePath2);
text2 = regex.Replace(text2, string.Format("versionCode {0}", versionCode));
text2 = regex2.Replace(text2, string.Format("versionName '{0}'", versionName));
File.WriteAllText(gradleFilePath2, text2);
}
/// <summary>
/// 内网Release包接入Bugly
/// </summary>
static void AddBuglyParamsToReleaseProject()
{
// 修改build.grade
var gradleFilePath = Path.Combine(GradleExcuteProjectPath, "build.gradle");
var text = File.ReadAllText(gradleFilePath);
var regex = new Regex("implementation");
var match = regex.Match(text);
var index = match.Index;
text = text.Insert(index, "\n" +
"implementation 'com.tencent.bugly:crashreport:latest.release'\n" +
"implementation 'com.tencent.bugly:nativecrashreport:latest.release'\n");
File.WriteAllText(gradleFilePath, text);
// 修改AndroidManifest.xml
var manifestPath = GradleExcuteProjectPath + "/src/main/AndroidManifest.xml";
text = File.ReadAllText(manifestPath);
regex = new Regex("<uses-permission");
match = regex.Match(text);
index = match.Index;
text = text.Insert(index, "\n" +
"<uses-permission android:name='android.permission.READ_PHONE_STATE' />\n" +
"<uses-permission android:name='android.permission.ACCESS_WIFI_STATE' />\n" +
"<uses-permission android:name='android.permission.READ_LOGS' />\n" +
"<uses-permission android:name='android.permission.POST_NOTIFICATIONS'/>\n");
File.WriteAllText(manifestPath, text);
// 修改UnityPlayerActivity.java
var javaPath = GradleExcuteProjectPath + "/src/main/java/com/juzu/ub/release/android/UnityPlayerActivity.java";
text = File.ReadAllText(javaPath);
regex = new Regex("import");
match = regex.Match(text);
index = match.Index;
text = text.Insert(index, "\nimport com.tencent.bugly.crashreport.CrashReport;");
regex = new Regex("requestFocus\\(\\);");
match = regex.Match(text);
index = match.Index;
var length = match.Length;
var formatInsertString = string.Format("\nCrashReport.initCrashReport(getApplicationContext(), \"{0}\", false);", BUGLY_APP_ID);
text = text.Insert(index + length, formatInsertString);
File.WriteAllText(javaPath, text);
}
/// <summary>
/// release包加密后签名
/// </summary>
static bool ReleaseApkSign()
{
Debug.Log("[bfinfo]正在签名apk...");
var success = true;
BFEditorUtils.RunCommond("sh", "release_sign.sh", SignShellPath,
(msg) =>
{
},
(errorMsg) =>
{
Debug.LogError("[bferror] " + errorMsg);
success = false;
});
return success;
}
/// <summary>
/// gp包对齐
/// </summary>
static bool GPApkAlign()
{
Debug.Log("[bfinfo]正在对齐apk...");
var success = true;
BFEditorUtils.RunCommond("sh", "gp_align.sh", GpAlginShellPath,
(msg) =>
{
},
(errorMsg) =>
{
Debug.LogError("[bferror] " + errorMsg);
success = false;
});
return success;
}
/// <summary>
/// google official包对齐
/// </summary>
static bool GoogleOfficialApkAlign()
{
Debug.Log("[bfinfo]正在对齐apk...");
var success = true;
BFEditorUtils.RunCommond("sh", "google_align.sh", GpAlginShellPath,
(msg) =>
{
},
(errorMsg) =>
{
Debug.LogError("[bferror] " + errorMsg);
success = false;
});
return success;
}
/// <summary>
/// CopyRelease文件夹内的so到bugly文件夹制作并上传Bugly符号表
/// 因是非出包必要步骤且依赖网络环境和腾讯服务器有时会失败所以开一个Thread来处理
/// </summary>
static void CopySOAndUploadBuglySymbol(string bundleName, string version)
{
// 将release的so复制到bugly文件下
BFEditorUtils.CopyDir(ReleaseSOPath, BuglySOPath);
// 开启Thread处理打包so和上传
TryCloseUploadThread();
var args = "bugly.sh" + " " + BUGLY_APP_ID + " " + BUGLY_APP_KEY + " " + bundleName + " " + version;
buglyUploadThread = new Thread(new ThreadStart(() => { UploadBuglySymbol(args); }));
buglyUploadThread.IsBackground = true;
buglyUploadThread.Start();
}
static void UploadBuglySymbol(string args)
{
BFEditorUtils.RunCommond("sh", args, BuglyPath,
(msg) =>
{
Debug.Log("UploadBuglySymbol: " + msg);
},
(errorMsg) =>
{
Debug.LogError("[bferror] UploadBuglySymbol: " + errorMsg);
});
TryCloseUploadThread();
}
static void TryCloseUploadThread()
{
if (buglyUploadThread != null)
{
var tmp = buglyUploadThread;
buglyUploadThread = null;
tmp.Abort();
}
}
public static void ConvertToAAB()
{
var installTimePackDirPath = Path.Combine(PublishAsProjectPath, "../", "dz_google_abb", "install_time_pack");
var targetDirPath = Path.Combine(PublishAsProjectPath, "install_time_pack");
if (Directory.Exists(targetDirPath))
{
Directory.Delete(targetDirPath, true);
Directory.CreateDirectory(targetDirPath);
}
else
{
Directory.CreateDirectory(targetDirPath);
}
BFEditorUtils.CopyDir(installTimePackDirPath, targetDirPath);
var abDirPath = Path.Combine(PublishAsProjectPath, "unityLibrary/src/main/assets");
var destFolderName = Path.Combine(PublishAsProjectPath, "install_time_pack/src/main/assets");
var dirInfo = new DirectoryInfo(abDirPath);
var floders = dirInfo.GetDirectories();
for (var i = 0; i < floders.Length; i++)
{
if (AABInPackageFileHashSet.Contains(floders[i].Name))
{
continue;
}
var newDir = Path.Combine(destFolderName, floders[i].Name);
if (!Directory.Exists(newDir))
{
Directory.CreateDirectory(newDir);
}
BFEditorUtils.CopyDir(floders[i].FullName, newDir);
Directory.Delete(floders[i].FullName, true);
}
var files = dirInfo.GetFiles();
for (var i = 0; i < files.Length; i++)
{
var file = files[i];
if (AABInPackageFileHashSet.Contains(file.Name))
{
continue;
}
var destFile = Path.Combine(destFolderName, file.Name);
if (File.Exists(destFile))
{
File.Delete(destFile);
}
File.Move(file.FullName, destFile);
}
var settingsGradleFilePath = Path.Combine(PublishAsProjectPath, "settings.gradle");
var text = File.ReadAllText(settingsGradleFilePath);
var appendText = "include ':install_time_pack'";
if (!text.EndsWith(appendText))
{
text = text + "\n" + appendText;
}
File.WriteAllText(settingsGradleFilePath, text);
var buildGradlePath = Path.Combine(PublishAsProjectPath, "launcher/build.gradle");
var buildGradleText = File.ReadAllText(buildGradlePath);
var regex2 = new Regex("assetPacks = [\":install_time_pack\"]");
if (!regex2.IsMatch(buildGradleText))
{
var regex22 = new Regex("android {");
buildGradleText = regex22.Replace(buildGradleText, "android {\n assetPacks = [\":install_time_pack\"]");
}
File.WriteAllText(buildGradlePath, buildGradleText);
Debug.Log("Android转换为AAB工程完成");
}
}
}