Xamarin.iOS 中的交接

本文介绍如何在 Xamarin.iOS 应用中使用 Handoff,以在用户的其他设备上运行的应用之间传输用户活动。

Apple 在 iOS 8 和 OS X Yosemite (10.10) 中引入了接力功能,为用户提供一种通用机制,可将某个设备上启动的活动传输到运行同一应用的另一台设备或支持相同活动的其他应用。

An example of performing a Handoff operation

本文将快速了解如何在 Xamarin.iOS 应用中启用活动共享,并详细介绍 Handoff 框架:

关于讲义

Apple 在 iOS 8 和 OS X Yosemite (10.10) 中引入了接力(也称为连续互通),它可让用户在其一台设备(iOS 或 Mac)上启动活动,并在其另一台设备(由用户的 iCloud 帐户标识)上继续执行相同的活动。

iOS 9 中对接力进行了扩展,还支持新增强的搜索功能。 有关详细信息,请参阅我们的搜索增强文档。

例如,用户可以在其 i 上启动电子邮件电话并无缝地继续其 Mac 上的电子邮件,并填写了所有相同的邮件信息,并且光标位于他们在 iOS 中留下的同一位置。

共享同一 团队 ID 的任何应用都有资格使用 Handoff 继续跨应用的用户活动,前提是这些应用是通过 iTunes App Store 交付的,或者由注册的开发人员(适用于 Mac、企业或即席应用)签名。

任何 NSDocumentUIDocument 基于的应用都自动内置了 Handoff 支持,并且需要最少的更改来支持 Handoff。

继续用户活动

NSUserActivity 类(以及对某些小更改 UIKitAppKit)提供对定义用户活动的支持,这些活动可能在另一个用户的设备上继续。

若要将活动传递给另一个用户设备,该活动必须封装在标记为“当前活动”的实例NSUserActivity中,使其有效负载集(用于执行延续的数据),然后必须将活动传输到该设备。

传递传递最少的信息以定义要继续的活动,并通过 iCloud 同步更大的数据包。

在接收设备上,用户将收到一条通知,指出活动可用于延续。 如果用户选择在新设备上继续活动,则会启动指定的应用(如果尚未运行),并使用有效 NSUserActivity 负载重新启动活动。

An overview of Continuing User Activities

只有共享同一开发人员团队 ID 并响应给定 活动类型的 应用才有资格继续。 应用定义它在 Info.plist 文件的键NSUserActivityTypes支持的活动类型。 鉴于此情况,继续设备会根据团队 ID、活动类型和活动标题(可选)选择应用来执行延续。

接收应用使用字典UserInfo中的信息NSUserActivity来配置其用户界面并还原给定活动的状态,使转换看起来与最终用户无缝。

如果延续需要的信息比可以通过 a NSUserActivity高效发送的信息,则恢复应用可以向发起的应用发送调用,并建立一个或多个流来传输所需数据。 例如,如果活动正在编辑包含多个图像的大型文本文档,则需要流式传输传输接收设备上继续活动所需的信息。 有关详细信息,请参阅 下面的“支持延续流 ”部分。

如上所述, NSDocumentUIDocument 基于应用自动内置了 Handoff 支持。 有关详细信息,请参阅 下面的“基于文档的应用 ”部分中的支持切换。

NSUserActivity 类

NSUserActivity 类是 Handoff 交换中的主要对象,用于封装可用于延续的用户活动的状态。 应用将实例化它支持的任何活动的副本 NSUserActivity ,并希望在另一台设备上继续。 例如,文档编辑器将为当前打开的每个文档创建一个活动。 但是,只有最前面的文档(显示在最前面窗口或 Tab 中)是 当前活动 ,并有可用的延续。

NSUserActivity实例由其ActivityType属性Title标识。 UserInfo字典属性用于传递有关活动状态的信息。 NeedsSave如果要通过NSUserActivity'委托延迟加载状态信息,请将属性设置为trueAddUserInfoEntries使用该方法将其他客户端的新数据合并到字典中UserInfo,以保留活动的状态。

NSUserActivityDelegate 类

