Xamarin+Prism开发详解七:Plugin开发与打包测试
有了上章【Xamarin+Prism开发详解六:DependencyService与IPlatformInitializer的关系】的基础,现在来理解Plugin开发就简单了。
本文实例代码地址:https://github.com/NewBLife/XamarinDemo/tree/master/Speecher
简介
Plugin其实就是各类相对独立的功能提取出来的Package,一般都不引用Xamarin相关类库,比如上章的Text To Speech功能(Github库地址https://github.com/jamesmontemagno/TextToSpeechPlugin):
xamarin的Plugin有一个专门的统计库【xamarin/XamarinComponents】,目前统计结果如下:
Battery StatusGather battery level, charging status, and type.NuGetGitHub@JamesMontemagnoBarcode ScannerScan and create barcodes with ZXing.NET.Mobile.NuGetGitHub@RedthBluetooth LEScan and connect to Bluetooth devices.NuGetGitHub@allanritchie911CalendarQuery and modify device calendarsNuGetGitHubCaleb ClarkeCompassAccess device compass heading.NuGetGitHub@cbartonnh & @JamesMontemagnoConnectivityGet network connectivity info such as type and if connection is available.NuGetGitHub@JamesMontemagnoCryptographyPCL Crypto provides a consistent, portable set of crypto APIs.NuGetGitHub@aarnottDevice InfoProperties about device such as OS, Model, and Id.NuGetGitHub@JamesMontemagnoDevice MotionProvides access to Accelerometer, Gyroscope, Magnetometer, and Compass.NuGetGitHub@rdelrosarioEmbedded ResourceUnpack embedded resource cross-platform.NuGetGitHub@JosephHillExternal MapsLaunch external maps from Lat/Long or Address.NuGetGitHub@JamesMontemagnoFile Storage/File SystemPCL Storage offers cross platform storage APIs.NuGetGitHub@dsplaistedFile PickerPick and save files.NuGetGitHub@studyxnetFingerprintAccess Fingerprint sensor on iOS, Android, and Windows.NuGetGitHub@smstuebeFFImageLoadingImage loading with caching, placeholders, transformations and moreNuGetGitHub@molinch, @daniel-luberdaGeofencingMonitor regions when user enters/exits.NuGetGitHub@allanritchie911GeolocatorEasily detect GPS location of device.NuGetGitHub@JamesMontemagnoiBeacon & EstimoteRange and monitor Bluetooth beacons.NuGetGitHub@allanritchie911LampAccess to LEDNuGetGitHub@kphillpottsLocal NotificationsShow local notificationsNuGetGitHub@EdSnider, @JamesMontemagnoManage SleepManage auto sleep/auto lock.NuGetGitHub@molinch0MediaTake or pick photos and videos.NuGetGitHub@JamesMontemagnoMedia ManagerPlayback for Audio.NuGetGitHub@mhvdijkMessagingMake phone call, send sms, and send e-mailNuGetGitHub@cjlotzMicrosoft BandConnect and communicate with the Microsoft Band from shared code!NuGetGitHub@mattleibowMono.Data.SqliteAdd Mono.Data.Sqlite to any Xamarin or Windows .NET app.NuGetGitHub@mattleibowPermissionsEasily check and request runtime permissions.NuGetGitHub@JamesMontemagnoPersistent key-value storeAkavache is an asynchronous, persistent (i.e. writes to disk) key-value store.NuGetGitHub@paulcbettsPortable RazorLightweight implemenation of ASP.NET MVC APIs for mobile.NuGetGitHub@JosephHillPush NotificationsCross platform iOS and Android Push Notifications.NuGetGitHub@rdelrosarioSecure StorageProvides secure storage for key value pairs DataNuGetGitHub@sameerIOTAppsSettingsSimple & Consistent cross platform settings API.NuGetGitHub@JamesMontemagnoShareEasily share text, links, or open a browser.NuGetGitHub@JamesMontemagno & @Jakob GürtlSocketsTCP & UDP Listeners and Clients + UDP multicast.NuGetGitHub@rdavis_auSpeech RecognitionSpeech to Text.NuGetGitHub@allanritchie911Text To SpeechTalk back text from shared code.NuGetGitHub@JamesMontemagnoToastA simple way of showing toast/pop-up notifications.NuGetGitHub@AdamPed & @EgorBoUser DialogsMessage box style dialogs.NuGetGitHub@allanritchie911Version TrackingTrack which versions of your app a user has previously installed.NuGetGitHub@ColbyLWilliamsVibrateVibrate any device.NuGetGitHub@JamesMontemagno开发实践
Xamarin为Plugin开发提供了一个Visual Studio模板,通过它你可以快速开发各类Plugin。
1、安装Plugin for Xamarin Templates
2、从Plugin for Xamarin模板新建项目
整体项目结构:
通过模板创建的项目包含三类工程文件:
- Plugin.功能名(PCL):懒汉式实例创建文件,生成Plugin.功能名.dll。(共享类文件)
- Plugin.功能名.Abstractions(PCL):接口和Enums的定义,生成Plugin.功能名.Abstractions.dll。
- Plugin.功能名.平台名(n个):接口实现,生成Plugin.功能名.dll。
3、添加接口
namespace Plugin.Speecher.Abstractions { /// <summary> /// Interface for Speecher /// </summary> public interface ISpeecher { void Speak(string text); } }
4、各个平台接口实现
iOS平台
using AVFoundation; using Plugin.Speecher.Abstractions; namespace Plugin.Speecher { /// <summary> /// Implementation for Speecher /// </summary> public class SpeecherImplementation : ISpeecher { 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); } } }
Android平台(由于没有Xamarin.Forms,所以这里使用Application.Context)
using Android.App; using Android.Runtime; using Android.Speech.Tts; using Plugin.Speecher.Abstractions; using System.Collections.Generic; namespace Plugin.Speecher { /// <summary> /// Implementation for Feature /// </summary> public class SpeecherImplementation : Java.Lang.Object, ISpeecher, TextToSpeech.IOnInitListener { TextToSpeech speaker; string toSpeak; public SpeecherImplementation() { } public void Speak(string text) { var ctx = Application.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)) { var p = new Dictionary<string, string>(); speaker.Speak(toSpeak, QueueMode.Flush, p); } else { System.Diagnostics.Debug.WriteLine("was quiet"); } } } }
UWP平台
using Plugin.Speecher.Abstractions; using System; using Windows.Media.SpeechSynthesis; using Windows.UI.Xaml.Controls; namespace Plugin.Speecher { /// <summary> /// Implementation for Feature /// </summary> public class SpeecherImplementation : ISpeecher { 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(); } } }
备注
如果确定一直使用Prism做开发的话,只需要定义接口与接口的实现就可以了。然后在实际项目各个平台的RegisterTypes方法中注册接口到IOC容器,
public void RegisterTypes(IUnityContainer container){ container.RegisterType<ISpeecher, SpeecherImplementation>();}
使用IOC容器管理对象生存周期,比懒汉式加载更加自由方便。比如在Prism.Forms中只需构造函数添加接口作为参数就可以自动创建对象。
打包并测试
在安装Plugin for Xamarin Templates的时候其实已经安装了另外一个打包用的模板【Plugin for Xamarin NuSpec】,通过它可以快速打包。
1、添加Package配置文件【Speecher.Plugin.nuspec】
默认生成的文件如下,包含所有平台:
<?xml version="1.0" encoding="utf-8"?> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata minClientVersion="2.8.3"> <id>Plugin.Speecher</id> <version>1.0.0</version> <title>Speecher Plugin for Xamarin and Windows</title> <authors>Your Name</authors> <owners>Your Name</owners> <licenseUrl/> <projectUrl/> <!--Default Icon, a template can be found: https://raw.githubusercontent.com/jamesmontemagno/Xamarin-Templates/master/Plugins-Templates/icons/plugin_icon.png--> <iconUrl>https://raw.githubusercontent.com/jamesmontemagno/Xamarin-Templates/master/Plugins-Templates/icons/plugin_icon_nuget.png</iconUrl> <requireLicenseAcceptance>false</requireLicenseAcceptance> <description> Long description for your plugin. </description> <summary>Short description for your plugin.</summary> <tags>xamarin, pcl, xam.pcl, plugin, plugin for xamarin, windows phone, winphone, wp8, winrt, android, xamarin.forms, ios</tags> <dependencies> <group targetFramework="net"> </group> <group targetFramework="win"> </group> <group targetFramework="wp"> </group> <group targetFramework="wpa"> </group> <group targetFramework="MonoAndroid"> </group> <group targetFramework="Xamarin.iOS10"> </group> <group targetFramework="Xamarin.Mac20"> </group> <group targetFramework="portable-net45+win+wpa81+wp80"> </group> <group targetFramework="uap"> </group> <group targetFramework="dotnet"> </group> </dependencies> </metadata> <files> <!--Core--> <file src="Speecher\Plugin.Speecher\bin\Release\Plugin.Speecher.dll" target="lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10+UAP10\Plugin.Speecher.dll" /> <file src="Speecher\Plugin.Speecher\bin\Release\Plugin.Speecher.xml" target="lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10+UAP10\Plugin.Speecher.xml" /> <file src="Speecher\Plugin.Speecher\bin\Release\Plugin.Speecher.pdb" target="lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10+UAP10\Plugin.Speecher.pdb" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.dll" target="lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10+UAP10\Plugin.Speecher.Abstractions.dll" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.xml" target="lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10+UAP10\Plugin.Speecher.Abstractions.xml" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.pdb" target="lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10+UAP10\Plugin.Speecher.Abstractions.pdb" /> <!--dotnet--> <file src="Speecher\Plugin.Speecher\bin\Release\Plugin.Speecher.dll" target="lib\dotnet\Plugin.Speecher.dll" /> <file src="Speecher\Plugin.Speecher\bin\Release\Plugin.Speecher.xml" target="lib\dotnet\Plugin.Speecher.xml" /> <file src="Speecher\Plugin.Speecher\bin\Release\Plugin.Speecher.pdb" target="lib\dotnet\Plugin.Speecher.pdb" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.dll" target="lib\dotnet\Plugin.Speecher.Abstractions.dll" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.xml" target="lib\dotnet\Plugin.Speecher.Abstractions.xml" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.pdb" target="lib\dotnet\Plugin.Speecher.Abstractions.pdb" /> <!--Win Phone Silverlight--> <file src="Speecher\Plugin.Speecher.WindowsPhone8\bin\Release\Plugin.Speecher.dll" target="lib\wp8\Plugin.Speecher.dll" /> <file src="Speecher\Plugin.Speecher.WindowsPhone8\bin\Release\Plugin.Speecher.xml" target="lib\wp8\Plugin.Speecher.xml" /> <file src="Speecher\Plugin.Speecher.WindowsPhone8\bin\Release\Plugin.Speecher.pdb" target="lib\wp8\Plugin.Speecher.pdb" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.dll" target="lib\wp8\Plugin.Speecher.Abstractions.dll" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.xml" target="lib\wp8\Plugin.Speecher.Abstractions.xml" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.pdb" target="lib\wp8\Plugin.Speecher.Abstractions.pdb" /> <!--Win Phone 81--> <file src="Speecher\Plugin.Speecher.WindowsPhone81\bin\Release\Plugin.Speecher.dll" target="lib\wpa81\Plugin.Speecher.dll" /> <file src="Speecher\Plugin.Speecher.WindowsPhone81\bin\Release\Plugin.Speecher.xml" target="lib\wpa81\Plugin.Speecher.xml" /> <file src="Speecher\Plugin.Speecher.WindowsPhone81\bin\Release\Plugin.Speecher.pdb" target="lib\wpa81\Plugin.Speecher.pdb" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.dll" target="lib\wpa81\Plugin.Speecher.Abstractions.dll" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.xml" target="lib\wpa81\Plugin.Speecher.Abstractions.xml" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.pdb" target="lib\wpa81\Plugin.Speecher.Abstractions.pdb" /> <!--WinStore--> <file src="Speecher\Plugin.Speecher.WindowsStore\bin\Release\Plugin.Speecher.dll" target="lib\win8\Plugin.Speecher.dll" /> <file src="Speecher\Plugin.Speecher.WindowsStore\bin\Release\Plugin.Speecher.xml" target="lib\win8\Plugin.Speecher.xml" /> <file src="Speecher\Plugin.Speecher.WindowsStore\bin\Release\Plugin.Speecher.pdb" target="lib\win8\Plugin.Speecher.pdb" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.dll" target="lib\win8\Plugin.Speecher.Abstractions.dll" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.xml" target="lib\win8\Plugin.Speecher.Abstractions.xml" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.pdb" target="lib\win8\Plugin.Speecher.Abstractions.pdb" /> <!--Xamarin.Android--> <file src="Speecher\Plugin.Speecher.Android\bin\Release\Plugin.Speecher.dll" target="lib\MonoAndroid10\Plugin.Speecher.dll" /> <file src="Speecher\Plugin.Speecher.Android\bin\Release\Plugin.Speecher.xml" target="lib\MonoAndroid10\Plugin.Speecher.xml" /> <file src="Speecher\Plugin.Speecher.Android\bin\Release\Plugin.Speecher.pdb" target="lib\MonoAndroid10\Plugin.Speecher.pdb" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.dll" target="lib\MonoAndroid10\Plugin.Speecher.Abstractions.dll" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.xml" target="lib\MonoAndroid10\Plugin.Speecher.Abstractions.xml" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.pdb" target="lib\MonoAndroid10\Plugin.Speecher.Abstractions.pdb" /> <!--Xamarin.iOS--> <file src="Speecher\Plugin.Speecher.iOS\bin\iPhone\Release\Plugin.Speecher.dll" target="lib\Xamarin.iOS10\Plugin.Speecher.dll" /> <file src="Speecher\Plugin.Speecher.iOS\bin\iPhone\Release\Plugin.Speecher.xml" target="lib\Xamarin.iOS10\Plugin.Speecher.xml" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.dll" target="lib\Xamarin.iOS10\Plugin.Speecher.Abstractions.dll" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.xml" target="lib\Xamarin.iOS10\Plugin.Speecher.Abstractions.xml" /> <!--uap--> <file src="Speecher\Plugin.Speecher.UWP\bin\Release\Plugin.Speecher.dll" target="lib\UAP10\Plugin.Speecher.dll" /> <file src="Speecher\Plugin.Speecher.UWP\bin\Release\Plugin.Speecher.xml" target="lib\UAP10\Plugin.Speecher.xml" /> <file src="Speecher\Plugin.Speecher.UWP\bin\Release\Plugin.Speecher.pdb" target="lib\UAP10\Plugin.Speecher.pdb" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.dll" target="lib\UAP10\Plugin.Speecher.Abstractions.dll" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.xml" target="lib\UAP10\Plugin.Speecher.Abstractions.xml" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.pdb" target="lib\UAP10\Plugin.Speecher.Abstractions.pdb" /> <!--Xamarin.Mac <file src="Speecher\Plugin.Speecher.Mac\bin\iPhone\Release\Plugin.Speecher.dll" target="lib\Xamarin.Mac20\Plugin.Speecher.dll" /> <file src="Speecher\Plugin.Speecher.Mac\bin\iPhone\Release\Plugin.Speecher.xml" target="lib\Xamarin.Mac20\Plugin.Speecher.xml" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.dll" target="lib\Xamarin.Mac20\Plugin.Speecher.Abstractions.dll" /> <file src="Speecher\Plugin.Speecher.Abstractions\bin\Release\Plugin.Speecher.Abstractions.xml" target="lib\Xamarin.Mac20\Plugin.Speecher.Abstractions.xml" /> --> </files> </package>
- 由于只正对iOS,Android,UWP三个平台,其他的配置都删除掉
- 固定文件名用*代替
修改后的文件如下:
<?xml version="1.0" encoding="utf-8"?> <package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"> <metadata minClientVersion="2.8.3"> <id>Plugin.Speecher</id> <version>1.0.0</version> <title>Speecher Plugin for Xamarin and Windows</title> <authors>NewBlifes</authors> <owners>NewBlifes</owners> <iconUrl>https://github.com/NewBLife/NewBlife.Core/blob/master/logo.png</iconUrl> <requireLicenseAcceptance>false</requireLicenseAcceptance> <description> Text to speech plugin. </description> <summary>Text to speech plugin.</summary> <tags>xamarin, pcl, xam.pcl, plugin, plugin for xamarin, windows phone, UWP, android, xamarin.forms, ios</tags> <dependencies> <group targetFramework="MonoAndroid"> </group> <group targetFramework="Xamarin.iOS10"> </group> <group targetFramework="portable-net45+win+wpa81+wp80"> </group> <group targetFramework="uap"> </group> </dependencies> </metadata> <files> <!--Core--> <file src="Speecher\Plugin.Speecher\bin\Release\Plugin.Speecher.*" target="lib\portable-net45+wp8+wpa81+win8+MonoAndroid10+MonoTouch10+Xamarin.iOS10+UAP10\" /> <!--Xamarin.Android--> <file src="Speecher\Plugin.Speecher.Android\bin\Release\Plugin.Speecher.*" target="lib\MonoAndroid10\" /> <!--Xamarin.iOS--> <file src="Speecher\Plugin.Speecher.iOS\bin\iPhone\Release\Plugin.Speecher.*" target="lib\Xamarin.iOS10\" /> <!--uap--> <file src="Speecher\Plugin.Speecher.UWP\bin\Release\Plugin.Speecher.*" target="lib\UAP10\" /> </files> </package>
2、编译解决方案的Release版本,使用【nuget pack】命令打包
如果没有Nuget的命令行工具记得安装。
3、测试
添加本地包路径
创建测试项目,添加Package引用
测试代码
using Plugin.Speecher; using Prism.Commands; using Prism.Mvvm; namespace SpeecherTest.ViewModels { public class MainPageViewModel : BindableBase { private string _speakText; public string SpeakText { get { return _speakText; } set { SetProperty(ref _speakText, value); } } public MainPageViewModel() { } public DelegateCommand SpeakCommand => new DelegateCommand( () => { CrossSpeecher.Current.Speak(SpeakText); }, () => !string.IsNullOrEmpty(SpeakText)).ObservesProperty(() => this.SpeakText); } }
总结
通过使用Plugin For xamarin模块来开发Plugin简单又快速,你只需要关注真正的逻辑就可以。