首页 > 代码库 > 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>的构造函数中:

IPlatformInitializer<T> _platformInitializer = null;
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的关系