中的相依性解析 Xamarin.Forms

Download Sample 下載範例

本文說明如何將相依性解析方法 Xamarin.Forms 插入至 ,讓應用程式的相依性插入容器能夠控制自定義轉譯器、效果和 DependencyService 實作的建立和存留期。 本文中的程式代碼範例取自 使用容器 的相依性解析範例。

在使用 Model-View-ViewModel (MVVM) 模式的應用程式內容 Xamarin.Forms 中,相依性插入容器可用於註冊和解析檢視模型,以及註冊服務,並將其插入檢視模型中。 在檢視模型建立期間,容器會插入所需的任何相依性。 如果尚未建立這些相依性,容器會先建立並解析相依性。 如需相依性插入的詳細資訊,包括將相依性插入至檢視模型的範例,請參閱 相依性插入

在平台專案中控制型別的建立和存留期,通常是由 Xamarin.Forms執行,它會使用 Activator.CreateInstance 方法來建立自定義轉譯器、效果和 DependencyService 實作的實例。 不幸的是,這會限制開發人員對這些類型的建立和存留期的控制,以及插入相依性的能力。 藉由將相依性解析方法Xamarin.Forms插入至 ,以變更此行為,以控制將如何建立類型 – 應用程式相依性插入容器或 。Xamarin.Forms 不過,請注意,不需要將相依性解析方法 Xamarin.Forms插入 。 Xamarin.Forms 如果未插入相依性解析方法,將繼續在平台專案中建立和管理類型的存留期。

注意

雖然本文著重於將相依性解析方法 Xamarin.Forms 插入至 ,以使用相依性插入容器解析已註冊的型別,但您也可以插入使用 Factory 方法來解析已註冊的型別的相依性解析方法。 如需詳細資訊,請參閱 使用 Factory 方法 的相依性解析範例。

插入相依性解析方法

類別DependencyResolver可讓您使用 ResolveUsing 方法將相依性解析方法插入 。Xamarin.Forms 然後,當需要特定類型的實例時 Xamarin.Forms ,相依性解析方法會有機會提供 實例。 如果要求的型別傳 null 回相依性解析方法, Xamarin.Forms 請回復為嘗試使用 Activator.CreateInstance 方法建立類型實例本身。

下列範例示範如何使用 方法設定相依性解析方法 ResolveUsing

using Autofac;
using Xamarin.Forms.Internals;
...

public partial class App : Application
{
    // IContainer and ContainerBuilder are provided by Autofac
    static IContainer container;
    static readonly ContainerBuilder builder = new ContainerBuilder();

    public App()
    {
        ...
        DependencyResolver.ResolveUsing(type => container.IsRegistered(type) ? container.Resolve(type) : null);
        ...
    }
    ...
}

在此範例中,相依性解析方法會設定為 Lambda 表達式,該運算式會使用 Autofac 相依性插入容器來解析已向容器註冊的任何類型。 否則會 null 傳回 ,這會導致 Xamarin.Forms 嘗試解析類型。

注意

相依性插入容器所使用的 API 專屬於容器。 本文中的程式代碼範例使用 Autofac 作為相依性插入容器,其提供 IContainerContainerBuilder 類型。 您也可以使用替代相依性插入容器,但會使用與這裡不同的 API。

請注意,在應用程式啟動期間不需要設定相依性解析方法。 您可以隨時設定它。 唯一的條件約束是 Xamarin.Forms ,在應用程式嘗試取用儲存在相依性插入容器中的類型時,需要知道相依性解析方法。 因此,如果應用程式在啟動期間所需的相依性插入容器中有服務,則相依性解析方法必須在應用程式的生命週期早期設定。 同樣地,如果相依性插入容器管理特定 Effect的建立和存留期, Xamarin.Forms 在嘗試建立使用該 Effect的檢視之前,必須先知道相依性解析方法。

警告

使用相依性插入容器註冊和解析類型具有效能成本,因為容器使用反映來建立每個類型,特別是針對應用程式中每個頁面導覽重建相依性時。 如果有許多或深度相依性,則建立的成本可能會大幅增加。

註冊類型

類型必須先向相依性插入容器註冊,才能透過相依性解析方法加以解析。 下列程式代碼範例顯示範例應用程式在 類別中針對 Autofac 容器公開的 App 註冊方法:

using Autofac;
using Autofac.Core;
...

public partial class App : Application
{
    static IContainer container;
    static readonly ContainerBuilder builder = new ContainerBuilder();
    ...

