共用方式為


Apple 通用連結

通常建議連接網站和行動應用程式,讓網站上的鏈接啟動行動應用程式,並在行動應用程式中顯示內容。 應用程式連結也稱為 深層連結,是一種技術,可讓行動裝置回應 URL,並在 URL 所代表的行動應用程式中啟動內容。

在 Apple 平臺上,深層鏈接稱為 通用連結。 當使用者點選通用連結時,系統會直接將連結重新導向至您的應用程式,而不需要透過 Safari 或您的網站進行路由。 這些連結可以根據自定義配置,例如 myappname://,或使用 HTTP 或 HTTPS 配置。 例如,按兩下食譜網站上的連結會開啟與該網站相關聯的行動應用程式,然後向用戶顯示特定的食譜。 未安裝應用程式的使用者會前往您網站上的內容。 本文著重於使用 HTTPS 配置的通用連結。

.NET MAUI iOS 應用程式支援通用連結。 這需要裝載數位資產連結網域上的 JSON 檔案,以描述與您的應用程式的關係。 這可讓 Apple 確認嘗試處理 URL 的應用程式擁有 URL 網域的擁有權,以防止惡意應用程式攔截您的應用程式連結。

在 .NET MAUI iOS 或 Mac Catalyst 應用程式中處理 Apple 通用連結的程式如下:

如需詳細資訊,請參閱 允許應用程式和網站連結到您 developer.apple.com 上的內容 。 如需為您的應用程式定義自定義 URL 配置的相關信息,請參閱 在 developer.apple.com 上定義應用程式的 自定義 URL 配置。

建立及裝載相關聯的網域檔案

若要將網站與您的應用程式產生關聯,您必須在網站上裝載相關聯的網域檔案。 相關聯的網域檔案是 JSON 檔案,必須在您的網域上裝載於下列位置: https://domain.name/.well-known/apple-app-site-association

下列 JSON 顯示一般相關聯網域檔案的內容:

{
    "activitycontinuation": {
        "apps": [ "85HMA3YHJX.com.companyname.myrecipeapp" ]
    },
    "applinks": {
        "apps": [],
        "details": [
            {
                "appID": "85HMA3YHJX.com.companyname.myrecipeapp",
                "paths": [ "*", "/*" ]
            }
        ]
    }
}

appsappID 索引鍵應指定網站上可供使用之應用程式的應用程式識別碼。 這些索引鍵的值是由應用程式標識碼前置詞和套件組合標識碼所組成。

重要

相關聯的網域檔案必須 https 搭配有效的憑證裝載,且不會重新導向。

如需詳細資訊,請參閱 支援 developer.apple.com 的相關網域

將相關聯的網域權利新增至您的應用程式

在網域上裝載相關聯的網域檔案之後,您必須將相關聯的網域權利新增至您的應用程式。 當使用者安裝您的應用程式時,iOS 會嘗試下載相關聯的網域檔案,並確認您權利中的網域。

相關聯的網域權利會指定應用程式相關聯的網域清單。 此權利應該新增至 您應用程式中的 Entitlements.plist 檔案。 如需在 iOS 上新增權利的詳細資訊,請參閱 權利。 如需在Mac Catalyst上新增權利的詳細資訊,請參閱 權利

權利是使用 com.apple.developer.associated-domainsArrayString別的索引鍵來定義:

<key>com.apple.developer.associated-domains</key>
<array>
  <string>applinks:recipe-app.com</string>
</array>

如需此權利的詳細資訊,請參閱 developer.apple.com 的相關網域權利

或者,您可以修改項目檔 (.csproj) 以在 元素中 <ItemGroup> 新增權利:

<ItemGroup Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios' Or $([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">

    <!-- For debugging, use '?mode=developer' for debug to bypass apple's CDN cache -->
    <CustomEntitlements
        Condition="$(Configuration) == 'Debug'"
        Include="com.apple.developer.associated-domains"
        Type="StringArray"
        Value="applinks:recipe-app.com?mode=developer" />

    <!-- Non-debugging, use normal applinks:url value -->
    <CustomEntitlements
        Condition="$(Configuration) != 'Debug'"
        Include="com.apple.developer.associated-domains"
        Type="StringArray"
        Value="applinks:recipe-app.com" />

