利用Unity提供的PBXObject来自动化iOS工程设置
1.概论近来看了点关于Unity导出iOS项目的相关资料,没有看到一个非常全的脚本来进行自动化项目设置,经过几天的尝试,最终产出了这篇文章。Unity提供了非常强大的PBXObject利用C#代码来设置iOS工程的各种设置,官方文档连接:https://docs.unity3d.com/2018.4/Documentation/ScriptReference/iOS.Xcode.PBXProject.html,脚本写好后放在Unity工程里即可,导出的iOS工程可以直接运行,不用再各种设置才能运行了,非常方便,文章第4条.完整示例贴了一个完整的文件供大家参考,项目使用的Unity版本为2018.4.20f。
2.详细介绍
a.获取一个iOS工程对象
通过OnPostprocessBuild方法里面的path可以很简单的得到一个PBXObject对象
public static void OnPostprocessBuild(BuildTarget buildTarget, string path){string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj";PBXProject proj = new PBXProject();proj.ReadFromFile(projPath);}b.通过PBXObject获取到目标Target
基本上所有的设置都会在这个目标里完成,懂iOS开发的就知道这个东西我们设置签名,路径(Framework search path、Library search path、Other link flags)等的地方
string target = proj.TargetGuidByName("Unity-iPhone");c.利用target设置具体的参数
c1.设置自动签名
proj.SetBuildProperty(target, "CODE_SIGN_IDENTITY", "Apple Development");proj.SetBuildProperty(target, "CODE_SIGN_STYLE", "Automatic");proj.SetTeamId(target, teamId); //teamId 是对应开发者正好的团队id (在苹果后台可以看到)c2.添加系统的Framework
proj.AddFrameworkToProject(target, "AdSupport.framework", true);proj.AddFrameworkToProject(target, "CoreTelephony.framework", true);proj.AddFrameworkToProject(target, "StoreKit.framework", true); //内购需要 否则PBXCapabilityType.InAppPurchase会加不上c3.设置bitcode和Other link flags
// 设置 BitCodeproj.SetBuildProperty(target, "ENABLE_BITCODE", "false");// 设置 other link flags -ObjCproj.AddBuildProperty (target, "OTHER_LDFLAGS", "-ObjC");c3.添加系统的tbd库 (和添加Framework不一样)
// 自定义一个添加方法private static void AddLibToProject(PBXProject proj, string target, string lib) { string file = proj.AddFile("usr/lib/" + lib, "Frameworks/" + lib, PBXSourceTree.Sdk); proj.AddFileToBuild(target, file);}// 调用AddLibToProject(proj,target,"libc++.1.tbd");AddLibToProject(proj,target,"libresolv.tbd");c4.添加自定义的动态库(Embed&Sign)
string defaultLocationInProj = Application.dataPath+"/Editor/Plugins/iOS"; //framework 存放的路径const string coreFrameworkName = "boxjing.framework";// framework 的文件名string framework = Path.Combine(defaultLocationInProj, coreFrameworkName);string fileGuid = proj.AddFile(framework, "Frameworks/" + coreFrameworkName, PBXSourceTree.Sdk);PBXProjectExtensions.AddFileToEmbedFrameworks(proj, target, fileGuid);proj.SetBuildProperty(target, "LD_RUNPATH_SEARCH_PATHS", "$(inherited) @executable_path/Frameworks");c5.添加内购和推送,这里需要把正确运行工程的entitlements文件复制到工程里一份,后面让代码自动再复制进iOS工程里使用
proj.AddCapability(target, PBXCapabilityType.InAppPurchase);// 添加 Capabilities-推送string entitlement = Application.dataPath+ "/Editor/AppleNative/Unity-iPhone.entitlements";File.Copy(entitlement, path+"/Unity-iPhone.entitlements");proj.AddCapability(target, PBXCapabilityType.PushNotifications,"Unity-iPhone.entitlements",true);d.修改Info.plist文件
正常的工程打包时候都需要设置version和build,以及添加项目所使用的某些权限描述,如相册、麦克风等,其根本都是修改Info.plist文件
Info.plist文件比较特殊,其根本就是一个map,方便修改可以直接将其读出来作为Map进行操作:
string plistPath = path + "/Info.plist";PlistDocument plist = new PlistDocument();plist.ReadFromString(File.ReadAllText(plistPath));PlistElementDict infoDict = plist.root;
在所有的修改完毕后,需要调用保存:
File.WriteAllText(plistPath, plist.WriteToString());d1.修改version和build
infoDict.SetString("CFBundleShortVersionString",version); //versioninfoDict.SetString("CFBundleVersion",build); //buildd2.增加权限描述
// 权限根据自己的项目 修改后面的提示文案infoDict.SetString("NSLocationWhenInUseUsageDescription", "为了发现周围的好友,请允许App访问您的地里位置"); //地理位置infoDict.SetString("NSPhotoLibraryUsageDescription", "为了能选择照片进行上传,请允许App访问您的相册"); //相册infoDict.SetString("NSMicrophoneUsageDescription", "为了能够录制声音,请允许App访问您的麦克风权限"); //麦克风3.特殊需求
在开发的过程中可能会遇到一下,需要修改iOS原生代码来实现的功能,比如项目中集成了极光推送,需要在UnityAppController.mm中增加一些代码和在Preprocessor.h中修改一个宏定义值来实现,这里可以提供两种办法,一种是用脚本直接用另一个写好的UnityAppController.mm文件替换Unity打包出来的工程里面的文件,还有一种就是直接在文件中进行字符串替换。
a.整个文件的替换
string UnityAppControllerMM = Application.dataPath + "/Editor/AppleNative/UnityAppController.mm"; //获取到我们自己的代码文件路径 string tagUnityAppControllerMM= path + "/Classes/UnityAppController.mm"; //要替换掉工程里面的文件路径if (File.Exists(tagUnityAppControllerMM)) //如果有 先删除掉 再复制过去{ File.Delete(tagUnityAppControllerMM);}File.Copy(UnityAppControllerMM, tagUnityAppControllerMM);b.替换部分代码或者在某一行后面插入代码
这里需要封装一个文件操作类,方便修改代码:
// 定义文件更新类public partial class XFileClass : System.IDisposable{ private string filePath; public XFileClass(string fPath) //通过文件路径初始化对象 { filePath = fPath; if( !System.IO.File.Exists( filePath ) ) { Debug.LogError( filePath +"该文件不存在,请检查路径!" ); return; } } // 替换某些字符串 public void ReplaceString(string oldStr,string newStr,string method="") { if (!File.Exists (filePath)) { return; } bool getMethod = false; string[] codes = File.ReadAllLines (filePath); for (int i=0; i<codes.Length; i++) { string str=codes.ToString(); if(string.IsNullOrEmpty(method)) { if(str.Contains(oldStr))codes.SetValue(newStr,i); } else { if(!getMethod) { getMethod=str.Contains(method); } if(!getMethod)continue; if(str.Contains(oldStr)) { codes.SetValue(newStr,i); break; } } } File.WriteAllLines (filePath, codes); } // 在某一行后面插入代码 public void WriteBelowCode(string below, string text) { StreamReader streamReader = new StreamReader(filePath); string text_all = streamReader.ReadToEnd(); streamReader.Close(); int beginIndex = text_all.IndexOf(below); if(beginIndex == -1){ return; } int endIndex = text_all.LastIndexOf("\n", beginIndex + below.Length); text_all = text_all.Substring(0, endIndex) + "\n"+text+"\n" + text_all.Substring(endIndex); StreamWriter streamWriter = new StreamWriter(filePath); streamWriter.Write(text_all); streamWriter.Close(); } public void Dispose() { }}
使用的时候非常方便:
//Preprocessor.h文件 XFileClass Preprocessor = new XFileClass(filePath + "/Classes/Preprocessor.h"); //在指定代码后面增加一行代码Preprocessor.WriteBelowCode("要增加的位置前一行代码","要增加的代码"); //在指定代码中替换一行 允许远程推送Preprocessor.ReplaceString("#define UNITY_USES_REMOTE_NOTIFICATIONS 0","#define UNITY_USES_REMOTE_NOTIFICATIONS 1");4.完整示例
using System.Collections;using System.Collections.Generic;using UnityEngine;using System.IO;using UnityEditor;using UnityEditor.Callbacks;using UnityEditor.iOS.Xcode;using UnityEditor.iOS.Xcode.Extensions;using System.Text.RegularExpressions;#if UNITY_IOS || UNITY_EDITORpublic class XcodeBuildPostprocessor{ // public static void OnPostprocessBuild(BuildTarget buildTarget, string path) { if (buildTarget == BuildTarget.iOS) { UnityEngine.Debug.Log("XCodePostProcess: Starting to perform post build tasks for iOS platform."); // 修改项目设置 如Bitcode Framework *tbd 等 ProjectSetting(path,"xxxxxxx"); // 后面参数TeamId 换成自己的TeamID 用来自动签名 // 修改Info.Plist文件如权限versionbuild InfoPlist(path,"1.0.0","20"); //后面两个参数依次为 version、build // 替换原生代码文件 // ReplaceNativeCodeFile(path);//替换文件如 xxx的UnityAppController.mm (加了推送相关代码)也可以使用EditNativeCode里面的方法实现 // 修改原生代码文件里的部分代码 追加、插入和替换 EditNativeCode(path); //修改文件中的代码如 xxx的Preprocessor.h中UNITY_USES_REMOTE_NOTIFICATIONS 改为1 推送使用 } } private static void ProjectSetting(string path,string teamId) { // 主要官方文档https://docs.unity3d.com/cn/2018.4/ScriptReference/iOS.Xcode.PBXProject.html string projPath = path + "/Unity-iPhone.xcodeproj/project.pbxproj"; PBXProject proj = new PBXProject(); proj.ReadFromFile(projPath); string target = proj.TargetGuidByName("Unity-iPhone"); // 证书设置自动签名 proj.SetBuildProperty(target, "CODE_SIGN_IDENTITY", "Apple Development"); proj.SetBuildProperty(target, "CODE_SIGN_STYLE", "Automatic"); proj.SetTeamId(target, teamId); // add extra framework(s) proj.AddFrameworkToProject(target, "AdSupport.framework", true); proj.AddFrameworkToProject(target, "CoreTelephony.framework", true); proj.AddFrameworkToProject(target, "StoreKit.framework", true); //内购需要 否则PBXCapabilityType.InAppPurchase会加不上 // XXXX的 tersafe2.framework 动态库 需要设置 Embed&Sign string defaultLocationInProj = Application.dataPath+"/Editor/Plugins/iOS"; //framework 存放的路径 const string coreFrameworkName = "tersafe2.framework";// framework 的文件名 string framework = Path.Combine(defaultLocationInProj, coreFrameworkName); string fileGuid = proj.AddFile(framework, "Frameworks/" + coreFrameworkName, PBXSourceTree.Sdk); PBXProjectExtensions.AddFileToEmbedFrameworks(proj, target, fileGuid); proj.SetBuildProperty(target, "LD_RUNPATH_SEARCH_PATHS", "$(inherited) @executable_path/Frameworks"); // 添加 Capabilities-内购 proj.AddCapability(target, PBXCapabilityType.InAppPurchase); // 添加 Capabilities-推送 string entitlement = Application.dataPath+ "/Editor/AppleNative/Unity-iPhone.entitlements"; File.Copy(entitlement, path+"/Unity-iPhone.entitlements"); proj.AddCapability(target, PBXCapabilityType.PushNotifications,"Unity-iPhone.entitlements",true); // 自定义资源 如自定义代码中使用到的图片资源等 // AddCustomResource(path,proj,target,"xxx.png"); // 设置 BitCode proj.SetBuildProperty(target, "ENABLE_BITCODE", "false"); // 设置 other link flags -ObjC proj.AddBuildProperty (target, "OTHER_LDFLAGS", "-ObjC"); // add other files like *.tbd AddLibToProject(proj,target,"libc++.1.tbd"); AddLibToProject(proj,target,"libresolv.tbd"); // 保存 File.WriteAllText(projPath, proj.WriteToString()); } // 添加lib方法 private static void AddLibToProject(PBXProject proj, string target, string lib) { string file = proj.AddFile("usr/lib/" + lib, "Frameworks/" + lib, PBXSourceTree.Sdk); proj.AddFileToBuild(target, file); } // 添加自定义资源如自定义代码中使用的图片资源放在 Assets/Editor/AppleNative 中 private static void AddCustomResource(string path, PBXProject proj, string target, string fileName) { string customImagePath = Application.dataPath+ "/Editor/AppleNative/"+fileName; File.Copy(customImagePath, path+"/"+fileName); string file = proj.AddFile(path+"/"+fileName, fileName , PBXSourceTree.Source); proj.AddFileToBuild(target, file); } // 修改Info.Plist private static void InfoPlist(string path,string version,string build) { string plistPath = path + "/Info.plist"; PlistDocument plist = new PlistDocument(); plist.ReadFromString(File.ReadAllText(plistPath)); // Get root PlistElementDict infoDict = plist.root; // 版本 infoDict.SetString("CFBundleShortVersionString",version); //version infoDict.SetString("CFBundleVersion",build); //build // 权限 infoDict.SetString("NSLocationWhenInUseUsageDescription", "为了发现周围的好友,请允许App访问您的地里位置"); //地理位置 infoDict.SetString("NSPhotoLibraryUsageDescription", "为了能选择照片进行上传,请允许App访问您的相册"); //相册 // infoDict.SetString("NSMicrophoneUsageDescription", "为了能够录制声音,请允许App访问您的麦克风权限"); //麦克风 // 设置BackgroundMode 远程推送 PlistElementArray bmArray = null; if (!infoDict.values.ContainsKey("UIBackgroundModes")) bmArray = infoDict.CreateArray("UIBackgroundModes"); else bmArray = infoDict.values["UIBackgroundModes"].AsArray(); bmArray.values.Clear(); bmArray.AddString("remote-notification"); // 允许HTTP请求 不设置的话 只能全部HTTPS请求 var atsKey = "NSAppTransportSecurity"; PlistElementDict dictTmp = infoDict.CreateDict(atsKey); dictTmp.SetBoolean("NSAllowsArbitraryLoads", true); /*------如果要设置scheme白名单 请放开该段 追加自己需要添加的白名单 下面举例为微信分享所需要的白名单-----*/ /* // 增加白名单 scheme 打开别的app需要比如 分享到微信 需要添加 wechat、weixin、weixinULAPI PlistElement array = null; if (infoDict.values.ContainsKey("LSApplicationQueriesSchemes")) { array = infoDict["LSApplicationQueriesSchemes"].AsArray(); } else { array = infoDict.CreateArray("LSApplicationQueriesSchemes"); } infoDict.values.TryGetValue("LSApplicationQueriesSchemes", out array); PlistElementArray Qchemes = array.AsArray(); Qchemes.AddString("wechat"); Qchemes.AddString("weixin"); Qchemes.AddString("weixinULAPI"); */ // 保存 File.WriteAllText(plistPath, plist.WriteToString()); } // 替换文件 private static void ReplaceNativeCodeFile(string path) { string UnityAppControllerMM = Application.dataPath + "/Editor/AppleNative/UnityAppController.mm"; string tagUnityAppControllerMM= path + "/Classes/UnityAppController.mm"; if (File.Exists(tagUnityAppControllerMM)) { File.Delete(tagUnityAppControllerMM); } File.Copy(UnityAppControllerMM, tagUnityAppControllerMM); } // 修改部分代码 private static void EditNativeCode(string filePath) { //Preprocessor.h文件 XFileClass Preprocessor = new XFileClass(filePath + "/Classes/Preprocessor.h"); //在指定代码后面增加一行代码 // Preprocessor.WriteBelow("要增加的位置前一行代码","要增加的代码"); //在指定代码中替换一行xxxxxx 允许远程推送 Preprocessor.ReplaceString("#define UNITY_USES_REMOTE_NOTIFICATIONS 0","#define UNITY_USES_REMOTE_NOTIFICATIONS 1"); //在指定代码后面增加一行 // Preprocessor.WriteBelowCode("UnityCleanup();\n}","- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url\r{\r return ;\r}"); XFileClass AppController = new XFileClass(filePath + "/Classes/UnityAppController.mm"); AppController.WriteBelow("#include <sys/sysctl.h>","\n#import \"JPUSHService.h\""); AppController.WriteBelow("UnitySendDeviceToken(deviceToken);"," ;"); } // 定义文件更新类 public partial class XFileClass : System.IDisposable { private string filePath; public XClass(string fPath) //通过文件路径初始化对象 { filePath = fPath; if( !System.IO.File.Exists( filePath ) ) { Debug.LogError( filePath +"该文件不存在,请检查路径!" ); return; } } // 替换某些字符串 public void ReplaceString(string oldStr,string newStr,string method="") { if (!File.Exists (filePath)) { return; } bool getMethod = false; string[] codes = File.ReadAllLines (filePath); for (int i=0; i<codes.Length; i++) { string str=codes.ToString(); if(string.IsNullOrEmpty(method)) { if(str.Contains(oldStr))codes.SetValue(newStr,i); } else { if(!getMethod) { getMethod=str.Contains(method); } if(!getMethod)continue; if(str.Contains(oldStr)) { codes.SetValue(newStr,i); break; } } } File.WriteAllLines (filePath, codes); } // 在某一行后面插入代码 public void WriteBelowCode(string below, string text) { StreamReader streamReader = new StreamReader(filePath); string text_all = streamReader.ReadToEnd(); streamReader.Close(); int beginIndex = text_all.IndexOf(below); if(beginIndex == -1){ return; } int endIndex = text_all.LastIndexOf("\n", beginIndex + below.Length); text_all = text_all.Substring(0, endIndex) + "\n"+text+"\n" + text_all.Substring(endIndex); StreamWriter streamWriter = new StreamWriter(filePath); streamWriter.Write(text_all); streamWriter.Close(); } public void Dispose() { } }}#endif写在结尾
后面可以再看看,能不能在这里面直接加入自动化打包ipa的脚本,这样的话,Unity开发者连Xcode开发工具都不用打开了,可以直接传内部测试或传App Store进行审核发布。
页:
[1]