用于NSUserActivityDelegate使信息保持NSUserActivityUserInfo“字典最新”,并与活动的当前状态同步。 当系统需要更新活动中的信息(例如在另一台设备上继续之前),它会调用 UserActivityWillSave 委托的方法。

你需要实现UserActivityWillSave该方法,并对(如UserInfoTitle,等)进行任何更改NSUserActivity,以确保它仍然反映当前活动的状态。 当系统调用 UserActivityWillSave 该方法时, NeedsSave 将清除标志。 如果修改活动的任何数据属性,则需要再次设置为NeedsSavetrue

可以选择性地自动拥有UIKitAppKit管理用户活动,而不是使用UserActivityWillSave上述方法。 为此,请设置响应方对象的 UserActivity 属性并实现 UpdateUserActivityState 该方法。 有关详细信息,请参阅下面的响应者中的支持切换部分。

应用框架支持

UIKit (iOS) 和 AppKit (OS X) 在响应方 (NSResponderUIResponder/) 和AppDelegate类中NSDocument为 Handoff 提供内置支持。 虽然每个 OS 都实现 Handoff 略有不同,但基本机制和 API 是相同的。

基于文档的应用中的用户活动

基于文档的 iOS 和 OS X 应用会自动内置支持 Handoff。 若要激活此支持,需要为应用 Info.plist 文件中的每个CFBundleDocumentTypes条目添加一个NSUbiquitousDocumentUserActivityType键和值。

如果存在此键,则UIDocument同时NSDocument自动为指定的类型的基于 iCloud 的文档创建NSUserActivity实例。 你需要为应用支持的每种文档类型提供一个活动类型,并且多个文档类型可以使用相同的活动类型。 同时NSDocument自动UIDocument填充UserInfoFileURL属性值NSUserActivity的属性。

在 OS X 上,当文档的窗口成为主窗口时, NSUserActivityAppKit 响应方和与之关联的管理者会自动成为当前活动。 在 iOS 上,对于 NSUserActivityUIKit管理的对象,你必须显式调用 BecomeCurrent 方法,或在应用来到前台时设置文档 UserActivity 的属性 UIViewController

AppKit 将在 OS X 上自动还原以这种方式创建的任何 UserActivity 属性。如果 ContinueUserActivity 方法返回 false 或未实现,则会发生此情况。 在这种情况下,将使用该方法NSDocumentController打开OpenDocument文档,然后它将收到方法RestoreUserActivityState调用。

有关详细信息,请参阅下面的“基于文档的应用”部分中的支持切换。

用户活动和响应者

UIKit如果将用户活动设置为响应方对象的UserActivity属性,则可以AppKit同时自动管理该活动。 如果状态已修改,则需要将响应方UserActivity的属性设置为 。NeedsSavetrue 系统在通过调用其UpdateUserActivityState方法来更新状态后,系统会自动保存UserActivity所需的时间。

如果多个响应者共享单个 NSUserActivity 实例,则当系统更新用户活动对象时,它们会收到回调 UpdateUserActivityState 。 响应方需要调用 AddUserInfoEntries 该方法来更新 NSUserActivity'字典 UserInfo ',以反映当前活动状态。 每次 UserInfo 调用前 UpdateUserActivityState 都会清除字典。

若要将自身与活动取消关联,响应者可以将其 UserActivity 属性设置为 null。 当应用框架托管 NSUserActivity 实例没有更多关联的响应者或文档时,它将自动失效。

有关详细信息,请参阅下面的响应者中的支持切换部分。

用户活动和 AppDelegate

处理 Handoff 延续时,应用 AppDelegate 是它的主要入口点。 当用户响应 Handoff 通知时,将启动相应的应用(如果尚未运行), WillContinueUserActivityWithType 并调用该方法 AppDelegate 。 此时,应用应通知用户继续开始。

调用 's ContinueUserActivity 方法时AppDelegate,将NSUserActivity传递该实例。 此时,应配置应用的用户界面并继续给定活动。

有关详细信息,请参阅下面的“实现讲义”部分。

在 Xamarin 应用中启用切换

由于 Handoff 施加的安全要求,必须在 Apple 开发人员门户和 Xamarin.iOS 项目文件中正确配置使用 Handoff 框架的 Xamarin.iOS 应用。

请执行以下操作:

  1. 登录到 Apple 开发人员门户

  2. 单击 “证书”、“标识符和配置文件”。

  3. 如果尚未执行此操作,请单击“ 标识符 ”并为应用创建 ID(例如 com.company.appname),否则请编辑现有 ID。

  4. 确保已为给定 ID 检查 iCloud 服务:

    Enable the iCloud service for the given ID

  5. 保存所做更改。

  6. 单击“预配配置文件>开发”并为应用创建新的开发预配配置文件:

    Create a new development provisioning profile for the app

  7. 下载并安装新的预配配置文件,或使用 Xcode 下载并安装配置文件。

  8. 编辑 Xamarin.iOS 项目选项,并确保使用刚刚创建的预配配置文件:

    Select the provisioning profile just created

  9. 接下来,编辑 Info.plist 文件,并确保使用用于创建预配配置文件的应用 ID:

    Set App ID

  10. 滚动到“后台模式”部分,并检查以下项:

    Enable the required background modes

  11. 保存对所有文件的更改。

有了这些设置,应用程序即可访问 Handoff Framework API。 有关预配的详细信息,请参阅我们的 设备预配预配应用 指南。

实现 Handoff

可以在使用同一开发人员团队 ID 进行签名的应用中继续用户活动,并支持相同的活动类型。 在 Xamarin.iOS 应用中实现 Handoff 需要创建用户活动对象(位于或AppKit),UIKit更新对象的状态以跟踪活动,并继续接收设备上的活动。

标识用户活动

实现 Handoff 的第一步是确定应用支持的用户活动类型,并查看哪些活动是其他设备上继续的良好候选项。 例如:ToDo 应用可能支持将项目编辑为一个用户活动类型,并支持以另一种形式浏览可用项列表。

应用可以根据需要创建任意数量的用户活动类型,一个用于应用提供的任何函数。 对于每个用户活动类型,应用需要跟踪类型开始和结束的时间,并且需要保持最新的状态信息才能在另一台设备上继续该任务。

可以在使用同一团队 ID 签名的任何应用上继续执行用户活动,而无需在发送和接收应用之间进行任何一对一映射。 例如,给定的应用可以创建四种不同类型的活动,这些活动由其他设备上的不同单个应用使用。 这是 Mac 版应用(可能具有许多功能和功能)和 iOS 应用(其中每个应用较小且侧重于特定任务)之间的常见情况。

创建活动类型标识符

活动类型标识符是添加到NSUserActivityTypes应用 Info.plist 文件的数组中的短字符串,用于唯一标识给定的用户活动类型。 对于应用支持的每个活动,数组中将有一个条目。 Apple 建议对活动类型标识符使用反向 DNS 样式表示法以避免冲突。 例如: com.company-name.appname.activity 针对特定基于应用的活动或 com.company-name.activity 可跨多个应用运行的活动。

创建 NSUserActivity 实例以标识活动类型时,将使用活动类型标识符。 在另一台设备上继续活动时,活动类型(以及应用的团队 ID)确定要启动以继续活动的应用。

例如,我们将创建一个名为 MonkeyBrowser 的示例应用(请在此处下载)。 此应用将显示四个选项卡,每个选项卡在 Web 浏览器视图中打开不同的 URL。 用户将能够继续运行应用的其他 iOS 设备上的任意选项卡。

若要创建所需的活动类型标识符以支持此行为,请编辑 Info.plist 文件并切换到 视图。 NSUserActivityTypes添加密钥并创建以下标识符:

The NSUserActivityTypes key and required identifiers in the plist editor

我们创建了四个新的活动类型标识符,其中一个用于示例 MonkeyBrowser 应用中的每个选项卡。 创建自己的应用时,请将数组的内容 NSUserActivityTypes 替换为特定于应用支持的活动的活动类型标识符。

跟踪用户活动更改

创建类的新实例 NSUserActivity 时,我们将指定一个 NSUserActivityDelegate 实例来跟踪活动状态的更改。 例如,以下代码可用于跟踪状态更改:

using System;
using CoreGraphics;
using Foundation;
using UIKit;

namespace MonkeyBrowse
{
    public class UserActivityDelegate : NSUserActivityDelegate
    {
        #region Constructors
        public UserActivityDelegate ()
        {
        }
        #endregion

        #region Override Methods
        public override void UserActivityReceivedData (NSUserActivity userActivity, NSInputStream inputStream, NSOutputStream outputStream)
        {
            // Log
            Console.WriteLine ("User Activity Received Data: {0}", userActivity.Title);
        }

        public override void UserActivityWasContinued (NSUserActivity userActivity)
        {
            Console.WriteLine ("User Activity Was Continued: {0}", userActivity.Title);
        }

        public override void UserActivityWillSave (NSUserActivity userActivity)
        {
            Console.WriteLine ("User Activity will be Saved: {0}", userActivity.Title);
        }
        #endregion
    }
}

UserActivityReceivedData当延续流从发送设备接收数据时调用此方法。 有关详细信息,请参阅 下面的“支持延续流 ”部分。

UserActivityWasContinued当另一个设备接管当前设备的活动时,将调用此方法。 根据活动类型(例如向 ToDo 列表添加新项),应用可能需要中止发送设备上的活动。

UserActivityWillSave在保存活动的任何更改并在本地可用设备之间同步之前调用该方法。 可以使用此方法在发送实例之前对实例的属性NSUserActivity进行任何最后一分钟的更改UserInfo

创建 NSUserActivity 实例

应用希望提供继续在另一台设备上的可能性的每个活动都必须封装在实例中 NSUserActivity 。 应用可以根据需要创建任意数量的活动,这些活动的性质取决于相关应用的功能和功能。 例如,电子邮件应用可能会创建一个用于创建新邮件的活动,另一个活动用于阅读邮件。

对于示例应用, NSUserActivity 用户每次在选项卡式 Web 浏览器视图中输入新 URL 时都会创建一个新 URL。 以下代码存储给定选项卡的状态:

public NSString UserActivityTab1 = new NSString ("com.xamarin.monkeybrowser.tab1");
public NSUserActivity UserActivity { get; set; }
...

UserActivity = new NSUserActivity (UserActivityTab1);
UserActivity.Title = "Weather Tab";
UserActivity.Delegate = new UserActivityDelegate ();

// Update the activity when the tab's URL changes
var userInfo = new NSMutableDictionary ();
userInfo.Add (new NSString ("Url"), new NSString (url));
UserActivity.AddUserInfoEntries (userInfo);

// Inform Activity that it has been updated
UserActivity.BecomeCurrent ();

它使用上面创建的用户活动类型之一创建新的 NSUserActivity 用户活动类型,并为活动提供可读标题。 它附加到上面创建的实例 NSUserActivityDelegate ,以监视状态更改,并通知 iOS 此用户活动是当前活动。

填充 UserInfo 字典

如上所述, UserInfo 类的属性 NSUserActivity 是用于 NSDictionary 定义给定活动的状态的键值对。 存储在其中的UserInfo值必须是下列类型之一:NSArray、、、NSData、、NSNullNSDictionaryNSDateNSNumber、、 NSSet或。 NSStringNSURL NSURL 将自动调整指向 iCloud 文档的数据值,以便它们指向接收设备上的相同文档。

在上面的示例中,我们创建了一个 NSMutableDictionary 对象,并使用提供用户当前在给定选项卡上查看的 URL 的单个键填充该对象。 AddUserInfoEntries 用户活动的方法用于使用将用于在接收设备上还原活动的数据更新活动:

// Update the activity when the tab's URL changes
var userInfo = new NSMutableDictionary ();
userInfo.Add (new NSString ("Url"), new NSString (url));
UserActivity.AddUserInfoEntries (userInfo);

Apple 建议将发送的信息保持在最低水平,以确保活动及时发送到接收设备。 如果需要更大的信息,如需要编辑附加到文档的图像,则应使用延续流。 有关更多详细信息,请参阅下面的“支持延续流”部分。

继续活动

交接会自动通知本地 iOS 和 OS X 设备,这些设备与原始设备物理邻近,并登录到同一 iCloud 帐户,了解可连续用户活动的可用性。 如果用户选择在新设备上继续活动,系统将启动相应的应用(基于团队 ID 和活动类型),并显示其延续需要发生的信息 AppDelegate