</ItemGroup>

在此範例中,請將 取代 applinks:recipe-app.com 為您網域的正確值。 請確定您只包含所需的子域和最上層網域。 請勿包含路徑和查詢元件或尾端斜線 (/)。

注意

在 iOS 14+ 和 macOS 11+ 中,應用程式不會再將檔案的要求 apple-app-site-association 直接傳送至網頁伺服器。 相反地,他們會將要求傳送至專用於相關聯網域的Apple管理內容傳遞網路(CDN)。

將相關聯的網域功能新增至您的應用程式標識碼

將相關聯的網域權利新增至您的應用程式之後,您必須在Apple開發人員帳戶中將相關聯的網域功能新增至應用程式的應用程式識別碼。 這是必要專案,因為您應用程式中定義的任何權利也必須新增為Apple開發人員帳戶中應用程式的應用程式識別碼功能。

若要將相關聯的網域功能新增至您的應用程式識別碼:

  1. 在網頁瀏覽器中,登入您的 Apple開發人員帳戶 ,並流覽至 [憑證]、[標識符和配置檔] 頁面。

  2. 在 [ 憑證]、[標識符和配置檔] 頁面上,選取 [ 標識符] 索引卷標

  3. 在 [ 標識符] 頁面上,選取對應至您應用程式的 [應用程式識別符]。

  4. 在 [ 編輯您的應用程式識別符組態 ] 頁面上,啟用 [相關聯的網域 ] 功能,然後選取 [ 儲存 ] 按鈕:

    Screenshot of enabling the associated domains capability in the Apple Developer Portal.

  5. 在 [ 修改應用程式功能] 對話框中,選取 [ 確認] 按鈕。

更新應用程式的應用程式識別碼之後,您必須產生並下載更新的布建配置檔。

注意

如果您稍後從應用程式移除相關聯的網域權利,則必須在Apple開發人員帳戶中更新應用程式標識碼的設定。

當用戶啟動通用連結時,iOS 和 Mac Catalyst 會啟動您的應用程式並傳送 NSUserActivity 物件。 您可以查詢此物件來判斷您的應用程式啟動方式,以及判斷要採取的動作。 這應該在和 ContinueUserActivity 生命週期委派中FinishedLaunching執行。 FinishedLaunching啟動應用程式時會叫用委派,並在ContinueUserActivity應用程式執行或暫停時叫用委派。 如需生命週期委派的詳細資訊,請參閱 平臺生命週期事件

若要回應所叫用的 iOS 生命週期委派,ConfigureLifecycleEvents請在 類別MauiProgram的 方法中MauiAppBuilder呼叫 物件CreateMauiapp上的 方法。 然後,在 ILifecycleBuilder 物件上,呼叫 AddiOS 方法並指定 Action ,以註冊必要委派的處理程式:

using Microsoft.Maui.LifecycleEvents;
using Microsoft.Extensions.Logging;

namespace MyNamespace;

public static class MauiProgram
{
    public static MauiApp CreateMauiApp()
    {
        var builder = MauiApp.CreateBuilder();
        builder
            .UseMauiApp<App>()
            .ConfigureFonts(fonts =>
            {
                fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
            })
            .ConfigureLifecycleEvents(lifecycle =>
            {
#if IOS || MACCATALYST
                lifecycle.AddiOS(ios =>
                {
                    // Universal link delivered to FinishedLaunching after app launch.
                    ios.FinishedLaunching((app, data) => HandleAppLink(app.UserActivity));

                    // Universal link delivered to ContinueUserActivity when the app is running or suspended.
                    ios.ContinueUserActivity((app, userActivity, handler) => HandleAppLink(userActivity));

                    // Only required if using Scenes for multi-window support.
                    if (OperatingSystem.IsIOSVersionAtLeast(13) || OperatingSystem.IsMacCatalystVersionAtLeast(13))
                    {
                        // Universal link delivered to SceneWillConnect after app launch
                        ios.SceneWillConnect((scene, sceneSession, sceneConnectionOptions)
                            => HandleAppLink(sceneConnectionOptions.UserActivities.ToArray()
                                .FirstOrDefault(a => a.ActivityType == Foundation.NSUserActivityType.BrowsingWeb)));

                        // Universal link delivered to SceneContinueUserActivity when the app is running or suspended
                        ios.SceneContinueUserActivity((scene, userActivity) => HandleAppLink(userActivity));
                    }
                });
#endif
            });

#if DEBUG
        builder.Logging.AddDebug();
#endif

        return builder.Build();
    }

#if IOS || MACCATALYST
    static bool HandleAppLink(Foundation.NSUserActivity? userActivity)
    {
        if (userActivity is not null && userActivity.ActivityType == Foundation.NSUserActivityType.BrowsingWeb && userActivity.WebPageUrl is not null)
        {
            HandleAppLink(userActivity.WebPageUrl.ToString());
            return true;
        }
        return false;
    }
#endif

