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開發的原因之一。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM