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 AABInPackageFileHashSet = new HashSet() { "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); // } } /// /// 打包android /// 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; } } /// /// 打包设置 /// 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 = "Pull Pull Pull Heroes"; EditorUserBuildSettings.development = false; EditorUserBuildSettings.androidBuildType = AndroidBuildType.Release; // 是否导出as工程 EditorUserBuildSettings.exportAsGoogleAndroidProject = true; var scriptImp = ScriptingImplementation.IL2CPP; PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, scriptImp); } /// /// 获取打包参数 /// 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; } /// /// 合并工程 /// 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); } } /// /// fix versionCode versionName /// 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); } /// /// 内网Release包接入Bugly /// 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("\n" + "\n" + "\n" + "\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); } /// /// release包加密后签名 /// 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; } /// /// gp包对齐 /// 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; } /// /// google official包对齐 /// 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; } /// /// CopyRelease文件夹内的so到bugly文件夹,制作并上传Bugly符号表 /// 因是非出包必要步骤,且依赖网络环境和腾讯服务器,有时会失败,所以开一个Thread来处理 /// 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工程完成"); } } }