问题分析:
从事Unity Android SDK开发前后近2年,大大小小问题遇到很多,R文件的,SDK抽取出错的,反编译才能修改的,各种各样奇葩的问题。让我极为蛋疼的却不多,这个硬件加速的算得上一个。
之前测试反馈,新打包的游戏播放视频广告时会黑屏,声音正常。开始不理解为什么,后面反编译后一个个对比,找到了问题,播放广告的Activity被添加了android:hardwareAccelerated=false
,硬件加速被禁用。即使主动添加并设置为true打包后也会被覆盖为false。别的SDK却不会,使用该SDK的其他游戏也是正常的。由此得出结论:
1.别的SDK在代码里进行了开启硬件加速,该SDK没有。
2.部分Unity游戏的某些设置禁用了硬件加速,导致合并后为false。
因此方案就有2个:
1.反编译该SDK,开启硬件加速。
2.找出Unity的设置修改为开启。
显然方案1难度不小,且在后续SDK更新后又需要重复修改,尤其在SDK混淆的情况下,极为麻烦。因此采用方案2,但是经过协同Unity同事多方查找之后仍然没有找到有用的配置,猜测是组合配置,这样想自己摸索出来,可能性几乎为零。那么就还有另外2个退而求其次的方案:
1.导出Android工程开启硬件加速后再打包。
2.向SDK平台反馈问题,等待修复。
方案1最大的缺点就在于打包太麻烦,如果是偶尔打包还好,但因为公司的业务问题,每天需大量打包,显然此方案只是临时解决方案。而方案2的缺点就是等待时间不确定,毕竟有求于人嘛。很长时间以来,这个问题都是一个无法言说的痛,直到今天再次遇到这个问题,我再次进入搜索狂魔模式,终于在stackoverflow上找到了解决方案!!!
但该方案仍有缺陷——只对gradle打包方式有效,老游戏就没救了,只能使用导出Android工程的方式解决。不过在Unity 2018都发了几个版本的今天,奇葩如我供职的公司,老游戏也不是那么多,因此这个缺陷可以忽略不计了。
解决方案:
1.复制ModifyUnityAndroidAppManifestSample.cs script 到你的游戏 Assets/Editor/目录下。
2.在 AndroidManifest class中添加如下方法:
internal void SetHardwareAccel(){
GetActivityWithLaunchIntent().Attributes.Append(CreateAndroidAttribute("hardwareAccelerated", "true"));
}
3.在 OnPostGenerateGradleAndroidProject(string basePath)
回调方法中调用androidManifest.SetHardwareAccel()
方法。
using System.IO;
using System.Text;
using System.Xml;
using UnityEditor.Android;
public class ModifyUnityAndroidAppManifestSample : IPostGenerateGradleAndroidProject
{
public void OnPostGenerateGradleAndroidProject(string basePath)
{
// If needed, add condition checks on whether you need to run the modification routine.
// For example, specific configuration/app options enabled
var androidManifest = new AndroidManifest(GetManifestPath(basePath));
androidManifest.SetHardwareAccel();
// Add your XML manipulation routines
androidManifest.Save();
}
public int callbackOrder {
get { return 1; }
}
private string _manifestFilePath;
private string GetManifestPath(string basePath)
{
if (string.IsNullOrEmpty(_manifestFilePath))
{
var pathBuilder = new StringBuilder(basePath);
pathBuilder.Append(Path.DirectorySeparatorChar).Append("src");
pathBuilder.Append(Path.DirectorySeparatorChar).Append("main");
pathBuilder.Append(Path.DirectorySeparatorChar).Append("AndroidManifest.xml");
_manifestFilePath = pathBuilder.ToString();
}
return _manifestFilePath;
}
}
internal class AndroidXmlDocument : XmlDocument
{
private string m_Path;
protected XmlNamespaceManager nsMgr;
public readonly string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android";
public AndroidXmlDocument(string path)
{
m_Path = path;
using (var reader = new XmlTextReader(m_Path))
{
reader.Read();
Load(reader);
}
nsMgr = new XmlNamespaceManager(NameTable);
nsMgr.AddNamespace("android", AndroidXmlNamespace);
}
public string Save()
{
return SaveAs(m_Path);
}
public string SaveAs(string path)
{
using (var writer = new XmlTextWriter(path, new UTF8Encoding(false)))
{
writer.Formatting = Formatting.Indented;
Save(writer);
}
return path;
}
}
internal class AndroidManifest : AndroidXmlDocument
{
private readonly XmlElement ApplicationElement;
public AndroidManifest(string path) : base(path)
{
ApplicationElement = SelectSingleNode("/manifest/application") as XmlElement;
}
private XmlAttribute CreateAndroidAttribute(string key, string value)
{
XmlAttribute attr = CreateAttribute("android", key, AndroidXmlNamespace);
attr.Value = value;
return attr;
}
internal XmlNode GetActivityWithLaunchIntent()
{
return SelectSingleNode("/manifest/application/activity[intent-filter/action/@android:name='android.intent.action.MAIN' and " +
"intent-filter/category/@android:name='android.intent.category.LAUNCHER']", nsMgr);
}
internal void SetApplicationTheme(string appTheme)
{
ApplicationElement.Attributes.Append(CreateAndroidAttribute("theme", appTheme));
}
internal void SetStartingActivityName(string activityName)
{
GetActivityWithLaunchIntent().Attributes.Append(CreateAndroidAttribute("name", activityName));
}
internal void SetHardwareAccel(){
GetActivityWithLaunchIntent().Attributes.Append(CreateAndroidAttribute("hardwareAccelerated", "true"));
}
}
然后再使用gradle打包,一切都完美了。
总结:
其实这个方案的原理很简单: Unity在使用gradle生成AndroidManifest之后,build apk之前会回调OnPostGenerateGradleAndroidProject(string basePath)
方法,此时对AndroidManifest做任何操作都不会再被覆盖了,不光android:hardwareAccelerated
,其他任意一切我们都可以修改。
但是这个简单的方案却需要对API相当熟悉,国外这位老哥向我展示了真正的技术,这里我搬运过来分享给其他那些一样被同类问题困扰的猿们,看到就是猿粪!同时也感谢stackoverflow上的pale bone老哥不吝惜分享技术,这里挂上stackoverflow 上的问题链接以示感谢。
https://stackoverflow.com/questions/45820447/unity-force-android-hardware-acceleration