首页 > 代码库 > Xamarin+Prism开发详解六:DependencyService与IPlatformInitializer的关系
Xamarin+Prism开发详解六:DependencyService与IPlatformInitializer的关系
祝各位2017年事业辉煌!开年第一篇博客,继续探索Xamarin.Forms…
为什么我做Xamarin开发的时候中意于Prism.Forms框架?本章为你揭晓。
实例代码地址:https://github.com/NewBLife/XamarinDemo/tree/master/TextToSpeechDemo
DependencyService
1、简介
软件开发有一个原则叫【依赖倒置Dependence Inversion Principle 】
A.高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象。
B.抽象不应该依赖于具体实现,具体实现应该依赖于抽象。
Xamarin.Forms在面对无法实现的平台特有功能时就是使用以上原则设计一个叫【DependencyService】的功能。DependencyService的目的就是让PCL共通代码可以调用与平台相关的功能,它使Xamarin.Forms能像原生应用一样做任何事情!
2、工作原理
- 接口:定义功能接口在PCL类库或者共享类库
- 接口实现:各个平台实现接口功能
- 注册:各个平台实现接口的类库注册DependencyAttribute属性
- 调用:PCL类库或者共享类库调用DependencyService.Get<接口>()方法获取平台实例对象
稍微看看原代码了解Xamarin.Forms如何实现依赖注入
DependencyAttribute.cs文件,定义了程序集属性标签:
using System;namespace Xamarin.Forms{ [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] public class DependencyAttribute : Attribute { public DependencyAttribute(Type implementorType) { Implementor = implementorType; } internal Type Implementor { get; private set; } }}
DependencyService.cs文件的Get方法(实体对象默认是单例形式存在)。
static bool s_initialized; static readonly List<Type> DependencyTypes = new List<Type>(); static readonly Dictionary<Type, DependencyData> DependencyImplementations = new Dictionary<Type, DependencyData>(); public static T Get<T>(DependencyFetchTarget fetchTarget = DependencyFetchTarget.GlobalInstance) where T : class { if (!s_initialized) Initialize(); Type targetType = typeof(T); if (!DependencyImplementations.ContainsKey(targetType)) { Type implementor = FindImplementor(targetType); DependencyImplementations[targetType] = implementor != null ? new DependencyData { ImplementorType = implementor } : null; } DependencyData dependencyImplementation = DependencyImplementations[targetType]; if (dependencyImplementation == null) return null; if (fetchTarget == DependencyFetchTarget.GlobalInstance) { if (dependencyImplementation.GlobalInstance == null) { dependencyImplementation.GlobalInstance = Activator.CreateInstance(dependencyImplementation.ImplementorType); } return (T)dependencyImplementation.GlobalInstance; } return (T)Activator.CreateInstance(dependencyImplementation.ImplementorType); }
DependencyService.cs文件的Initialize方法,遍历所有程序集获取标记了DependencyAttribute属性的类型。这是不太好的地方,这样的做法性能会大打折扣,这也是为什么不推荐使用DependencyService的一个方面。
static void Initialize() { Assembly[] assemblies = Device.GetAssemblies(); if (Registrar.ExtraAssemblies != null) { assemblies = assemblies.Union(Registrar.ExtraAssemblies).ToArray(); } Type targetAttrType = typeof(DependencyAttribute); // Don‘t use LINQ for performance reasons // Naive implementation can easily take over a second to run foreach (Assembly assembly in assemblies) { Attribute[] attributes = assembly.GetCustomAttributes(targetAttrType).ToArray(); if (attributes.Length == 0) continue; foreach (DependencyAttribute attribute in attributes) { if (!DependencyTypes.Contains(attribute.Implementor)) { DependencyTypes.Add(attribute.Implementor); } } } s_initialized = true; }
3,实例使用
使用TextToSpeechDemo(文本语音)实例讲解如何使用DependencyService。
项目结构:
接口定义:
namespace TextToSpeechDemo{ public interface ITextToSpeech { void Speak(string text); }}
Android平台实现ITextToSpeech接口:API定义
最重要的[assembly: Dependency(typeof(TextToSpeech_Android))] 这句注册Dependency属性。
using Android.Runtime;using Android.Speech.Tts;using System.Collections.Generic;using TextToSpeechDemo.Droid;using Xamarin.Forms;[assembly: Dependency(typeof(TextToSpeech_Android))]namespace TextToSpeechDemo.Droid{ public class TextToSpeech_Android : Java.Lang.Object, ITextToSpeech, TextToSpeech.IOnInitListener { TextToSpeech speaker; string toSpeak; public TextToSpeech_Android() { } public void Speak(string text) { var ctx = Forms.Context; toSpeak = text; if (speaker == null) { speaker = new TextToSpeech(ctx, this); } else { var p = new Dictionary<string, string>(); speaker.Speak(toSpeak, QueueMode.Flush, p); } } public void OnInit([GeneratedEnum] OperationResult status) { if (status.Equals(OperationResult.Success)) { System.Diagnostics.Debug.WriteLine("speaker init"); var p = new Dictionary<string, string>(); speaker.Speak(toSpeak, QueueMode.Flush, p); } else { System.Diagnostics.Debug.WriteLine("was quiet"); } } }}
iOS平台实现ITextToSpeech接口:
最重要的[assembly: Dependency(typeof(TextToSpeech_iOS))] 这句注册Dependency属性。
using AVFoundation;using TextToSpeechDemo.iOS;using Xamarin.Forms;[assembly: Dependency(typeof(TextToSpeech_iOS))]namespace TextToSpeechDemo.iOS{ class TextToSpeech_iOS : ITextToSpeech { public void Speak(string text) { var speechSynthesizer = new AVSpeechSynthesizer(); var speechUtterance = new AVSpeechUtterance(text) { Rate = AVSpeechUtterance.MaximumSpeechRate / 4, Voice = AVSpeechSynthesisVoice.FromLanguage("en-US"), Volume = 0.5f, PitchMultiplier = 1.0f }; speechSynthesizer.SpeakUtterance(speechUtterance); } }}
UWP平台实现ITextToSpeech接口:
最重要的[assembly: Dependency(typeof(TextToSpeech_UWP))] 这句注册Dependency属性。
using System;using TextToSpeechDemo.UWP;using Windows.Media.SpeechSynthesis;using Windows.UI.Xaml.Controls;using Xamarin.Forms;[assembly: Dependency(typeof(TextToSpeech_UWP))]namespace TextToSpeechDemo.UWP{ class TextToSpeech_UWP : ITextToSpeech { public async void Speak(string text) { MediaElement mediaElement = new MediaElement(); var synth = new SpeechSynthesizer(); var stream = await synth.SynthesizeTextToStreamAsync(text); mediaElement.SetSource(stream, stream.ContentType); mediaElement.Play(); } }}
调用平台特性的时候通过DependencyService.Get<T>()实现:
public void btnSpeak_Clicked(object sender, EventArgs args) { DependencyService.Get<ITextToSpeech>().Speak(txtData.Text.Trim()); }
整体效果:
IPlatformInitializer
1、简介
IPlatformInitializer其实为Prism.Forms共通类库里面的一个接口,代码如下:
namespace Prism{ public interface IPlatformInitializer<T> { void RegisterTypes(T container); }}
包含一个注册类型函数(注册实现了平台特性的类型)。至于为什么是泛型接口?这是为了支持多种IOC容器(AutoFac,Unity,DryIoc,Ninject等),主流为Unity。Unity的IPlatformInitializer代码如下:传入了Unity的容器类型IUnityContainer
using Microsoft.Practices.Unity;namespace Prism.Unity{ public interface IPlatformInitializer : IPlatformInitializer<IUnityContainer> { }}
2、工作原理
- 接口:定义功能接口在PCL类库或者共享类库
- 接口实现:各个平台实现接口功能
- 注册:各个平台实现IPlatformInitializer接口,并在RegisterTypes方法中将实现接口的类注册到IOC容器内
- 调用:ViewModel的构造函数添加接口为参数(Prism.Forms会自动从IOC容器加载)
调用RegisterTypes是在Prism.Forms共通类库里面PrismApplicationBase<T>的构造函数中:
protected PrismApplicationBase(IPlatformInitializer<T> initializer = null) { base.ModalPopping += PrismApplicationBase_ModalPopping; base.ModalPopped += PrismApplicationBase_ModalPopped; _platformInitializer = initializer; InitializeInternal(); } /// <summary> /// Run the intialization process. /// </summary> void InitializeInternal() { ConfigureViewModelLocator(); Initialize(); OnInitialized(); } /// <summary> /// Run the bootstrapper process. /// </summary> public virtual void Initialize() { Logger = CreateLogger(); ModuleCatalog = CreateModuleCatalog(); ConfigureModuleCatalog(); Container = CreateContainer(); ConfigureContainer(); NavigationService = CreateNavigationService(); RegisterTypes(); _platformInitializer?
.RegisterTypes(Container); InitializeModules(); }
3,实例使用
使用PrismTextToSpeech(文本语音)实例讲解如何使用IPlatformInitializer。
项目结构:
接口定义:
namespacePrismTextToSpeech.Services
{ public interface ITextToSpeech { void Speak(string text); }}
Android平台实现ITextToSpeech接口:API定义
与DependencyService的区别是没有Dependency属性。
using Android.Runtime;using Android.Speech.Tts;using PrismTextToSpeech.Services;using System.Collections.Generic;using Xamarin.Forms;namespace PrismTextToSpeech.Droid{ public class TextToSpeech_Android : Java.Lang.Object, ITextToSpeech, TextToSpeech.IOnInitListener { TextToSpeech speaker; string toSpeak; public TextToSpeech_Android() { } public void Speak(string text) { var ctx = Forms.Context; toSpeak = text; if (speaker == null) { speaker = new TextToSpeech(ctx, this); } else { var p = new Dictionary<string, string>(); speaker.Speak(toSpeak, QueueMode.Flush, p); } } public void OnInit([GeneratedEnum] OperationResult status) { if (status.Equals(OperationResult.Success)) { System.Diagnostics.Debug.WriteLine("speaker init"); var p = new Dictionary<string, string>(); speaker.Speak(toSpeak, QueueMode.Flush, p); } else { System.Diagnostics.Debug.WriteLine("was quiet"); } } }}
注册类型到IOC容器:
using Android.App;using Android.Content.PM;using Android.OS;using Microsoft.Practices.Unity;using Prism.Unity;using PrismTextToSpeech.Services;namespace PrismTextToSpeech.Droid{ [Activity(Label = "PrismTextToSpeech", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity { protected override void OnCreate(Bundle bundle) { TabLayoutResource = Resource.Layout.tabs; ToolbarResource = Resource.Layout.toolbar; base.OnCreate(bundle); global::Xamarin.Forms.Forms.Init(this, bundle); LoadApplication(new App(new AndroidInitializer())); } } public class AndroidInitializer : IPlatformInitializer { public void RegisterTypes(IUnityContainer container) { container.RegisterType<ITextToSpeech, TextToSpeech_Android>
(); } }}
iOS与UWP的接口实现与DependencyService的一样,唯独就是没有Dependency属性,这里略过。
调用的时候:
using Prism.Commands;using Prism.Mvvm;using PrismTextToSpeech.Services;namespace PrismTextToSpeech.ViewModels{ public class MainPageViewModel : BindableBase { private ITextToSpeech _textToSpeech; private string _speakText; public string SpeakText { get { return _speakText; } set { SetProperty(ref _speakText, value); SpeakCommand.RaiseCanExecuteChanged(); } } public MainPageViewModel(ITextToSpeech textToSpeech) { _textToSpeech=
textToSpeech; } public DelegateCommand SpeakCommand => new DelegateCommand( () => { _textToSpeech.Speak(SpeakText); }, () => !string.IsNullOrEmpty(SpeakText)).ObservesProperty(() => this.SpeakText); }}
Prism就是这么简单,效果更佳:
DependencyAttribute+IPlatformInitializer
1、简介
这种方式是Prism为了兼容DepdencyService而创建的,及Prism内部封装了DependencyService。
namespace Prism.Services{ /// <summary> /// A service that provides acess to platform-specific implementations of a specified type /// </summary> public class DependencyService : IDependencyService { /// <summary> /// Returns a platform-specific implementation of a type registered with the Xamarin.Forms.DependencyService /// </summary> /// <typeparam name="T">The type of class to get</typeparam> /// <returns>The class instance</returns> public T Get<T>() where T : class { return Xamarin.Forms.DependencyService.Get<T>(); } }}
2、使用方法
- 接口:与DependencyService或者IPlatformInitializer实例一样
- 接口实现:与DependencyService实例一样
- 注册:与DependencyService实例一样,各个平台实现接口的类库注册DependencyAttribute属性
- 调用:与IPlatformInitializer实例一样,ViewModel的构造函数添加接口为参数(Prism.Forms会自动从IOC容器加载)
总结
DependencyService其实就是依赖注入的自我实现,而Prism的IPlatformInitializer则巧妙的借助Unity等容器达到同样的目的。不过从应用以后扩展角度也好,性能角度也好还是建议使用IOC容器技术(Prism创始人Brian Lagunas也是这么建议的)。特别是在使用Mvvm模式开发的时候,更加需要依赖IOC容器来管理ViewModel与Service,这也是我选择Prism做Xamarin开发的原因之一。
Xamarin+Prism开发详解六:DependencyService与IPlatformInitializer的关系