• 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、工作原理

    untitled

    • 接口:定义功能接口在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。

    项目结构:

    image

    接口定义:

    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());
            }

    整体效果:

    image

    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

    项目结构:

    image

    接口定义:

    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就是这么简单,效果更佳:

    image

    image

    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开发的原因之一。

  • 相关阅读:
    C#中如何求时间间隔?
    Ilist<T> 转换成 DataSet
    EditPlus 快捷键
    Array和ArrayList的异同点
    sql server 查询数据库中有多少个表
    jquery + Css 模式对话框
    paddingtop、margintop和top的区别
    JQuery之ContextMenu(右键菜单)
    关于TextBox的Enable与ReadOnly属性
    AjaxToollit 3.5 使用整理
  • 原文地址:https://www.cnblogs.com/lixiaobin/p/DependenceServiceVSIPlatformInitializer.html
Copyright © 2020-2023  润新知