App Center 分发 - MAUI 和 Xamarin 应用内更新

重要

Visual Studio App Center 计划于 2025 年 3 月 31 日停用。 虽然可以继续使用 Visual Studio App Center,直到它完全停用,但你可以考虑迁移到几个建议的替代方法。

详细了解支持时间线和替代方案。

当你通过 App Center 分发应用时,App Center 分发将允许用户安装新版本的应用。 新版本的应用可用后,SDK 将向用户显示一个更新对话框,以下载或推迟新版本。 选择更新后,SDK 将开始更新应用程序。

警告

Google Play 会将应用内更新代码视为恶意行为,即使运行时未使用该代码。 在将应用提交到 Google Play 之前,按照 本部分中 的指示使用分发 SDK 的变体。 如果不删除应用内更新代码,可能会导致应用不符合要求,并从 Google Play 中删除应用。

注意

如果运行的是自动 UI 测试,则启用的应用内更新将阻止自动化 UI 测试,因为它们将尝试针对 App Center 后端进行身份验证。 建议不要为 UI 测试启用 App Center 分发。

将应用内更新添加到应用

如果尚未在应用程序中设置并启动 SDK,请按照 入门 部分进行操作。

1. 添加 App Center 分发模块

App Center SDK 采用模块化方法设计 - 开发人员只需集成他们感兴趣的服务的模块。

Visual Studio for Mac

  • 打开 Visual Studio for Mac。
  • 单击“ 文件>打开 ”并选择解决方案。
  • 在解决方案导航器中,右键单击“ 包” 部分,然后选择 “添加 NuGet 包...”
  • 搜索 App Center,并安装 App Center 分发
  • 单击“ 添加包”。

适用于 Windows 的 Visual Studio

  • 打开 Visual Studio for Windows。
  • 单击“ 文件>打开 ”并选择解决方案。
  • 在解决方案导航器中,右键单击“ 引用 ”,然后选择“ 管理 NuGet 包”。
  • 搜索 App Center,并安装 Microsoft.AppCenter.Distribut

程序包管理器控制台

  • Visual Studio 中打开控制台。 为此,请选择 “工具”>“NuGet 包管理器>包管理器控制台”。
  • 如果使用的是 Visual Studio for Mac,请确保已安装 NuGet 包管理扩展。 为此,请选择 “Visual Studio>扩展”,搜索 “NuGet” 并根据需要安装。
  • 在控制台中键入以下命令:
Install-Package Microsoft.AppCenter.Distribute

注意

如果在可移植项目 ((如 Xamarin.Forms) )中使用 App Center SDK,则必须在每个项目中安装包:可移植、Android 和 iOS。 为此,应打开每个子项目,并按照Visual Studio for MacVisual Studio for Windows 部分中所述的相应步骤进行操作。

注意

Android 10 或更高版本对从后台启动活动有限制。 请参阅有关 从后台启动活动的限制的文章。

注意

在 Android 10 (Go 版本) 上运行的应用无法接收 SYSTEM_ALERT_WINDOW 权限。 请参阅有关 Go 设备上SYSTEM_ALERT_WINDOW的文章。

注意

从 Android 11 开始, ACTION_MANAGE_OVERLAY_PERMISSION 意向始终将用户带到顶级“设置”屏幕,用户可以在此屏幕中授予或撤销 SYSTEM_ALERT_WINDOW 应用的权限。 请参阅有关 Android 11 中权限更新的文章。

2. 启动 App Center 分发

按照入门指南中所述,通过调用 来AppCenter.Start(...)配置 App Center SDK。

对于 iOS 应用程序,打开 并在AppDelegate.cs调用 LoadApplication之前添加以下行:

Distribute.DontCheckForUpdatesInDebug();

在运行时自动检测到调试配置的 Android 上,不需要执行此步骤。

若要在 Android 上为调试版本启用应用内更新,请在项目的 MainActivity.cs 文件中、方法中 OnCreate 和 之前 LoadApplication调用以下方法。

Distribute.SetEnabledForDebuggableBuild(true);

注意

此方法仅影响调试版本,对发布版本没有影响。

2.3 [仅适用于 iOS] 修改项目的 Info.plist

App Center SDK 检查重定向到应用程序的 URL 以避免旁加载,因此,若要正确处理通过门户分发的更新,需要在 文件中指定 CFBundleURLSchemesCFBundleURLTypesInfo.plist

注意

