Unity打iOS包之xcodeapi的使用
xcodeapi
https://github.com/onevcat/XUPorter
https://bitbucket.org/Unity-Technologies/xcodeapi
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode.Custom;
public class ProjectPostProcess
{
[PostProcessBuildAttribute(1)]
public static void OnPostProcessBuild(BuildTarget buildTarget, string pathToBuiltProject)
{
// 只处理IOS工程, pathToBuildProject会传入导出的ios工程的根目录
if (buildTarget != BuildTarget.iOS)
return;
// 创建工程设置对象
var projectPath = pathToBuiltProject + "/Unity-iPhone.xcodeproj/project.pbxproj";
PBXProject project = new PBXProject();
project.ReadFromFile(projectPath);
string targetGuid = project.TargetGuidByName("Unity-iPhone");
// 修改BITCODE设置的例子
project.SetBuildProperty(targetGuid, "ENABLE_BITCODE", "NO");
// 你可能会设置的--------------------------------------------------
//project.SetBuildProperty(targetGuid, "CODE_SIGN_IDENTITY", "p12证书的code_sign");
// 把证书设置设置为手动,即不使用Automatically manage signing
//project.SetTargetAttributes("ProvisioningStyle","Manual");
//project.SetBuildProperty(targetGuid, "PROVISIONING_PROFILE", "mobileprovison文件的UUID");
//project.SetBuildProperty(targetGuid, "PROVISIONING_PROFILE_SPECIFIER", "mobileprovison文件的Name");
//project.SetTeamId(targetGuid,"证书的TeamId");
//project.SetBuildProperty(targetGuid, "IPHONEOS_DEPLOYMENT_TARGET", "8.0");
// 添加framework
//project.AddFrameworkToProject(targetGuid, "StoreKit.framework", true);
//----------------------------------------------------------------
// 修改后的内容写回到配置文件
File.WriteAllText(projectPath, project.WriteToString());
// 修改Info.plist的示例
var plistPath = Path.Combine(pathToBuiltProject, "Info.plist");
var plist = new PlistDocument();
plist.ReadFromFile(plistPath);
// 增加字符串类型的设置
plist.root.SetString("fieldname", "value");
// 修改后的内容写回到文件Info.plist
plist.WriteToFile(plistPath);
}
}
关于这个方法的属性,网上的有些例子上写的是PostProcessBuild,其实只是PostProcessBuildAttribute的简写,意义是一样的,不需要纠结。
常用的的Property:
Property |
值 |
备注 |
---|---|---|
CODE_SIGN_IDENTITY |
p12证书的code_sign |
在钥匙串那里看双击钥匙串里已安装的证书最上面显示的标题就code_sign也叫“常用名称” |
DEVELOPMENT_TEAM |
证书的TeamId |
在钥匙串那里看,查看方法同上,“组织单位”的值就是TeamId使用project.SetTeamId接口进行设置 |
ENABEL_BITCODE |
YES / NO |
关于Bitcode:https://www.cnblogs.com/QianChia/p/6223047.html |
IPHONEOS_DEPLOYMENT_TARGET |
8.0/8.1/8.2/…/12.1 |
Unity打出来的默认是6.0目前最低必须设置为8.0以上 |
PRODUCT_BUNDLE_IDENTIFIER |
项目的bundleId |
com.xxx.xxxx |
PRODUCT_NAME |
项目的名称 |
|
PROVISIONING_PROFILE |
.mobileprovision的UUID |
用vim打开.mobileprovision文件然后查找UUID:在vim中输入/UUID,然后按回车键 |
PROVISIONING_PROFILE_SPECIFIER |
mobileprovision的Name |
用vim打开.mobileprovision文件然后查找Name:在vim中输入/UUID,然后按回车键 |
一步到位
如果想要一步到位,导出xcode后自动执行Archive和生产ipa,可以写好shell脚本自动运行
Archive脚本
xcodebuild -project “工程目录/Unity-iPhone.xcodeproj” -scheme Unity-iPhone archive -archivePath "Archive的目录"
生成ipa脚本
xcodebuild -project “工程目录/Unity-iPhone.xcodeproj” -exportArchive -exportFormat ipa -archivePath “Archive的目录” -exportPath “ipa的目录” -exportProvisioningProfile "mobileprovision文件的Name"
xcodebuild -exportArchive -exportOptionsPlist xxx/Info.plist -archivePath xxx.xcarchive -exportPath ~/Desktop/
例
xcodebuild -exportArchive -exportOptionsPlist …/ExportOptions.plist -archivePath …/testPacking.xcarchive -exportPath ./autoPackage -allowProvisioningUpdates
我们需要自己创建一个plist作为Export Options
写个工具类方便生成
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
class XcodeExportPlist
{
/// <summary>
/// 对应ios_build_config.json的exportArchiveMethod
/// </summary>
public static readonly string[] kMethods =
{
"app-store",
"enterprise",
"ad-hoc",
"development",
};
public static void GenFile(string filPath, string teamID, string method, string bundleID, string profileName)
{
string text = GenText(teamID, method, bundleID, profileName);
System.IO.File.WriteAllText(filPath, text);
GameLogger.LogGreen("GenFile: " + filPath);
}
static string GenText(string teamID, string method, string bundleID, string profileName)
{
var sb = new System.Text.StringBuilder();
sb.Append("<?xml version="1.0" encoding="UTF-8"?>n");
sb.Append("<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">n");
sb.Append("<plist version="1.0">n");
sb.Append("<dict>n");
AppendTeamID(sb, teamID);
AppendMethod(sb, method);
AppendUploadSymbols(sb, false);
AppendProfiles(sb, bundleID, profileName);
sb.Append("</dict>n");
sb.Append("</plist>n");
return sb.ToString();
}
static void AppendTeamID(System.Text.StringBuilder sb, string teamID, string ind = "t")
{
sb.Append(ind).Append("<key>teamID</key>n");
AppendPStr(sb, teamID, ind);
}
static void AppendMethod(System.Text.StringBuilder sb, string method, string ind = "t")
{
sb.Append(ind).Append("<key>method</key>n");
AppendPStr(sb, method, ind);
}
static void AppendUploadSymbols(System.Text.StringBuilder sb, bool isUploadSymbols, string ind = "t")
{
sb.Append(ind).Append("<key>uploadSymbols</key>n");
AppendPBool(sb, isUploadSymbols, ind);
}
static void AppendProfiles(System.Text.StringBuilder sb, string bundleID, string profilesName, string ind = "t")
{
Dictionary<string, string> dic = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(bundleID))
{
dic.Add(bundleID, profilesName);
}
if (dic.Count > 0)
{
sb.Append(ind).Append("<key>provisioningProfiles</key>n");
AppendPDict(sb, dic, ind);
}
}
static void AppendPStr(System.Text.StringBuilder sb, string s, string ind)
{
sb.Append(ind);
sb.Append("<string>");
sb.Append(s);
sb.Append("</string>n");
}
static void AppendPBool(System.Text.StringBuilder sb, bool b, string ind)
{
sb.Append(ind).Append(b ? "<true/>" : "<false/>").Append("n");
}
static void AppendPDict(System.Text.StringBuilder sb, Dictionary<string, string> key_value, string ind)
{
sb.Append(ind);
sb.Append("<dict>");
foreach (var item in key_value)
{
sb.Append("n").Append(ind);
sb.Append("<key>");
sb.Append(item.Key);
sb.Append("</key>");
sb.Append("n").Append(ind);
sb.Append("<string>");
sb.Append(item.Value);
sb.Append("</string>");
}
sb.Append("n").Append(ind);
sb.Append("</dict>n");
}
}// end class XcodeExportPlist
结合《Unity中C#如何执行cmd命令(System.Diagnostics.Process的使用)》
https://blog.csdn.net/linxinfa/article/details/52982384 用上面的XcodeExportPlist工具类生成exportOption.plist
public static string GenArchiveOptPlist()
{
var path = Application.dataPath.Replace("Assets", "Bin/exportOption.plist";
var teamId = "BQCHO456";
// "app-store","enterprise","ad-hoc","development"
var archiveMethod = "enterprise";
var bundleId = "com.linxinfa.test";
var provisionName = "linxinfaEnt2019614";
XcodeExportPlist.GenFile(path, teamId, archiveMethod, bundleId, provisionName);
return path;
}
可以通过C#执行shell命令
//var version = "1.0.0";
//var binPath = Application.dataPath + "/../Bin";
//var xcodeProjPath = binPath + "/XcodeProj";
//var xcodeArchPath = binPath + "/Archives/Unity-iPhone-archive.xcarchive";
//var outputPath = string.Format("{0}/{1}/", binPath, version);
//var appName = string.Format("mygame_v{0}_{1}.ipa", version, DateTime.Now.ToString("yyyyMMdd"));
private static void XCodeArchive(string xcodeProjPath, string xcodeArchPath)
{
Debug.Log("XCodeArchive Begin");
string args = string.Format("-project "{0}/Unity-iPhone.xcodeproj" -scheme Unity-iPhone archive -archivePath "{1}" ", xcodeProjPath, xcodeArchPath);
var r = EdtUtil.RunCmd("xcodebuild", args);
if (string.IsNullOrEmpty(r[0]) || r[0].LastIndexOf("ARCHIVE SUCCEEDED") < 0)
{
Debug.LogError("Build IPA : XCodeBuildArchive FAILEDn" + r[0]);
throw new Exception("Build IPA : XCodeBuildArchive FAILED");
}
Debug.LogGreen("Build IPA : XCodeBuildArchive OK");
}
private static void XCodeExportIPA(string xcodeProjPath, string xcodeArchPath, string outputPath, string ipaName)
{
// 生成plist
var expOptPath = GenArchiveOptPlist();
Debug.Log("XCodeExportIPA Beginn xcodeProjPath: " + xcodeProjPath + "nxcodeArchPath: " + xcodeArchPath + "noutputPath: " + outputPath);
string args = string.Format("-project "{0}/Unity-iPhone.xcodeproj" -exportArchive -archivePath "{1}" -exportPath "{2}" -exportOptionsPlist "{3}" ",
xcodeProjPath, xcodeArchPath, outputPath, expOptPath);
var r = EdtUtil.RunCmd("xcodebuild", args);
if (string.IsNullOrEmpty(r[0]) || r[0].LastIndexOf("EXPORT SUCCEEDED") < 0)
{
Debug.LogError("Build IPA : XCodeBuildIPA FAILEDn" + r[0]);
throw new Exception("Build IPA : XCodeBuildIPA FAILED");
}
Debug.LogGreen("Build IPA : XCodeBuild IPA OK");
// 重命名
File.Move(outputPath + "Unity-iPhone.ipa", outputPath + ipaName);
}
如果不放心,也可以到XCode进行手动ipa打包
Archive:
先检查Product->Scheme->Edit Scheme…中的设置是否正确,比如现在是要打的是面向外部玩家的版本,则要Run里面要选择Relaese,Archive里也要选择Release,记得勾选Archive的Options的Reveal Archive in Organizer,检查工程的General中的证书设置是否正确,检查工程的Images.xcassets中的AppIcon的图标设置是否正常
检查无误之后,就可以执行Archive了: Product->Archive
导出ipa:
Archive完之后,会自动弹出Organizer窗口,如果没有弹出来,则手动打开:Window->Organizer
然后点击Distribute App按钮,接下来一步一步Next,注意中间会让你选择一些选项,根据需求进行选择即可
补充:
如果要对不同的配置做修改,比如,单独修改Debug版本或者Release版本的配置项,那么就需要用BuildConfigByName先获取到指定的配置项,然后使用SetBuildPropertyForConfig设置对应的配置项,例如,单独修改debug版本的ENABLE_BITCODE设置:
string configGuid = project.BuildConfigByName("targetGuid", "Debug");
project.SetBuildPropertyForConfig(configGuid, "ENABLE_BITCODE", "NO");
另外,对于XUPorter的导出插件和native代码的功能,Unity 5之后可以直接把需要的文件放在Assets/Plugins/iOS目录下面,文件就会被自动导出到目标工程中,请参考Unity文档:https://docs.unity3d.com/Manual/PluginsForIOS.html 相关参考:https://www.cnblogs.com/pandawuwyj/p/6904770.html
- Android终端上视频转GIF的实现及GIF质量讨论
- Android手机上用户操作模拟方法的研究与实现
- Firefox内存释放重用漏洞高级利用(Pwn2Own2014、CVE-2014-1512)
- android 线程那点事
- android 向webview传值
- 用搜索神器Everything定位Webshell木马后门
- 终端图像处理系列 - OpenGL ES 2.0 - 3D基础(矩阵投影)
- XssHtml – 基于白名单的富文本XSS过滤类
- fireeyee解剖新型Android恶意软件
- WordPress系统暴力破解测试工具 – wpbf
- RecyclerView notifyItem闪烁的问题
- 独家: iOS是如何收集用户的地理信息的
- Hygieia 为何物?DevOps 利器也
- 汽车攻击离你很近:一分钟变成汽车黑客
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- python 技术篇-时间戳的获取,记录程序处理时间
- 基于SpringBoot AOP面向切面编程实现Redis分布式锁
- Python+Selenium 技巧篇-svg标签内元素的xpath定位方式
- 小书MybatisPlus第5篇-Active Record模式精讲
- Python 语法问题-module ‘pip._internal‘ has no attribute ‘pep425tags‘. 原因及解决办法,32位、64位查看pip支持万能方法
- 总在说 Spring Boot 内置了 Tomcat 启动,那它的原理你说的清楚吗?
- JavaScript 技巧篇-js增加延迟时间解决单击双击事件冲突,双击事件触发单击事件
- Python 技术篇-sha256()加密的使用方法,sha1、md5加密方法
- Python 技术篇-如何打印一段文字,用友云霸气控制台颜文字打印
- Python 技术篇-获取秒级时间戳、毫秒级时间戳
- Python 技术篇-基于随机数的uuid码的生成
- BAT 脚本技术-利用bat批处理脚本静态指定ip地址、自动获取ip地址设置
- Oracle 技巧篇-快速批量删除当前数据库连接的用户,一键清空所有session会话方法
- Spring Security 中如何细化权限粒度?
- 小书MybatisPlus第4篇-表格分页与下拉分页查询