Version: 2020.3
准备应用程序进行应用内购 (IAP)
iOS 上的托管堆栈跟踪

应用程序精简

Apple 商店和 iOS 可根据用户具体设备的性能来定制 iOS、tvOS 和 watchOS 应用程序的分发,从而优化这些应用程序的安装。此优化称为应用程序精简。借助应用程序精简,可以使创建的应用程序利用最多的设备功能、占用最少的磁盘空间并适应 Apple 未来可能应用的更新。请参阅 Apple 关于应用程序精简的开发者库页面,了解有关此优化过程的更多信息。

本章介绍可以在 Unity 中实现的应用程序精简的两个主要组件:

按需加载资源

按需加载资源 (ODR) 是适用于 iOS 和 tvOS 平台(从 iOS 和 tvOS 9.0 版开始)的功能。此功能允许将核心资源(应用程序启动时需要的资源)与可选资源(或者出现在游戏后期关卡中的资源)分开,从而减小应用程序的大小。这些附加资源称为 AssetBundles。它们可用于所有 Unity 构建目标,但必须采取额外步骤才能通过 App Store 托管它们。

AssetBundles 可以包含资源文件,例如模型、材质、纹理和场景,但不能包含脚本。这意味着所有脚本逻辑都必须位于主应用程序中。为减少加载时间(特别是在无线条件下)并最大限度减少设备上使用的存储空间,Apple 建议 AssetBundle 的大小不要超过 64MB。

设置 ODR

要对项目进行 ODR 设置,请首先确认构建类型设置为 iOS。在菜单栏中,选择 File > Build Settings

如果尚未选中相关选项,请单击 iOS__,然后单击 Switch Platform__ 按钮。接下来,单击 Player Settings 按钮。在 Inspector 窗口中,打开 Other Settings__,导航到 Configuration__ 部分,然后启用 Use on demand resources 复选框。

创建 AssetBundle

首先,创建一个新文件夹以放置需要添加到 AssetBundle 的资源。要执行此操作,请在 Project 窗口内右键单击,然后选择 Create > Folder__(或单击 Project 窗口左上角的 Create__ > __Folder__)。

选择要添加到 AssetBundle 的资源文件,然后将它们拖放到新文件夹中。

创建 AssetBundle 时,需要为其分配一个标签,该标签将在请求下载 AssetBundle 时用作标识符。最好创建一个与捆绑包的文件名匹配的标签;这样可以确保标签的唯一性,并便于以后使用时进行识别。

要创建或分配标签,请选择新文件夹,然后导航到 Inspector 窗口底部的 Asset Labels 部分。单击左侧下拉菜单,选择 __New__,然后输入新标签的名称。请注意,AssetBundle 标签必须为小写字母。

在此示例中,一个名为 Textures 的文件夹被赋予了新标签

要生成新的 AssetBundle 文件,必须在 Editor 脚本中引用新标签。要创建 Editor 脚本,请在 Project 窗口中创建一个名为 Editor 的新文件夹。右键单击 Editor 文件夹,然后选择 Create > C# Script。将新脚本命名为 BuildiOSAssetBundles.cs

在此示例中,为 Textures 文件夹赋予了标签 textures。在 Editor 文件夹中创建了新脚本 BuildiOSAssetBundles.cs

打开 BuildiOSAssetBundles.cs 并将以下代码复制到其中。此示例中使用了标签 _textures_;请用所需的标签名称(小写文本)通改此名称。

请注意,此示例使用未压缩的捆绑包;但这并非应用程序精简的要求。

using UnityEngine;
using UnityEditor;


public class BuildiOSAssetBundles : MonoBehaviour
{
    [InitializeOnLoadMethod]
    static void SetupResourcesBuild( )
    {
        UnityEditor.iOS.BuildPipeline.collectResources += CollectResources;
    }

    static UnityEditor.iOS.Resource[] CollectResources( )
    {
        return new UnityEditor.iOS.Resource[]
        {
            new UnityEditor.iOS.Resource( "textures", "Assets/ODR/textures" ).AddOnDemandResourceTags( "textures" ),
            new UnityEditor.iOS.Resource( "bundle", "Assets/Bundles/bundle.unity3d" ).AddOnDemandResourceTags( "bundle" ),
        };
    }