Info.plist或信息属性列表文件是包含捆绑可执行文件的基本配置信息的结构化文本文件。 有关它的详细信息,请参阅 Apple 开发人员文档

  1. URL typesCFBundleURLTypes Info.plist 文件添加新密钥, (以防 Xcode 将 Info.plist 显示为源代码) 。
  2. 将第一个子项的键更改为 URL SchemesCFBundleURLSchemes
  3. 输入 appcenter-${APP_SECRET} 作为 URL 方案,并将 替换为 ${APP_SECRET} 应用的“应用机密”。

提示

如果要验证是否已正确修改 Info.plist,请将其作为源代码打开。 它应包含应用机密的以下条目, ${APP_SECRET}而不是 :

<key>CFBundleURLTypes</key>
  <array>
      <dict>
          <key>CFBundleURLSchemes</key>
          <array>
              <string>appcenter-${APP_SECRET}</string>
          </array>
      </dict>
  </array>

删除 Google Play 内部版本的应用内更新

Google Play 会将应用内更新代码视为恶意行为,即使运行时未使用该代码。 如果不删除应用内更新代码,可能会导致应用不符合要求,并从 Google Play 中删除应用。 为了简化操作,我们提供了 App Center 分发 SDK 版本,其中包含基元 API,因此唯一的更改是进行依赖项交换。

  1. Xamarin.Android 和共享项目添加名为 GooglePlay 的新生成配置。 确保项目生成配置已正确映射到相应的解决方案配置。 有关更多详细信息,请参阅 Visual StudioVisual Studio for Mac 说明。

  2. 在任何文本编辑器中打开 Xamarin.Android 和共享项目 .csproj ,并将分发引用移动到条件项组:

    <ItemGroup Condition=" '$(Configuration)' != 'GooglePlay' ">
        <PackageReference Include="Microsoft.AppCenter.Distribute" Version="3.3.0" />
    </ItemGroup>
    <ItemGroup Condition=" '$(Configuration)' == 'GooglePlay' ">
        <PackageReference Include="Microsoft.AppCenter.DistributePlay" Version="3.3.0" />
    </ItemGroup>
    

    注意

    如果使用旧的 packages.config 格式来管理 NuGet 引用,则可以迁移到 PackageReference 格式,请按照 迁移说明操作

  3. 保存更改并还原 NuGet 包。

  4. 可以在 IDE 顶部的命令栏中更改配置。

使用专用通讯组

默认情况下,分发使用公共通讯组。 如果要使用专用通讯组,则需要通过 UpdateTrack 属性显式设置它。

Distribute.UpdateTrack = UpdateTrack.Private;

注意

默认值为 UpdateTrack.Public。 此属性只能在方法调用之前 AppCenter.Start 更新。 应用程序进程重启时,不会保留对更新跟踪的更改,因此,如果属性在调用之前 AppCenter.Start 并不总是更新,则默认情况下,该属性是公开的。

此调用后,浏览器窗口将打开以对用户进行身份验证。 所有后续更新检查都将在专用轨道上获取最新版本。在应用启动时,更新跟踪不会保留在 SDK 中。

如果用户处于 专用通道上,则意味着身份验证成功后,他们将从其所属的任何专用通讯组获取最新版本。 如果用户处于 公共轨道上,则意味着他们将从任何公共通讯组获取最新版本。

禁用更新的自动检查

默认情况下,SDK 会自动检查新版本:

  • 应用程序启动时。
  • 当应用程序进入后台时,然后再次在前台。
  • 启用分发模块(如果以前禁用)。

如果要手动检查新版本,可以禁用自动更新检查。 为此,请在 SDK 启动之前调用以下方法:

Distribute.DisableAutomaticCheckForUpdate();

注意

在调用方法之前 AppCenter.Start ,必须调用此方法。

然后, CheckForUpdate 可以使用以下部分所述的 API。

手动检查更新

Distribute.CheckForUpdate();

注意

即使启用了自动更新,更新调用的手动检查也有效。 如果已执行其他检查,则会忽略更新的手动检查。 如果用户已推迟更新 (,则不会处理更新的手动检查,除非最新版本是强制更新) 。

自定义或本地化应用内更新对话框

1. 自定义或本地化文本

如果要本地化更新对话框中显示的文本,可以轻松提供自己的资源字符串。 查看 此资源文件中 适用于 iOS 的字符串文件,以及 此资源文件中适用于 Android 的字符串文件。 使用相同的字符串名称/键,并指定要在你自己的应用资源文件中的对话框中反映的本地化值。

2.自定义更新对话框

可以通过实现 ReleaseAvailable 回调来自定义默认更新对话框的外观。 在调用 AppCenter.Start 之前,需要注册回调,如以下示例所示:

// In this example OnReleaseAvailable is a method name in same class
Distribute.ReleaseAvailable = OnReleaseAvailable;
AppCenter.Start(...);