首先, WillContinueUserActivityWithType 调用该方法,以便应用可以通知用户继续即将开始。 我们在示例应用的AppDelegate.cs文件中使用以下代码来处理延续开始:

public NSString UserActivityTab1 = new NSString ("com.xamarin.monkeybrowser.tab1");
public NSString UserActivityTab2 = new NSString ("com.xamarin.monkeybrowser.tab2");
public NSString UserActivityTab3 = new NSString ("com.xamarin.monkeybrowser.tab3");
public NSString UserActivityTab4 = new NSString ("com.xamarin.monkeybrowser.tab4");
...

public FirstViewController Tab1 { get; set; }
public SecondViewController Tab2 { get; set;}
public ThirdViewController Tab3 { get; set; }
public FourthViewController Tab4 { get; set; }
...

public override bool WillContinueUserActivity (UIApplication application, string userActivityType)
{
    // Report Activity
    Console.WriteLine ("Will Continue Activity: {0}", userActivityType);

    // Take action based on the user activity type
    switch (userActivityType) {
    case "com.xamarin.monkeybrowser.tab1":
        // Inform view that it's going to be modified
        Tab1.PreparingToHandoff ();
        break;
    case "com.xamarin.monkeybrowser.tab2":
        // Inform view that it's going to be modified
        Tab2.PreparingToHandoff ();
        break;
    case "com.xamarin.monkeybrowser.tab3":
        // Inform view that it's going to be modified
        Tab3.PreparingToHandoff ();
        break;
    case "com.xamarin.monkeybrowser.tab4":
        // Inform view that it's going to be modified
        Tab4.PreparingToHandoff ();
        break;
    }

    // Inform system we handled this
    return true;
}

在上面的示例中,每个视图控制器都注册了 AppDelegate 一个公共 PreparingToHandoff 方法,该方法显示一个活动指示器和一条消息,告知用户活动即将移交给当前设备。 示例:

private void ShowBusy(string reason) {

    // Display reason
    BusyText.Text = reason;

    //Define Animation
    UIView.BeginAnimations("Show");
    UIView.SetAnimationDuration(1.0f);

    Handoff.Alpha = 0.5f;

    //Execute Animation
    UIView.CommitAnimations();
}
...

public void PreparingToHandoff() {
    // Inform caller
    ShowBusy ("Continuing Activity...");
}

ContinueUserActivity将调用 AppDelegate 以实际继续给定的活动。 同样,在我们的示例应用中:

public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{

    // Report Activity
    Console.WriteLine ("Continuing User Activity: {0}", userActivity.ToString());

    // Get input and output streams from the Activity
    userActivity.GetContinuationStreams ((NSInputStream arg1, NSOutputStream arg2, NSError arg3) => {
        // Send required data via the streams
        // ...
    });

    // Take action based on the Activity type
    switch (userActivity.ActivityType) {
    case "com.xamarin.monkeybrowser.tab1":
        // Preform handoff
        Tab1.PerformHandoff (userActivity);
        completionHandler (new NSObject[]{Tab1});
        break;
    case "com.xamarin.monkeybrowser.tab2":
        // Preform handoff
        Tab2.PerformHandoff (userActivity);
        completionHandler (new NSObject[]{Tab2});
        break;
    case "com.xamarin.monkeybrowser.tab3":
        // Preform handoff
        Tab3.PerformHandoff (userActivity);
        completionHandler (new NSObject[]{Tab3});
        break;
    case "com.xamarin.monkeybrowser.tab4":
        // Preform handoff
        Tab4.PerformHandoff (userActivity);
        completionHandler (new NSObject[]{Tab4});
        break;
    }

    // Inform system we handled this
    return true;
}

每个视图控制器的公共 PerformHandoff 方法实际上预制了交接,并还原当前设备上的活动。 在本示例中,它在给定选项卡中显示用户在不同设备上浏览的相同 URL。 示例:

private void HideBusy() {

    //Define Animation
    UIView.BeginAnimations("Hide");
    UIView.SetAnimationDuration(1.0f);

    Handoff.Alpha = 0f;

    //Execute Animation
    UIView.CommitAnimations();
}
...