    [MenuItem( "Bundle/Build iOS AssetBundle" )]
    static void BuildAssetBundles( )
    {
        var options = BuildAssetBundleOptions.None;

        bool shouldCheckODR = EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS;

# if UNITY_TVOS
            shouldCheckODR |= EditorUserBuildSettings.activeBuildTarget == BuildTarget.tvOS;
# endif

        if( shouldCheckODR )
        {
# if ENABLE_IOS_ON_DEMAND_RESOURCES
            if( PlayerSettings.iOS.useOnDemandResources )
                options |= BuildAssetBundleOptions.UncompressedAssetBundle;
# endif

# if ENABLE_IOS_APP_SLICING
            options |= BuildAssetBundleOptions.UncompressedAssetBundle;
# endif
        }

        BuildPipeline.BuildAssetBundles( "Assets/ODR", options, EditorUserBuildSettings.activeBuildTarget );
    }

}

上面代码示例中的重要代码行如下,其中使用带有 textures 标签的文件,并在 Assets/ODR 文件夹中创建名为 textures 的 AssetBundle 文件:

new UnityEditor.iOS.Resource( "textures", "Assets/ODR/textures" ).AddOnDemandResourceTags( "textures" )

出于演示目的,上面的代码示例还包括以下代码行,用于添加一个已经构建的名为 bundle 的 AssetBundle(例如,来自其他项目或第三方供应商):

new UnityEditor.iOS.Resource( "bundle", "Assets/Bundles/bundle.unity3d" ).AddOnDemandResourceTags( "bundle" )

整个代码示例将在 Unity Editor 菜单栏中创建一个新菜单。选择 Bundle > Build iOS AssetBundle。此时将在 ODR 文件夹中生成 AssetBundles。

以下脚本下载 textures ODR Asset Bundle,并将其分配给公共成员 TextureBundle。将此包放在项目的某个位置。

using UnityEngine;
using UnityEngine.iOS;
using System;
using System.Collections;

public class LoadBundle : MonoBehaviour
{
    public AssetBundle     TextureBundle;


    void Start( )
    {
        StartCoroutine( LoadAsset( "textures", "textures" ) );
    }

    public IEnumerator LoadAsset( string resourceName, string odrTag )
    {
        // 创建请求
        OnDemandResourcesRequest request = OnDemandResources.PreloadAsync( new string[] { odrTag } );

        // 等待请求完成
        yield return request;

        // 检查是否有错误
        if( request.error != null )
            throw new Exception( "ODR request failed: " + request.error );

        TextureBundle = AssetBundle.LoadFromFile( "res://" + resourceName );

        request.Dispose( );
    }
}

下一步是生成 Xcode 项目,构建 .IPA,并将其上传到 iTunes Connect 的 TestFlight。在 TestFlight 处理过程中,嵌入式 ODR AssetBundles 将从应用程序中删除并托管在 Apple 的服务器上,可供下载。

在 Xcode 中构建 .IPA 之前,请检查 XCode 的 Build Settings,确保 Assets 部分中的 Embed Asset packs In Product Bundle 设置为 No__,而 Enable On Demand Resources__ 设置为 Yes

iTunes Connect 处理完应用程序的上传后,单击 TestFlight Builds 中的构建版本即可查看相关的更多信息:

应用程序切片

应用程序切片遵循与按需加载资源类似的流程,允许根据运行应用程序的设备的规格动态下载资源(例如,下载用于 Retina iPad 的高分辨率资源,以及用于 iPhone 和 iPad Mini 等小型设备的低分辨率资源)。这是通过定义 AssetBundles 以及添加__变体__配置来实现的。这样可以在启动时决定使用哪种变体,并在下载时自动将其附加到资源文件名。