    static void HandleAppLink(string url)
    {
        if (Uri.TryCreate(url, UriKind.RelativeOrAbsolute, out var uri))
            App.Current?.SendOnAppLinkRequestReceived(uri);
    }
}

當 iOS 開啟您的應用程式作為通用連結的結果時, NSUserActivity 物件會有 ActivityType 值為 的屬性 BrowsingWeb。 活動物件的 WebPageUrl 屬性會包含使用者想要存取的 URL。 URL 可以使用 方法傳遞至您的 App 類別 SendOnAppLinkRequestReceived

注意

如果您未在應用程式中使用 Scenes 進行多視窗支援,您可以省略 Scene 方法的生命週期處理程式。

在您的 App 類別中 OnAppLinkRequestReceived ,覆寫 方法來接收及處理URL:

namespace MyNamespace;

public partial class App : Application
{
    ...

    protected override async void OnAppLinkRequestReceived(Uri uri)
    {
        base.OnAppLinkRequestReceived(uri);

        // Show an alert to test that the app link was received.
        await Dispatcher.DispatchAsync(async () =>
        {
            await Windows[0].Page!.DisplayAlert("App link received", uri.ToString(), "OK");
        });

        Console.WriteLine("App link: " + uri.ToString());
    }
}

在上述範例中 OnAppLinkRequestReceived ,覆寫會顯示應用程式連結URL。 實際上,應用程式鏈接應該讓使用者直接前往URL所代表的內容,而不會有任何提示、登入或其他中斷。 因此,覆 OnAppLinkRequestReceived 寫是用來叫用導覽至 URL 所表示內容的位置。

警告

通用連結會在您的應用程式中提供潛在的攻擊媒介,因此請確定您驗證所有URL參數,並捨棄任何格式不正確的URL。

如需詳細資訊,請參閱 在 developer.apple.com 上支援應用程式中 的通用連結。

重要

在 iOS 上,通用連結應該在裝置上測試,而不是在模擬器上測試。

若要測試通用連結,請將連結貼到您的 Notes 應用程式中,並長按它(在 iOS 上)或按下控件,以探索您遵循連結的選項。 如果已正確設定通用連結,則會出現在應用程式和Safari中開啟的選項。 選擇會在遵循來自此網域的通用連結時,在您的裝置上設定預設行為。 若要變更此預設選項,請重複這些步驟並做出不同的選擇。

注意

在 Safari 中輸入 URL 永遠不會開啟應用程式。 相反地,Safari 會接受此動作作為直接流覽。 如果使用者是在直接流覽後在您的網域上,您的網站將會顯示橫幅以開啟您的應用程式。

在 iOS 上,您可以在開發人員設定中,使用相關聯的網域診斷測試來測試您的通用連結:

  1. 在 設定 中啟用開發人員模式。 如需詳細資訊,請參閱 在 developer.apple.com 上的裝置 上啟用開發人員模式。
  2. [設定 > 開發人員] 中,捲動至 [通用連結] 並啟用 [相關聯的網域開發]。
  3. 開啟 [診斷] ,然後在您的網址中輸入 。 接著,您會收到有關連結是否適用於已安裝應用程式的意見反應。

通常,無效的通用連結是錯誤 applinks 設定的結果。

如需疑難解答建議,請參閱 對 developer.apple.com 上的通用鏈接 進行偵錯。