using UnityEngine; using UnityEditor; using System.IO; using UnityEditor.Build.Reporting; using System.Text.RegularExpressions; using System; using System.Threading; 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 DevAsProjectPath = Application.dataPath + "/../BFVersions/android/dz_dev"; static string LanReleaseAsProjectPath = Application.dataPath + "/../BFVersions/android/dz_release"; static string GoogleAsProjectPath = Application.dataPath + "/../BFVersions/android/dz_google_apk"; static string GoogleCommonProjectPath = Application.dataPath + "/../BFVersions/android/google_common"; static string GPAsProjectPath = Application.dataPath + "/../BFVersions/android/ub-gp"; // gp删档测试渠道 static string GPOfficialAsProjectPath = Application.dataPath + "/../BFVersions/android/ub-google"; // gp正式渠道 static string SignShellPath = Application.dataPath + "/../BFFiles/androidkey"; static string GpAlginShellPath = Application.dataPath + "/../BFFiles/androidkey"; 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) { return BuildAndroidAPK(buildInfo); } else { Debug.LogError("[bferror]unity打包失败"); return false; } } /// /// 打包设置 /// static void BuildSettings(BuildInfo buildInfo) { // 设置bundleVersion PlayerSettings.bundleVersion = buildInfo.version; // 设置VersionCode PlayerSettings.Android.bundleVersionCode = buildInfo.version_code; // 设置竖屏 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); Debug.Log("[bfinfo]设置包名:" + buildInfo.bundleName); // 跳过版本控制 var symbols = ANDROID_DEFINE_SYMBOLS; if (buildInfo.skipVersion) { symbols = symbols + ";SKIP_VERSION;"; } PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, symbols); Debug.Log("[bfinfo]设置defineSymbols: " + symbols); // 是否是dev var development = buildInfo.IsDevChannel() ? true : false; EditorUserBuildSettings.development = development; // 商品名称 // 应用名 if (buildInfo.IsPublish()) { PlayerSettings.productName = "Knight Combo"; } else { PlayerSettings.productName = development ? "b6-dev" : "b6-release"; } // BuildType设置dev/release EditorUserBuildSettings.androidBuildType = development ? AndroidBuildType.Debug : AndroidBuildType.Release; // 是否导出as工程 // EditorUserBuildSettings.exportAsGoogleAndroidProject = development ? false : true; EditorUserBuildSettings.exportAsGoogleAndroidProject = true; // dev使用Mono release使用IL2CPP var scriptImp = development ? ScriptingImplementation.Mono2x : ScriptingImplementation.IL2CPP; PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, scriptImp); } /// /// 获取打包参数 /// static BuildPlayerOptions GetBuildOptions(BuildInfo buildInfo) { var bpOptions = new BuildPlayerOptions(); bpOptions.scenes = AssetBundleUtils.GetBuildScenes(); bpOptions.target = BuildTarget.Android; // if (buildInfo.IsDevChannel()) // { // bpOptions.locationPathName = GetDevApkPathName(buildInfo); // Debug.Log("[bfinfo]apk path : " + bpOptions.locationPathName); // } // else // { // bpOptions.locationPathName = GetASProjectPathName(); // Debug.Log("[bfinfo]asProject path : " + bpOptions.locationPathName); // } bpOptions.locationPathName = GetASProjectPathName(buildInfo); BuildOptions options; if (buildInfo.IsReleaseChannel()) { options = BuildOptions.None; } else { options = BuildOptions.Development; } 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, buildInfo.mode); if (Directory.Exists(dir)) { Directory.Delete(dir, true); } return dir; } static bool BuildAPK(BuildInfo buildInfo) { if (buildInfo.IsGPChannel()) { MergeGPToReleaseProject(buildInfo); FixGradleVersion(buildInfo.version_code, buildInfo.version); } if (buildInfo.IsLanRelease()) { AddBuglyParamsToReleaseProject(); } Debug.Log("[bfinfo]正在buildApk..."); var success = true; var args = ""; if (buildInfo.IsReleaseChannel()) { args += " assembleRelease"; } else { args += " assembleDebug"; } BFEditorUtils.RunCommond(GRADLE_PATH, args, GradleExcuteProjectPath, (msg) => { }, (errorMsg) => { if (errorMsg.Contains("BUILD FAILED") || errorMsg.Contains("FAILURE") || errorMsg.Contains("ERROR")) { success = false; Debug.LogError("[bferror] " + errorMsg); } }); if (buildInfo.IsGPChannel()) { // 尝试制作并上传bugly符号表 开新Thread不影响打包流程 if (success) { CopySOAndUploadBuglySymbol(buildInfo.bundleName, buildInfo.version); } } return success; } static bool BuildAAB(BuildInfo buildInfo) { var result = true; if(buildInfo.IsGPChannel()) { // MergeGPToReleaseProject(buildInfo); // FixGradleVersion(buildInfo.version_code, buildInfo.version); } return result; } static bool BuildAndroidAPK(BuildInfo buildInfo) { var result = true; if(buildInfo.IsDevChannel()) { MergeProject(buildInfo, DevAsProjectPath); } else if(buildInfo.IsLanRelease()) { MergeProject(buildInfo, LanReleaseAsProjectPath); } else if(buildInfo.IsGPChannel()) { MergeProject(buildInfo, GoogleAsProjectPath); } return result; } /// /// 合并dev工程 /// static void MergeProject(BuildInfo buildInfo, String asProjectPath) { Debug.Log("[bfinfo]正在整合dev project..."); var dir = Path.Combine(Application.dataPath, "../", AS_PROJECT_PATH, buildInfo.mode); var javaPath = Path.Combine(dir, "unityLibrary/src/main/java"); var manifestPath = Path.Combine(dir, "unityLibrary/src/main/AndroidManifest.xml"); // 获取到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); } 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); text = File.ReadAllText(manifestPath); regex = new Regex("REPLACE_BUILD_ID"); text = regex.Replace(text, buildIdLine); File.WriteAllText(manifestPath, text); } /// /// 合并gp工程 /// static void MergeGPToReleaseProject(BuildInfo buildInfo) { Debug.Log("[bfinfo]正在整合gp渠道sdk..."); var javaPath = GradleExcuteProjectPath + "/src/main/java"; var manifestPath = GradleExcuteProjectPath + "/src/main/AndroidManifest.xml"; // 获取到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.IsGPOfficial()) { BFEditorUtils.CopyDir(GPOfficialAsProjectPath, GradleExcuteProjectPath); } else { BFEditorUtils.CopyDir(GPAsProjectPath, GradleExcuteProjectPath); } var text = File.ReadAllText(manifestPath); var regex = new Regex("REPLACE_BUILD_ID"); text = regex.Replace(text, buildIdLine); File.WriteAllText(manifestPath, text); } /// /// fix versionCode versionName /// static void FixGradleVersion(int versionCode, string versionName) { Debug.Log("[bfinfo]修正build.gradle: VersionCode " + versionCode + " VersionName " + versionName); var gradleFilePath = Path.Combine(GradleExcuteProjectPath, "build.gradle"); var text = File.ReadAllText(gradleFilePath); var regex = new Regex("versionCode 1"); text = regex.Replace(text, string.Format("versionCode {0}", versionCode)); regex = new Regex("versionName '0.1'"); text = regex.Replace(text, string.Format("versionName '{0}'", versionName)); File.WriteAllText(gradleFilePath, text); } /// /// 内网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(); } } } }