中的相依性解析 Xamarin.Forms
本文說明如何將相依性解析方法 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 方法來解析已註冊的型別的相依性解析方法。
插入相依性解析方法
類別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 作為相依性插入容器,其提供 IContainer
和 ContainerBuilder
類型。 您也可以使用替代相依性插入容器,但會使用與這裡不同的 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
註冊類型時不會進行介面對應。 當使用者流覽至包含BoxView
TouchEffect
附加實例的頁面時,將會叫用相依性解析方法,以從相依性插入容器解析平臺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
中的型別時,必須使用 方法。