要创建变体,请单击新文件夹,然后导航到 Inspector 窗口底部的 Asset Labels 部分。单击右侧下拉菜单,选择 __New__,然后输入新变体的名称。请注意,AssetBundle 变体必须为小写字母。

必须在 Editor 脚本中引用新变体。要创建 Editor 脚本,请在 Project 窗口中创建一个名为 Editor 的新文件夹。右键单击 Editor 文件夹,然后选择 Create > C# Script。将新脚本命名为 BuildiOSAppSlices.cs

复制并粘贴下面的代码,将示例标签(“textures”)和变体(“hd”和“sd”)替换为您自己的标签和变体。在此代码示例中,引用了多个文件夹:一个包含 HD 纹理,一个包含 SD 纹理。它们分别被赋予了变体“hd”和“sd”。

using UnityEngine;
using UnityEditor;


public class BuildiOSAppSlices : MonoBehaviour
{
    [InitializeOnLoadMethod]
    static void SetupResourcesBuild( )
    {
        UnityEditor.iOS.BuildPipeline.collectResources += CollectResources;
    }

    static UnityEditor.iOS.Resource[] CollectResources( )
    {
        return new UnityEditor.iOS.Resource[]
        {
            new UnityEditor.iOS.Resource("textures").BindVariant( "Assets/ODR/textures.hd", "hd" )
                                     .BindVariant( "Assets/ODR/textures.sd", "sd" )
                     .AddOnDemandResourceTags( "textures" ),
    };
    }

    [MenuItem( "Bundle/Build iOS App Slices" )]
    static void BuildAssetBundles( )
    {
        var options = BuildAssetBundleOptions.None;

        bool shouldCheckODR = EditorUserBuildSettings.activeBuildTarget == BuildTarget.iOS;

# if UNITY_TVOS
            shouldCheckODR |= EditorUserBuildSettings.activeBuildTarget == BuildTarget.tvOS;
# endif

        if( shouldCheckODR )
        {
# if ENABLE_IOS_ON_DEMAND_RESOURCES
            if( PlayerSettings.iOS.useOnDemandResources )
                options |= BuildAssetBundleOptions.UncompressedAssetBundle;
# endif

# if ENABLE_IOS_APP_SLICING
            options |= BuildAssetBundleOptions.UncompressedAssetBundle;
# endif
        }

        BuildPipeline.BuildAssetBundles( "Assets/ODR", options, EditorUserBuildSettings.activeBuildTarget );
    }

}

这段代码将在 Unity Editor 菜单栏中创建一个名为 Bundle 的新菜单。单击此菜单并选择列表中唯一的菜单项 Build iOS App Slices。此时将在 ODR 文件夹中生成 AssetBundles。

然后,要加载资源,请将此类放在项目中的某个位置,并传入要加载的变体的名称:

using UnityEngine;
using UnityEngine.iOS;
using System;
using System.Collections;

public class LoadBundle : MonoBehaviour
{
    public AssetBundle     TextureBundle;


    void Start( )
    {
        StartCoroutine( LoadAsset( "textures", "textures" ) );
    }

    public IEnumerator LoadAsset( string resourceName, string odrTag )
    {
        // 创建请求
        OnDemandResourcesRequest request = OnDemandResources.PreloadAsync( new string[] { odrTag } );

        // 等待请求完成
        yield return request;

        // 检查是否有错误
        if( request.error != null )
            throw new Exception( "ODR request failed: " + request.error );

        TextureBundle = AssetBundle.LoadFromFile( "res://" + resourceName );

        request.Dispose( );
    }
}

现在可以在 Player Settings > Other Settings > Configuration 中的 Variant map for app slicing 下拉菜单中查看和修改变体(仅当在 Player Settings 中启用了 Use on demand resources 时,此菜单才可见)。

要了解有关 AssetBundles 和按需加载资源的更多信息,请参阅 Bitbucket 上托管的 Unity AssetBundle Manager 演示项目。登陆页面提供了有关如何使用和调整演示项目的全面说明。


  • 2018–06–14 页面已修订
准备应用程序进行应用内购 (IAP)
iOS 上的托管堆栈跟踪