public void PerformHandoff(NSUserActivity activity) {

    // Hide busy indicator
    HideBusy ();

    // Extract URL from dictionary
    var url = activity.UserInfo ["Url"].ToString ();

    // Display value
    URL.Text = url;

    // Display the give webpage
    WebView.LoadRequest(new NSUrlRequest(NSUrl.FromString(url)));

    // Save activity
    UserActivity = activity;
    UserActivity.BecomeCurrent ();

}

该方法 ContinueUserActivity 包括一个 UIApplicationRestorationHandler 可以调用基于文档或响应方的活动恢复的方法。 调用还原处理程序时,需要将对象 NSArray 或可还原对象传递给还原处理程序。 例如:

completionHandler (new NSObject[]{Tab4});

对于传递的每个对象,将调用其 RestoreUserActivityState 方法。 然后,每个对象都可以使用字典中的数据 UserInfo 还原其自己的状态。 例如:

public override void RestoreUserActivityState (NSUserActivity activity)
{
    base.RestoreUserActivityState (activity);

    // Log activity
    Console.WriteLine ("Restoring Activity {0}", activity.Title);
}

对于基于文档的应用,如果不实现 ContinueUserActivity 该方法或它返回 falseUIKit 或者 AppKit 可以自动恢复活动。 有关详细信息,请参阅下面的“基于文档的应用”部分中的支持切换。

失败交接优雅

由于 Handoff 依赖于集合之间松散连接的 iOS 和 OS X 设备之间的信息传输,因此传输过程有时可能会失败。 你应该设计应用以正常处理这些故障,并通知用户出现的任何情况。

如果发生故障, DidFailToContinueUserActivitiy 将调用该方法 AppDelegate 。 例如:

public override void DidFailToContinueUserActivitiy (UIApplication application, string userActivityType, NSError error)
{
    // Log information about the failure
    Console.WriteLine ("User Activity {0} failed to continue. Error: {1}", userActivityType, error.LocalizedDescription);
}

应使用所提供的 NSError 信息向用户提供有关失败的信息。

本机应用到 Web 浏览器交接

用户可能需要继续活动,而无需在所需设备上安装适当的本机应用。 在某些情况下,基于 Web 的接口可能提供所需的功能,并且活动仍可继续。 例如,用户的电子邮件帐户可以提供用于撰写和阅读邮件的 Web 基础 UI。

如果发起的本机应用知道 Web 界面的 URL(以及用于标识给定项的必需语法),则可以在实例的属性NSUserActivityWebpageURL对此信息进行编码。 如果接收设备未安装适当的本机应用来处理延续,则可以调用提供的 Web 界面。

Web 浏览器到本机应用交接

如果用户在发起设备上使用基于 Web 的界面,并且接收设备上的本机应用声明属性的 WebpageURL 域部分,则系统将使用该应用处理延续。 新设备将收到一个 NSUserActivity 实例,该实例将标记为活动类型 BrowsingWeb ,并且 WebpageURL 将包含用户正在访问的 URL, UserInfo 字典将为空。

要使应用参与这种类型的 Handoff,它必须以具有格式<service>:<fully qualified domain name>的权利声明域com.apple.developer.associated-domains(例如: activity continuation:company.com) 。

如果指定的域与属性的值匹配 WebpageURL ,Handoff 将从该域的网站下载已批准的应用 ID 列表。 该网站必须在名为 apple-app-site-association 的已签名 JSON 文件中提供已批准的 ID 列表(例如)。 https://company.com/apple-app-site-association

此 JSON 文件包含一个字典,该字典指定窗体 <team identifier>.<bundle identifier>中的应用 ID 列表。 例如:

{
    "activitycontinuation": {
        "apps": [    "YWBN8XTPBJ.com.company.FirstApp",
            "YWBN8XTPBJ.com.company.SecondApp" ]
    }
}

若要对 JSON 文件进行签名(以便其正确Content-Typeapplication/pkcs7-mime),请使用终端应用以及 openssl iOS 信任的证书颁发机构颁发的证书和密钥的命令(请参阅https://support.apple.com/kb/ht5012列表)。 例如:

echo '{"activitycontinuation":{"apps":["YWBN8XTPBJ.com.company.FirstApp",
"YWBN8XTPBJ.com.company.SecondApp"]}}' > json.txt

