Xamarin+Prism开发详解六:DependencyService与IPlatformInitializer的关系
Posted 云中客
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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的关系的主要内容,如果未能解决你的问题,请参考以下文章
Xamarin+Prism开发详解三:Visual studio 2017 RC初体验
Xamarin+Prism开发详解二:Xaml文件如何简单绑定Resources资源文件内容
Xamarin+Prism开发详解四:简单Mac OS 虚拟机安装方法与Visual Studio for Mac 初体验
在Xamarin+Prism开发详解三:Visual studio 2017 RC初体验中分享了Visual studio 2017RC的大致情况,同时也发现大家对新的Visual Studio很