下面是回调实现的示例,该实现将 SDK 对话框替换为自定义对话:

bool OnReleaseAvailable(ReleaseDetails releaseDetails)
{
    // Look at releaseDetails public properties to get version information, release notes text or release notes URL
    string versionName = releaseDetails.ShortVersion;
    string versionCodeOrBuildNumber = releaseDetails.Version;
    string releaseNotes = releaseDetails.ReleaseNotes;
    Uri releaseNotesUrl = releaseDetails.ReleaseNotesUrl;

    // custom dialog
    var title = "Version " + versionName + " available!";
    Task answer;

    // On mandatory update, user can't postpone
    if (releaseDetails.MandatoryUpdate)
    {
        answer = Current.MainPage.DisplayAlert(title, releaseNotes, "Download and Install");
    }
    else
    {
        answer = Current.MainPage.DisplayAlert(title, releaseNotes, "Download and Install", "Maybe tomorrow...");
    }
    answer.ContinueWith((task) =>
    {
        // If mandatory or if answer was positive
        if (releaseDetails.MandatoryUpdate || (task as Task<bool>).Result)
        {
            // Notify SDK that user selected update
            Distribute.NotifyUpdateAction(UpdateAction.Update);
        }
        else
        {
            // Notify SDK that user selected postpone (for 1 day)
            // This method call is ignored by the SDK if the update is mandatory
            Distribute.NotifyUpdateAction(UpdateAction.Postpone);
        }
    });

    // Return true if you're using your own dialog, false otherwise
    return true;
}

Xamarin.Android 的实现说明:

如示例中所示,如果回调返回 true,则必须调用 Distribute.NotifyUpdateAction(UpdateAction.UPDATE);Distribute.NotifyUpdateAction(UpdateAction.POSTPONE);

如果不调用 NotifyUpdateAction,回调将在每次活动更改时重复。

如果在将用户操作通知到 SDK 之前活动发生更改,则可以使用同一版本再次调用回调。

需要此行为来涵盖以下方案:

  • 应用程序会发送到后台 (,例如按 HOME) 然后在其他活动中恢复。
  • 你的活动由另一个活动覆盖,而不离开应用程序 (如单击一些通知) 。
  • 其他类似方案。

在这种情况下,托管对话的活动可能会被替换,而无需用户交互。 因此,SDK 会再次调用侦听器,以便可以还原自定义对话。

3. 如果未找到更新,则执行代码

如果 SDK 检查更新,但找不到比当前使用的更新新的可用更新, NoReleaseAvailable 则会调用回调。 这允许在此类情况下执行自定义代码。 在调用 AppCenter.Start 之前,需要注册回调,如以下示例所示:

// In this example OnNoReleaseAvailable is a method name in same class
Distribute.NoReleaseAvailable = OnNoReleaseAvailable;
AppCenter.Start(...);
void OnNoReleaseAvailable()
{
    AppCenterLog.Info(LogTag, "No release available callback invoked.");
}

在运行时启用或禁用 App Center 分发

可以在运行时启用和禁用 App Center 分发。 如果禁用它,SDK 不会提供任何应用内更新功能,但你仍然可以在 App Center 门户中使用分发服务。

Distribute.SetEnabledAsync(false);

若要再次启用 App Center 分发,请使用同一 API,但作为参数传递 true

Distribute.SetEnabledAsync(true);

无需等待此调用, (其他 API 调用(例如 IsEnabledAsync) 一致)。

状态在应用程序启动时保留在设备的存储中。

注意

只有在启动后 Distribute 才能使用此方法。

检查是否启用了 App Center 分发

如果 App Center 分发已启用或未启用,还可以检查:

bool enabled = await Distribute.IsEnabledAsync();

注意

此方法只能在启动后 Distribute 使用,它始终在启动前返回 false

在应用关闭之前立即执行清理,以便仅在 iOS (更新)

注册回调,如以下示例所示:

// In this example, OnWillExitApp is a method name in same class
Distribute.WillExitApp = OnWillExitApp;
void OnWillExitApp()
{
    // Perform clean up here
}

这样, OnWillExitApp() 将在分发即将关闭时调用 。

应用内更新如何工作?

注意

若要运行应用内更新,应从链接下载应用内部版本。 如果从 IDE 或手动安装,则它不起作用。

