Unity的AndroidManifest中hardwareAccelerated被覆盖问题

问题分析:

​ 从事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

你可能感兴趣的:(Unity的AndroidManifest中hardwareAccelerated被覆盖问题)