之前写了一篇关于Unity安卓一键打包,那个是打出apk应用的,后面也说了平台要弄聚合SDK,需要我们这边出母包,也就是AAR。为了省去重复劳动(主要是我工作电脑上用Unity2017.4.7f1版本不管用Gradle打apk还是导出Android Studio工程,都会卡在done这,然后Unity就卡死了),所以专门花了一天时间研究下写了个一键生成AAR包的工程。
其实没什么难点,核心思想就是导出Android Studio Project,这个可以用Unity自带的API来实现,然后通过IO操作修改一些文件,比如build.gradle和AndroidManifest.xml这2个文件。最后再用批处理生成AAR就行。
有些比较重要的我都写了注释,所以也就懒得解释了。Unity自带的API导出Android Studio Project工程的地方需要注意下,注意BuildOptions参数和输出文件夹,不然会出错的。其实还有一段是对c#脚本的IO处理,也就是修改外网包和开发包几个变量的代码,因为涉及到项目了,所以也就不放出这些相关接口。
public class OneKeyBuildAAR
{
private static string androidProjectName = "test2018";
private static Dictionary andoridFiles;
private static List androidNeedUpdateFile = new List() { "build.gradle", "AndroidManifest.xml" };
[MenuItem("一键打包/一键生成AAR")]
public static void OneKeyExportAAR()
{
BuildAndroidStudioProject(false);
BuildAAR();
}
[MenuItem("一键打包/1. 导出Android Studio工程")]
public static void ExportAndroidStudioProject()
{
BuildAndroidStudioProject(true);
}
[MenuItem("一键打包/2. 生成AAR")]
public static void ExportAAR()
{
BuildAAR();
}
///
/// 导出Android Studio工程
///
/// 是否自动打开文件夹
///
private static bool BuildAndroidStudioProject(bool bIsOpenDirectory)
{
if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android)
{
UnityEditor.EditorUtility.DisplayDialog("打AAR包失败", "请切换到Android平台", "确认");
return false;
}
// 设置成Gradle打包
EditorUserBuildSettings.androidBuildType = AndroidBuildType.Release;
EditorUserBuildSettings.buildScriptsOnly = false;
EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle;
EditorUserBuildSettings.exportAsGoogleAndroidProject = true;
// 设置productName,因为如果设置为中文名的话,导出的工程就是中文名,后面生成AAR会失败
var productName = PlayerSettings.productName;
PlayerSettings.productName = androidProjectName;
var scenes = AutoBuilder.GetScenePaths();
var publisPath = GetPublishPath();
if (Directory.Exists(publisPath))
Directory.Delete(publisPath, true);
// BuildOptions必须设置为AcceptExternalModificationsToPlayer,不然的话会打出不带后缀的文件,不是导出AndroidStudio工程
var message = BuildPipeline.BuildPlayer(scenes, publisPath, BuildTarget.Android, BuildOptions.AcceptExternalModificationsToPlayer);
PlayerSettings.productName = productName;
if (!string.IsNullOrEmpty(message))
{
UnityEngine.Debug.LogError("build message: " + message);
return false;
}
else
UnityEngine.Debug.Log("Build Android Studio Project: Succeed ");
if(bIsOpenDirectory)
OpenDirectory(publisPath);
return true;
}
///
/// 生成AAR
///
private static void BuildAAR()
{
var projectPath = GetPublishPath() + "/" + androidProjectName;
if(!Directory.Exists(projectPath))
{
UnityEngine.Debug.LogError("Android Studio Project was not found: " + projectPath);
return;
}
if(SetupAndroidStudioProject(projectPath))
{
int lastIndex = Application.dataPath.LastIndexOf("/");
string path = Application.dataPath.Substring(0, lastIndex) + "/BulidAAR";
File.Copy(path + "/BuildAAR.bat", projectPath + "/BuildAAR.bat", true);
Process proc = System.Diagnostics.Process.Start(projectPath + "/BuildAAR.bat");
proc.WaitForExit();
}
}
///
/// 设置Android Studio工程一些参数
///
///
private static bool SetupAndroidStudioProject(string projectPath)
{
if (!Directory.Exists(projectPath))
{
UnityEngine.Debug.LogError("Android Studio Project was not found: " + projectPath);
return false;
}
if (null == andoridFiles)
andoridFiles = new Dictionary();
else
andoridFiles.Clear();
FindFiles(andoridFiles, projectPath, androidNeedUpdateFile);
var count = 0;
foreach (var temp in andoridFiles)
{
//UnityEngine.Debug.Log("key: " + temp.Key + "---value: " + temp.Value);
if (temp.Key.Equals("build.gradle"))
{
if(UpdateBuildGradle(temp.Value))
{
++count;
UnityEngine.Debug.Log("修改了" + temp.Key + "文件");
}
}
if(temp.Key.Equals("AndroidManifest.xml"))
{
if (UpdateAndroidManifest(temp.Value))
{
++count;
UnityEngine.Debug.Log("修改了" + temp.Key + "文件");
}
}
}
if(androidNeedUpdateFile.Count != count)
{
UnityEngine.Debug.Log("有文件漏改了");
return false;
}
return true;
}
///
/// 修改build.gradle
///
///
///
private static bool UpdateBuildGradle(string filePath)
{
if (!File.Exists(filePath))
return false;
try
{
StreamReader reader = new StreamReader(filePath);
var content = reader.ReadToEnd().Trim();
reader.Close();
if(content.Contains("com.android.application")) // 将Application改成Library
content = content.Replace("com.android.application", "com.android.library");
if(!content.Contains("//applicationId") && content.Contains("applicationId"))//去掉applicationId
content = content.Replace("applicationId", "//applicationId");
StreamWriter writer = new StreamWriter(new FileStream(filePath, FileMode.Create));
writer.WriteLine(content);
writer.Flush();
writer.Close();
}
catch (Exception e)
{
UnityEngine.Debug.Log("UpdateAndroidManifest - Failed: " + e.Message);
return false;
}
return true;
}
///
/// 修改AndroidManifest.xml
///
///
///
private static bool UpdateAndroidManifest(string filePath)
{
if (!File.Exists(filePath))
return false;
try
{
StreamReader reader = new StreamReader(filePath);
var content = reader.ReadToEnd().Trim();
reader.Close();
//去掉 intent-filter 标签
var firstStr = "";
var secondStr = " ";
var firstIndex = content.IndexOf(firstStr);
var lastIndex = content.LastIndexOf(secondStr);
var count = lastIndex - firstIndex + secondStr.Length;
if(firstIndex > 0 && count > 0)
content = content.Remove(firstIndex, count);
StreamWriter writer = new StreamWriter(new FileStream(filePath, FileMode.Create));
writer.WriteLine(content);
writer.Flush();
writer.Close();
}
catch(Exception e)
{
UnityEngine.Debug.Log("UpdateAndroidManifest - Failed: " + e.Message);
return false;
}
return true;
}
private static void FindFiles(Dictionary findFiles, string path, List needFindFiles)
{
if (!Directory.Exists(path))
return;
DirectoryInfo dirInfo = new DirectoryInfo(path);
var files = dirInfo.GetFileSystemInfos();
for (int i = 0; i < files.Length; ++i)
{
var fileName = files[i].Name;
if (fileName.Contains("."))//文件
{
var fileFullName = files[i].FullName;
for (int j = 0; j < needFindFiles.Count; ++j)
{
if(fileName.Equals(needFindFiles[j]))
{
if (findFiles.ContainsKey(fileName))
findFiles[fileName] = fileFullName;
else
findFiles.Add(fileName, fileFullName);
}
}
}
else//文件夹
FindFiles(findFiles, files[i].FullName, needFindFiles);
}
}
///
/// Android Studio工程输出路径
/// 使用这个路径(Application.dataPath + "/.."),会失败,说是(Build Path same as Project Path)
///
///
public static string GetPublishPath()
{
int lastIndex = Application.dataPath.LastIndexOf("/");
string publishPath = Application.dataPath.Substring(0, lastIndex) + "/output";
//UnityEngine.Debug.LogError("publishPath: " + publishPath);
return publishPath;
}
#region 打开文件夹
public static void OpenDirectory(string path)
{
// 新开线程防止锁死
Thread newThread = new Thread(new ParameterizedThreadStart(CmdOpenDirectory));
newThread.Start(path);
}
private static void CmdOpenDirectory(object obj)
{
Process p = new Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c start " + obj.ToString();
//UnityEngine.Debug.Log(p.StartInfo.Arguments);
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;
p.Start();
p.WaitForExit();
p.Close();
}
#endregion
}
代码中有用到一个批处理文件(BuildAAR.bat),代码也放出来,也很简单,就2句代码。
cd /d %~dp0
start gradle clean assembleRelease
参考了这篇文章unity打aar包工具,大佬是用Android Studio工程生成AAR的,而且还是用java写的,java窗口怎么生成还要用什么IDE的完全不清楚(没学过),只好用c#写,本来还打算用批处理来处理IO操作的,说尽量简单点,那就没办法了。
注意:
用了一段时间没什么大问题,但因为.gradle的缓存文件在C盘,导致C盘快炸了,只剩下5个g的内存。所以只好删除缓存文件,切换到其他盘区。修改到F盘后,发现用AndroidStudio可以编译出AAR,用批处理打AAR失败,一直在报这个错误Release resolve files of classpath。网上搜了半天都没找到解决的方法,看到一张图用的是Use local gradle distribution,在想是不是这个问题,测试下,发现没问题,标记下,省的以后花很多时间来重复这个问题。