应用内更新功能的工作原理如下:

  1. 此功能仅适用于默认情况下使用 App Center 分发服务分发) 发布版本 (。 如果 iOS 引导访问功能已打开,它将不起作用。

  2. 集成 SDK、生成应用的发布版本并上传到 App Center 后,该通讯组中的用户将通过电子邮件通知新版本。

  3. 当每个用户在其电子邮件中打开链接时,应用程序将安装在其设备上。 请务必使用电子邮件链接进行安装 - 我们不支持旁加载。 从链接下载应用程序时,SDK 会将 cookie 中的重要信息保存到检查以供以后更新,否则 SDK 没有该密钥信息。

  4. 如果应用程序将跟踪设置为专用,浏览器将打开以对用户进行身份验证并启用应用内更新。 只要身份验证信息保持有效,浏览器就不会再次打开,即使稍后切换回公共轨道并再次切换回专用。 如果浏览器身份验证成功,用户会自动重定向回应用程序。 如果轨道是公共 (这是默认) ,则直接执行下一步。

    • 在 iOS 9 和 10 上,将在应用中打开 一 SFSafariViewController 个 实例来对用户进行身份验证。 身份验证成功后,它将自动关闭。
    • 在 iOS 11 上,用户体验类似于 iOS 10,但 iOS 11 会要求用户提供访问登录信息的权限。 这是一个系统级对话框,无法对其进行自定义。 如果用户取消对话框,他们可以继续使用他们正在测试的版本,但不会获得应用内更新。 下次启动应用时,系统会要求他们再次访问登录信息。
  5. 应用的新版本显示应用内更新对话框,要求用户更新应用程序(如果为):

    • iOS:

      • 或 的 CFBundleShortVersionString 较高值
      • 的等值, CFBundleShortVersionString 但 更高的值 CFBundleVersion
      • 版本相同,但生成唯一标识符不同。
    • Android:

      • 或 的 versionCode 较高值
      • 相等值, versionCode 但 不同的 versionName值。

提示

如果再次上传同一 apk/ipa, 对话框将不会显示 ,因为二进制文件相同。 在 iOS 上,如果上传具有相同版本属性的 新版本 ,则会显示更新对话框。 原因是它是 不同的 二进制文件。 在 Android 上,如果两个版本属性相同,则二进制文件被视为相同。

如何实现测试应用内更新?

需要将使用 App Center SDK) 分发模块的发布版本 (上传到 App Center 门户,以测试应用内更新,每次都会增加版本号。

  1. 在 App Center 门户中创建应用(如果尚未创建)。
  2. 创建新的通讯组并将其命名,以便你可以识别它用于测试应用内更新功能。
  3. 将自己 (或要包含在应用内更新功能测试) 的所有人员。 为此,请使用新的或丢弃的电子邮件地址,该地址未用于应用中心上的该应用。 这可确保你的体验与真实测试人员的体验接近。
  4. 创建应用的新内部版本,其中包含 App Center Distribut 并包含如下所述的设置逻辑。 如果组是专用组,请在开始使用 UpdateTrack 属性之前,不要忘记设置专用应用内更新跟踪。
  5. 单击门户中的 “分发新版本 ”按钮,然后上传应用的内部版本。
  6. 上传完成后,单击“下一步”,然后选择作为该应用分发的目标创建的通讯组
  7. 查看分发并将生成分发到应用内测试组。
  8. 该组中人员将收到成为应用测试人员的邀请。 接受邀请后,即可从其移动设备从 App Center 门户下载应用。 安装应用内更新后,即可测试应用内更新。
  9. 将应用版本 (CFBundleShortVersionString CFBundleVersion iOS( versionCode 适用于 Android) )
  10. 生成应用的发布版本,并上传应用的新版本,就像在上一步中所做的那样,并将其分发到之前创建的 通讯组 。 下次应用启动时,将提示通讯组成员输入新版本。

提示

查看有关如何 利用 App Center 分发 的信息,了解有关 通讯组 等的更多详细信息。虽然可以在不添加任何代码的情况下使用 App Center 分发来分发新版本的应用,但将 App Center Distribut 添加到应用的代码将为测试人员和用户带来更无缝的体验,因为他们可以获得应用内更新体验。

禁用应用程序委托方法自动转发到 App Center 服务

App Center 使用重排自动将应用程序委托的方法转发到 App Center 服务,以改善 SDK 集成。 可能会与其他第三方库或应用程序委托本身发生冲突。 在这种情况下,可能需要按照以下步骤为所有 App Center 服务禁用 App Center 应用程序委托转发:

  1. 打开项目的 Info.plist 文件。
  2. 添加 AppCenterAppDelegateForwarderEnabled 键并将值设置为 0。 这会为所有 App Center 服务禁用应用程序委托转发。
  3. AppDelegate.cs文件中添加OpenUrl回调。
public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation)
{
    Distribute.OpenUrl(url);
    return true;
}