上回講到,做爲一個長期散播溫暖,散播但願的小清新無公害WP開發者,繼QQ SDK以後,又把UWP微信SDK這茬告終了,僅供學習交流。html
微信官方此前明確說明短期內暫不提供RT版和UWP版的微信SDK,但眼見UWP開發之勢愈烈,微信分享也必然是許多應用的標配功能,那怎麼辦呢,本身移植成UWP版吧。拙者提供的微信SDK是基於官方silverlight版sdk反編譯後從新打包封裝成UWP版本的,對部份內容稍加修改,命名空間、使用方法基本與官方文檔無異。linux
經過nuget下載並安裝微信SDK,連接https://www.nuget.org/packages/WeChatSDK/git
或者在控制檯輸入PM> Install-Package WeChatSDKgithub
注意項目會引用依賴Google.ProtocolBuffersLite,若是安裝完成以後該依賴未自動安裝,建議您Unload後Reload一下項目,或者手動安裝該依賴windows
PM> Install-Package Google.ProtocolBuffersLiteapi
另外,本項目代碼已放在github上,請高手幫忙改進https://github.com/zhxilin/WeChatSDK微信
到微信開放平臺官網https://open.weixin.qq.com/,建立應用得到AppId和AppSecret。數據結構
與QQ SDK不一樣,微信SDK僅使用文件關聯協議進行應用間通訊,爲此須要對配置文件進行配置。打開Package.appxmanifest文件,在<Extensions>節點下添加windows.fileTypeAssociation的聲明:app
<Extensions> <uap:Extension Category="windows.fileTypeAssociation"> <uap:FileTypeAssociation Name="wechat"> <uap:SupportedFileTypes> <uap:FileType>.[YOUR APP ID]</uap:FileType> </uap:SupportedFileTypes> </uap:FileTypeAssociation> </uap:Extension> </Extensions>
注意不要漏了appid前面的「.」框架
微信消息的基類是WXBaseMessage類,包含屬性Title、Description和ThumbData,其中Title的最大長度爲512B,Description最大長度爲1K,ThumbData最大長度爲32K,不然會引起WXException錯誤。根據不一樣場合消息又分爲8種類型,均集成自WXBaseMessage類:
WXTextMessage,文本消息,Type = WXBaseMessage.TYPE_TEXT
WXImageMessage,圖片消息,Type = WXBaseMessage.TYPE_IMAGE
WXMusicMessage,音樂消息,Type = WXBaseMessage.TYPE_MUSIC
WXVideoMessage,視頻消息,Type = WXBaseMessage.TYPE_VIDEO
WXWebpageMessage,網頁消息,Type = WXWebpageMessage.TYPE_URL
WXFileMessage,文件消息,Type = WXWebpageMessage.TYPE_FILE
WXAppExtendMessage,App擴展消息,Type = WXWebpageMessage.TYPE_APPDATA
WXEmojiMessage,表情消息,Type = WXEmojiMessage.TYPE_EMOJI
根據經常使用性,這裏列舉文本、圖片和網頁三種消息的分享方法,直接列舉代碼說明。如下是消息分享均經過打包消息數據後,發送SendMessageToWX.Req類型的請求來呼起微信客戶端。
1 try 2 { 3 var scene = SendMessageToWX.Req.WXSceneTimeline; 4 var message = new WXTextMessage 5 { 6 Title = "Sharing a text title!", 7 Text = "This is text content", 8 Description = "This is a text message.這是一個文本消息。", 9 ThumbData = null 10 }; 11 SendMessageToWX.Req req = new SendMessageToWX.Req(message, scene); 12 IWXAPI api = WXAPIFactory.CreateWXAPI("[YOUR APP ID]"); 13 var isValid = await api.SendReq(req); 14 } 15 catch (WXException ex) 16 { 17 Debug.WriteLine(ex.Message); 18 }
其中scene有3種類型能夠選擇:
SendMessageToWX.Req.WXSceneChooseByUser // 用戶自行選擇 SendMessageToWX.Req.WXSceneSession // 分享給好友 SendMessageToWX.Req.WXSceneTimeline // 分享到朋友圈
其中Text屬性是Text類型消息的專有屬性,最大長度640K。
1 try 2 { 3 var scene = SendMessageToWX.Req.WXSceneTimeline; 4 var file = await Package.Current.InstalledLocation.GetFileAsync("1.png"); 5 using (var stream = await file.OpenReadAsync()) 6 { 7 var pic = new byte[stream.Size]; 8 await stream.AsStream().ReadAsync(pic, 0, pic.Length); 9 var message = new WXImageMessage 10 { 11 Title = "Sharing a picture!", 12 Description = "This is a image message.這是一個圖片消息", 13 ThumbData = pic, 14 ImageUrl = "http://tp3.sinaimg.cn/1882347990/180/5725518284/1" 15 }; 16 SendMessageToWX.Req req = new SendMessageToWX.Req(message, scene); 17 IWXAPI api = WXAPIFactory.CreateWXAPI("[YOUR APP ID]"); 18 var isValid = await api.SendReq(req); 19 } 20 } 21 catch (WXException ex) 22 { 23 Debug.WriteLine(ex.Message); 24 }
其中ImageUrl和ImageData只能設置一個,ImageData不能超過10M。
1 try 2 { 3 var scene = SendMessageToWX.Req.WXSceneTimeline; 4 var file = await Package.Current.InstalledLocation.GetFileAsync("1.png"); 5 using (var stream = await file.OpenReadAsync()) 6 { 7 var pic = new byte[stream.Size]; 8 await stream.AsStream().ReadAsync(pic, 0, pic.Length); 9 var message = new WXWebpageMessage 10 { 11 WebpageUrl = "http://www.baidu.com", 12 Title = "Sharing a link!", 13 Description = "This is a link message.這是一個連接消息", 14 ThumbData = pic 15 }; 16 SendMessageToWX.Req req = new SendMessageToWX.Req(message, scene); 17 IWXAPI api = WXAPIFactory.CreateWXAPI("[YOUR APP ID]"); 18 var isValid = await api.SendReq(req); 19 } 20 } 21 catch (WXException ex) 22 { 23 Debug.WriteLine(ex.Message); 24 }
其中WebpageUrl不能超過10K。
登陸受權向微信客戶端發出的請求類型是SendAuth.Req
1 try 2 { 3 SendAuth.Req req = new SendAuth.Req("[YOUR SCOPE]", "test"); 4 IWXAPI api = WXAPIFactory.CreateWXAPI("[YOUR APP ID]"); 5 var isValid = await api.SendReq(req); 6 } 7 catch (WXException ex) 8 { 9 Debug.WriteLine(ex.Message); 10 }
其中[YOUR SCOPE]指的是在開放平臺註冊應用,開通登陸受權後的權限範圍。
SDK也提供了微信支付的請求類型SendPay.Req,因爲支付權限申請比較困難,暫時沒有測試,之後有實際場景再試。
微信SDK提供了WXEntryBasePage類,繼承自Page類,用來響應微信的回調。
若是你的頁面基類仍然是Page,能夠替換爲WXEntryBasePage做爲你的Page基類,直接重載各類Response方法便可獲得回調結果;若是你已有自定義的Page基類,則經過另外的途徑獲取回調結果。
WXEntryBasePage類提供public void Handle(FileActivatedEventArgs e)方法來處理回調,咱們僅需在App.xaml.cs中重載OnFileActivated方法,將參數傳入Hadle方法便可進行結果解析,並將不一樣類型的消息結果經過不一樣的響應方法進行通知。
WXEntryBasePage類包含如下Response方法:
1 public virtual void OnSendAuthResponse(SendAuth.Resp response) 2 { 3 } 4 5 public virtual void OnSendMessageToWXResponse(SendMessageToWX.Resp response) 6 { 7 } 8 9 public virtual void OnSendPayResponse(SendPay.Resp response) 10 { 11 }
以上三種Response方法分別對應登陸受權、消息分享以及支付等操做的結果。
1 public override async void OnSendMessageToWXResponse(SendMessageToWX.Resp response) 2 { 3 base.OnSendMessageToWXResponse(response); 4 var dialog = new MessageDialog(response.ErrCode == 0 ? "分享成功": "分享失敗"); 5 await dialog.ShowAsync(); 6 }
1 public class WeChatCallback : WXEntryBasePage 2 { 3 }
並在App.xaml.cs的OnFileActivated方法中,建立該對象,並調用其Handle方法:
1 protected override void OnFileActivated(FileActivatedEventArgs args) 2 { 3 base.OnFileActivated(args); 4 try 5 { 6 var wechat = new WeChatCallback(); 7 wechat.Handle(args); 8 } 9 catch (Exception) 10 { 11 // ignored 12 } 13 }
對於消息分享的Response,參數類型是SendMessageToWX.Resp,ErrCode == 0時表示分享成功,不然分享失敗;
對於登陸受權的Response,參數類型是SendAuth.Resp,其中Code即OAuth受權第一步所需的code,經過這個code,調用微信的Open API換取AccessToken,以後才能獲得用戶的基本資料。微信SDK並不實現Open API,因此須要自行實現。我還寫了一個WeChatSDK.Extensions類庫,提供WeChatSns類,封裝了部分Open API,直接調用封裝好的接口便可完成整個OAuth:
1 public override async void OnSendAuthResponse(SendAuth.Resp response) 2 { 3 base.OnSendAuthResponse(response); 4 if (response.ErrCode == 0) 5 { 6 if (!string.IsNullOrEmpty(response.Code)) 7 { 8 var token = await WeChatSns.GetAccessTokenAsync(response.Code); 9 if (token != null) 10 { 11 var user = await WeChatSns.GetUserInfoAsync(token.AccessToken, token.OpenId); 12 var dialog = new MessageDialog($"name:{user.Nickname}\r\nopenid:{user.OpenId}","受權成功"); 13 await dialog.ShowAsync(); 14 } 15 } 16 } 17 else 18 { 19 var dialog = new MessageDialog("受權失敗"); 20 await dialog.ShowAsync(); 21 } 22 }
WeChatSDK.Extensions類庫源碼已貼在Github上,歡迎補充https://github.com/zhxilin/WeChatSDK/tree/master/UWP/WeChatSDK.Extensions
更多微信Open API請參考官方文檔:https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&lang=zh_CN
說完微信SDK的用法,下面談談它的實現原理。
要理解微信SDK的實現原理,首先要解決一個問題,理解什麼是Protocol Buffers。可能不少人都注意到了微信SDK須要引用Google.ProtocolBuffersLite類庫。
Protocol Buffers 是一種輕便高效的結構化數據存儲格式,能夠用於結構化數據串行化,很適合作數據存儲或 RPC 數據交換格式。它可用於通信協議、數據存儲等領域的語言無關、平臺無關、可擴展的序列化結構數據格式。目前支持C++、Java、Python、C#等語言。
...
是Google 公司內部的混合語言數據標準
使用Protocol Buffers比XML的好處在於更簡單、更快、更小,對於微信這種頻繁數據序列化和反序列化的場景來講很是實用。微信的數據交換在設計之初必須考慮實現消息的跨平臺傳輸,那麼對於平臺無關、可擴展性超高的Protocol Buffers技術來講,就有了很大的用武之地。
對Protocol Buffers有興趣的朋友能夠多查閱一下相關資料
http://www.ibm.com/developerworks/cn/linux/l-cn-gpb/index.html
http://kb.cnblogs.com/page/77621/
http://www.cnblogs.com/wu-jian/archive/2011/02/22/1961104.html
簡單來講,對於服務端,只須要傳輸定義好結構的序列化文件 .proto文件到不一樣平臺的客戶端,經過Google提供的工具,如protoc、ProtoGen等工具便可反序列化爲對應平臺的具體對象。能夠經過一張圖來理解:
.proto文件會轉成二進制序列protobuf進行傳輸,客戶端接收到二進制序列後,按照指定的格式讀取到對應平臺的結構類型中就能夠了。整個解析過程須要 Protocol Buffers框架和Protobuf 編譯器生成的代碼共同完成。好比protoc將.proto文件轉成二進制文件,再經過protogen讀取二進制文件轉成C#代碼。
瞭解了以上知識以後,咱們來看看微信SDK是怎麼作的。
好比分享消息,SDK將每一個消息的結構整理後填入TransactionData中,該類包含ToProto方法和FromProto方法,分別將數據結構序列化成.proto對象和將.proto對象反序列化成數據結構。ToProto方法調用了Goolge.ProtocolBuffersLite類庫提供的GeneratedBuilderLite<TMessage, TBuilder>.Build()方法完成序列化;FromProto方法則調用了BuildParsed()方法,將二進制序列反序列化成數據結構。
1 internal TransactDataP ToProto() 2 { 3 TransactDataP.Builder builder = TransactDataP.CreateBuilder(); 4 // ... 將TransactData中的屬性填入builder後Build 5 return builder.Build(); 6 }
public static SendAuthResp ParseFrom(byte[] data) { return CreateBuilder().MergeFrom(data).BuildParsed(); }
咱們的應用將數據打包放入一個後綴爲.wechat的文件中,經過Launcher啓動這個文件,天然只有微信客戶端能識別這個文件,因此微信被呼叫起來並經過protobuf機制處理這個文件,完成咱們的請求。
至於文件關聯協議,以前講過,在Package.appxmanifest中使用AppId(如wx0123456789abcdef)填寫的聲明,就是爲了保證微信在處理完分享或者完成登陸受權後,生成數據並寫入文件.wx0123456789abcdef中,經過FileTypeAssociation協議,這種後綴的文件,也只能由咱們的應用來打開了,這樣就保證了互調關係的惟一性。OnFileActivated接收到文件以後,把文件內的數據經過protobuf機制反序列出來就能獲得結果了。