    public static void RegisterType<T>() where T : class
    {
        builder.RegisterType<T>();
    }

    public static void RegisterType<TInterface, T>() where TInterface : class where T : class, TInterface
    {
        builder.RegisterType<T>().As<TInterface>();
    }

    public static void RegisterTypeWithParameters<T>(Type param1Type, object param1Value, Type param2Type, string param2Name) where T : class
    {
        builder.RegisterType<T>()
               .WithParameters(new List<Parameter>()
        {
            new TypedParameter(param1Type, param1Value),
            new ResolvedParameter(
                (pi, ctx) => pi.ParameterType == param2Type && pi.Name == param2Name,
                (pi, ctx) => ctx.Resolve(param2Type))
        });
    }

    public static void RegisterTypeWithParameters<TInterface, T>(Type param1Type, object param1Value, Type param2Type, string param2Name) where TInterface : class where T : class, TInterface
    {
        builder.RegisterType<T>()
               .WithParameters(new List<Parameter>()
        {
            new TypedParameter(param1Type, param1Value),
            new ResolvedParameter(
                (pi, ctx) => pi.ParameterType == param2Type && pi.Name == param2Name,
                (pi, ctx) => ctx.Resolve(param2Type))
        }).As<TInterface>();
    }

    public static void BuildContainer()
    {
        container = builder.Build();
    }
    ...
}

當應用程式使用相依性解析方法來解析容器中的類型時,通常會從平臺專案執行類型註冊。 這可讓平台專案註冊自定義轉譯器、效果和 DependencyService 實作的類型。

從平臺項目進行型別註冊之後, IContainer 必須建置 物件,這可藉由呼叫 BuildContainer 方法來完成。 這個方法會在 實例上ContainerBuilder叫用 Autofac Build 的 方法,此實例會建置新的相依性插入容器,其中包含已進行的註冊。

在後續各節中,實 Logger 作 介面的類別 ILogger 會插入類別建構函式中。 類別 Logger 會使用 Debug.WriteLine 方法實作簡單的記錄功能,並用來示範如何將服務插入自定義轉譯器、效果和 DependencyService 實作中。

註冊自定義轉譯器

範例應用程式包含播放 Web 影片的頁面,其 XAML 來源會顯示在下列範例中:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:video="clr-namespace:FormsVideoLibrary"
             ...>
    <video:VideoPlayer Source="https://archive.org/download/BigBuckBunny_328/BigBuckBunny_512kb.mp4" />
</ContentPage>

檢視 VideoPlayer 是由 類別 VideoPlayerRenderer 在每個平台上實作,可提供播放影片的功能。 如需這些自定義轉譯器類別的詳細資訊,請參閱 實作視訊播放程式

在 iOS 和 通用 Windows 平台 (UWP),VideoPlayerRenderer類別具有下列建構函式,需要自ILogger變數:

public VideoPlayerRenderer(ILogger logger)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

在所有平臺上,使用 相依性插入容器的類型註冊是由 方法執行 RegisterTypes ,此方法會在平臺使用 方法載入應用程式 LoadApplication(new App()) 之前叫用。 下列範例顯示 RegisterTypes iOS 平臺上的 方法:

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterType<FormsVideoLibrary.iOS.VideoPlayerRenderer>();
    App.BuildContainer();
}

在此範例中 Logger ,具體類型是透過與其介面類型的對應來註冊,而且直接 VideoPlayerRenderer 註冊類型時不會進行介面對應。 當使用者流覽至包含VideoPlayer檢視的頁面時,將會叫用相依性解析方法,以從相依性插入容器解析VideoPlayerRenderer類型,這也會解析類型並將類型VideoPlayerRenderer插入Logger建構函式中。

VideoPlayerRenderer Android 平臺上的建構函式稍微複雜一點,因為除了 自變數之外,還需要自Context變數ILogger

public VideoPlayerRenderer(Context context, ILogger logger) : base(context)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

下列範例顯示 RegisterTypes Android 平臺上的 方法:

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterTypeWithParameters<FormsVideoLibrary.Droid.VideoPlayerRenderer>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
    App.BuildContainer();
}

在這裡範例中 App.RegisterTypeWithParameters ,方法會 VideoPlayerRenderer 向相依性插入容器註冊 。 註冊方法可確保 MainActivity 實例會插入為 Context 自變數,並將 Logger 類型插入為 ILogger 自變數。

註冊效果

範例應用程式包含一個頁面,該頁面會使用觸控追蹤效果來拖曳 BoxView 頁面周圍的實例。 Effect使用下列程式代碼將 新增至 BoxView

