祝各位2017年事业辉煌!开年第一篇博客,继续探索Xamarin.Forms…
为什么我做Xamarin开发的时候中意于Prism.Forms框架?本章为你揭晓。
实例代码地址:https://github.com/NewBLife/XamarinDemo/tree/master/TextToSpeechDemo
DependencyService
1、简介
软件开发有一个原则叫【依赖倒置Dependence Inversion Principle 】
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 == ) 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 / ,
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开发的原因之一。