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 = "Knights 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();
}
}
}
}