var boxView = new BoxView { ... };
var touchEffect = new TouchEffect();
boxView.Effects.Add(touchEffect);

類別 TouchEffect 是由 RoutingEffect 類別在每個平台上 TouchEffect 實作的 PlatformEffect。 平台 TouchEffect 類別提供在頁面周圍拖曳 BoxView 的功能。 如需這些效果類別的詳細資訊,請參閱 從效果叫用事件。

在所有平臺上,類別 TouchEffect 都有下列建構函式,需要自 ILogger 變數:

public TouchEffect(ILogger logger)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

在所有平臺上,使用 相依性插入容器的類型註冊是由 方法執行 RegisterTypes ,此方法會在平臺使用 方法載入應用程式 LoadApplication(new App()) 之前叫用。 下列範例顯示 RegisterTypes Android 平臺上的 方法:

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterType<TouchTracking.Droid.TouchEffect>();
    App.BuildContainer();
}

在此範例中 Logger ,具體類型是透過與其介面類型的對應來註冊,而且直接 TouchEffect 註冊類型時不會進行介面對應。 當使用者流覽至包含BoxViewTouchEffect附加實例的頁面時,將會叫用相依性解析方法,以從相依性插入容器解析平臺TouchEffect類型,這也會解析類型,並將類型TouchEffect插入Logger建構函式中。

註冊 DependencyService 實作

範例應用程式包含一個頁面,使用 DependencyService 每個平臺上的實作,讓使用者從裝置的圖片庫挑選相片。 介面 IPhotoPicker 會定義實作所 DependencyService 實作的功能,如下列範例所示:

public interface IPhotoPicker
{
    Task<Stream> GetImageStreamAsync();
}

在每個平台專案中,類別會 PhotoPicker 使用平臺 API 實作 IPhotoPicker 介面。 如需這些相依性服務的詳細資訊,請參閱 從圖片庫挑選相片。

在 iOS 和 UWP 上 PhotoPicker ,類別具有下列建構函式,需要自 ILogger 變數:

public PhotoPicker(ILogger logger)
{
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

在所有平臺上,使用 相依性插入容器的類型註冊是由 方法執行 RegisterTypes ,此方法會在平臺使用 方法載入應用程式 LoadApplication(new App()) 之前叫用。 下列範例顯示 RegisterTypes UWP 上的 方法:

void RegisterTypes()
{
    DIContainerDemo.App.RegisterType<ILogger, Logger>();
    DIContainerDemo.App.RegisterType<IPhotoPicker, Services.UWP.PhotoPicker>();
    DIContainerDemo.App.BuildContainer();
}

在此範例中 Logger ,具體類型是透過與其介面類型的對應來註冊,而且類型 PhotoPicker 也會透過介面對應來註冊。

PhotoPicker Android 平臺上的建構函式稍微複雜一點,因為除了 自變數之外,還需要自Context變數ILogger

public PhotoPicker(Context context, ILogger logger)
{
    _context = context ?? throw new ArgumentNullException(nameof(context));
    _logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

下列範例顯示 RegisterTypes Android 平臺上的 方法:

void RegisterTypes()
{
    App.RegisterType<ILogger, Logger>();
    App.RegisterTypeWithParameters<IPhotoPicker, Services.Droid.PhotoPicker>(typeof(Android.Content.Context), this, typeof(ILogger), "logger");
    App.BuildContainer();
}

在這裡範例中 App.RegisterTypeWithParameters ,方法會 PhotoPicker 向相依性插入容器註冊 。 註冊方法可確保 MainActivity 實例會插入為 Context 自變數,並將 Logger 類型插入為 ILogger 自變數。

當使用者瀏覽至相片挑選頁面並選擇選取相片時,會 OnSelectPhotoButtonClicked 執行處理程式:

async void OnSelectPhotoButtonClicked(object sender, EventArgs e)
{
    ...
    var photoPickerService = DependencyService.Resolve<IPhotoPicker>();
    var stream = await photoPickerService.GetImageStreamAsync();
    if (stream != null)
    {
        image.Source = ImageSource.FromStream(() => stream);
    }
    ...
}

DependencyService.Resolve<T>叫用 方法時,將會叫用相依性解析方法,以從相依性插入容器解析PhotoPicker型別,這也會解析類型PhotoPicker並插入Logger建構函式中。

注意

Resolve<T>透過 解析應用程式相依性插入容器DependencyService中的型別時,必須使用 方法。