cat json.txt | openssl smime -sign -inkey company.com.key
-signer company.com.pem
-certfile intermediate.pem
-noattr -nodetach
-outform DER > apple-app-site-association

openssl 命令将输出一个已签名的 JSON 文件,该文件放置在网站上的 apple-app-site-association URL。 例如:

https://example.com/apple-app-site-association.

应用将接收其 WebpageURL 域在其权利中的 com.apple.developer.associated-domains 任何活动。 http仅支持协议和https协议,任何其他协议都会引发异常。

支持基于文档的应用的切换

如上所述,在 iOS 和 OS X 上,如果应用的 Info.plist 文件包含密钥CFBundleDocumentTypesNSUbiquitousDocumentUserActivityType,基于文档的应用将自动支持基于 iCloud 的文档的交接。 例如:

<key>CFBundleDocumentTypes</key>
<array>
    <dict>
        <key>CFBundleTypeName</key>
        <string>NSRTFDPboardType</string>
        . . .
        <key>LSItemContentTypes</key>
        <array>
        <string>com.myCompany.rtfd</string>
        </array>
        . . .
        <key>NSUbiquitousDocumentUserActivityType</key>
        <string>com.myCompany.myEditor.editing</string>
    </dict>
</array>

在此示例中,字符串是一个反向 DNS 应用设计器,其名称追加了活动。 如果以这种方式输入,则不需要在 Info.plist 文件的数组NSUserActivityTypes重复活动类型条目。

自动创建的用户活动对象(通过文档 UserActivity 的属性提供)可由应用中的其他对象引用,并用于还原延续状态。 例如,跟踪项目选择和文档位置。 每当状态更改并更新方法中的UpdateUserActivityState字典时,都需要将此活动NeedsSave属性设置为trueUserInfo

UserActivity 属性可从任何线程使用,并符合键值观察(KVO)协议,因此可用于在文档移入和移出 iCloud 时保持同步。 关闭文档时,该 UserActivity 属性将失效。

有关详细信息,请参阅 Apple 在基于文档的应用文档中的用户活动支持。

支持响应者中的切换

可以通过设置UserActivity响应者的属性(从 UIResponder iOS 或 NSResponder OS X 上的响应者继承)关联到活动。 系统在适当的时间自动保存 UserActivity 属性,调用响应程序 UpdateUserActivityState 的方法,以使用 AddUserInfoEntriesFromDictionary 该方法将当前数据添加到用户活动对象。

支持延续流

在某些情况下,初始传递有效负载无法有效地传输继续活动所需的信息量。 在这些情况下,接收应用可以在自身与原始应用之间建立一个或多个流来传输数据。

原始应用将实例的属性NSUserActivity设置为 SupportsContinuationStreamstrue。 例如:

// Create a new user Activity to support this tab
UserActivity = new NSUserActivity (ThisApp.UserActivityTab1){
    Title = "Weather Tab",
    SupportsContinuationStreams = true
};
UserActivity.Delegate = new UserActivityDelegate ();

// Update the activity when the tab's URL changes
var userInfo = new NSMutableDictionary ();
userInfo.Add (new NSString ("Url"), new NSString (url));
UserActivity.AddUserInfoEntries (userInfo);

// Inform Activity that it has been updated
UserActivity.BecomeCurrent ();

然后,接收应用可以调用GetContinuationStreamsAppDelegate中的方法NSUserActivity来建立流。 例如:

public override bool ContinueUserActivity (UIApplication application, NSUserActivity userActivity, UIApplicationRestorationHandler completionHandler)
{

    // Report Activity
    Console.WriteLine ("Continuing User Activity: {0}", userActivity.ToString());

    // Get input and output streams from the Activity
    userActivity.GetContinuationStreams ((NSInputStream arg1, NSOutputStream arg2, NSError arg3) => {
        // Send required data via the streams
        // ...
    });

    // Take action based on the Activity type
    switch (userActivity.ActivityType) {
    case "com.xamarin.monkeybrowser.tab1":
        // Preform handoff
        Tab1.PerformHandoff (userActivity);
        completionHandler (new NSObject[]{Tab1});
        break;
    case "com.xamarin.monkeybrowser.tab2":
        // Preform handoff
        Tab2.PerformHandoff (userActivity);
        completionHandler (new NSObject[]{Tab2});
        break;
    case "com.xamarin.monkeybrowser.tab3":
        // Preform handoff
        Tab3.PerformHandoff (userActivity);
        completionHandler (new NSObject[]{Tab3});
        break;
    case "com.xamarin.monkeybrowser.tab4":
        // Preform handoff
        Tab4.PerformHandoff (userActivity);
        completionHandler (new NSObject[]{Tab4});
        break;
    }

    // Inform system we handled this
    return true;
}

在发起设备上,用户活动委托通过调用其 DidReceiveInputStream 方法接收流,以提供请求继续恢复设备上的用户活动的数据。

你将使用 a NSInputStream 提供对流数据的只读访问权限,并提供 NSOutputStream 仅写访问权限。 流应以请求和响应方式使用,其中接收应用请求更多数据和发起应用提供这些数据。 因此,从继续设备上的输入流中读取写入到源设备上的输出流的数据,反之亦然。

即使在需要延续流的情况下,两个应用之间的来回通信也应最少。

有关详细信息,请参阅 Apple 的 “使用延续流” 文档。

交接最佳做法

通过 Handoff 成功实现用户活动的无缝延续需要经过精心设计,因为涉及的所有组件。 Apple 建议对已启用 Handoff 的应用采用以下最佳做法:

  • 将用户活动设计为需要尽可能小的有效负载来关联要继续的活动状态。 有效负载越大,开始延续所需的时间就越长。
  • 如果必须传输大量数据才能成功延续,请考虑到配置和网络开销所涉及的成本。
  • 大型 Mac 应用通常会创建由 iOS 设备上的多个、更小的任务特定应用处理的用户活动。 不同的应用和 OS 版本应设计为很好地协同工作或正常失败。
  • 指定活动类型时,请使用反向 DNS 表示法以避免冲突。 如果某个活动特定于给定应用,则其名称应包含在类型定义中(例如 com.myCompany.myEditor.editing)。 如果活动可以跨多个应用工作,请从定义中删除应用名称(例如 com.myCompany.editing)。
  • 如果你的应用需要更新用户活动(NSUserActivity)的状态,请将 NeedsSave 属性设置为 true。 在适当的时间,Handoff 将调用委托 UserActivityWillSave 的方法,以便你可以根据需要更新 UserInfo 字典。
  • 由于接接过程可能不会在接收设备上立即初始化,因此应实现 AppDelegate's WillContinueUserActivity ,并通知用户继续即将启动。

示例 Handoff 应用

作为在 Xamarin.iOS 应用中使用 Handoff 的示例,本指南中包含了 MonkeyBrowser 示例应用。 该应用有四个选项卡,用户可用于浏览 Web,每个选项卡都有给定的活动类型:天气、收藏夹、咖啡休息和工作。

在任何选项卡上,当用户输入新 URL 并点击 “转到 ”按钮时,将为该选项卡创建一个新 NSUserActivity URL,其中包含用户当前正在浏览的 URL:

Example Handoff App

如果另一个用户设备 安装了 MonkeyBrowser 应用,则使用同一用户帐户登录到 iCloud,位于同一网络上,并且靠近上述设备,则“切换活动”将显示在主屏幕上(左下角):

The Handoff Activity displayed on the home screen in the lower left hand corner

如果用户在 Handoff 图标上向上拖动,应用将启动,并在 NSUserActivity 新设备上继续指定用户活动:

The User Activity continued on the new device

成功将用户活动发送到另一个 Apple 设备后,发送设备将收到对其UserActivityWasContinued方法NSUserActivityDelegateNSUserActivity调用,以告知用户活动已成功传输到另一台设备。

总结

本文介绍了用于在多个用户的 Apple 设备之间继续执行用户活动的 Handoff 框架。 接下来,它演示了如何在 Xamarin.iOS 应用中启用和实现 Handoff。 最后,它讨论了可用的不同类型的 Handoff 延续和 Handoff 最佳做法。