在 Xamarin.Form 使用 ProtoBuf, 提升APP的体验档次

XML被JSON代替的时候,是因为JSON的更小的文件体积.
现在移步到手机,json 数据包也愈发显的不可接受了.满眼的都是 json 的属性名,真正有用的属性值却只占整个JSON包的一小部份.如果能不要"属性名称",那可太好了,但是那是不可能的.

老早就听说过 ProtoBuf ,一直没有用过.这两天耗了些时间研究了一下,成功的应用于服务端(WebApi) 和 客户端(Xamarin.Form) 上.
先贴两张图感受一下:

在 Xamarin.Form 使用 ProtoBuf, 提升APP的体验档次_第1张图片

 

在 Xamarin.Form 使用 ProtoBuf, 提升APP的体验档次_第2张图片

能小多少,和具体的类结构及数据完整性有关. 不过单从这份结果对比来看, 效果真的很吸引人! 

如果将它应用于手机端, 可以给你的APP体验抬高不止一个档次!

 

1, 服务端实体类的声明 (WebApi):

NuGet -> ProtoBuf-net, 要安新版本的.
一开始, 我直接安装的 WebApiContrib.Formatting.ProtoBuf, 顺带安装 protobuf-net 2.0.0.448 版的, 结果在有循环引用的实体集上报错:
Possible recursion detected.

为什么会有循环引用? 你用过 EF吗? 搜了一圈, 有说什么 protobuf 只支持 Tree ,不支持图的.
Json.Net 序列化的时候,可以设置 LoopReferences = Ignore, 如果 protobuf 不支持这样的功能, 那真是有辱谷歌的大牙了.
后来看到 AsReferenceDefault 这个东西, 敲了一下, 没有这个属性, 才意思到下载的版本太老了.

 

服务端的数据实体定义:

1     [ProtoContract(AsReferenceDefault = true, ImplicitFields = ImplicitFields.AllFields)]
2     public partial class T_BRANCH
3     {
4         public decimal BRANCH_ID { get; set; }
5     
6         public string BRANCH_CODE { get; set; }
7 ...

ImplicitFields.AllFields 的功效等于在每个属性上加 ProtoMember.

 

2, 注册 Protobuf 格式器

在 Global 里添加:

1 GlobalConfiguration.Configuration.Formatters.Add(new ProtoBufFormatter());

ProtoBufFormatter 的源码:

 1     public class ProtoBufFormatter : MediaTypeFormatter {
 2         private static readonly MediaTypeHeaderValue mediaType = new MediaTypeHeaderValue("application/x-protobuf");
 3         private static Lazy<RuntimeTypeModel> model = new Lazy<RuntimeTypeModel>(CreateTypeModel);
 4 
 5         public static RuntimeTypeModel Model {
 6             get {
 7                 return model.Value;
 8             }
 9         }
10 
11         public ProtoBufFormatter() {
12             SupportedMediaTypes.Add(mediaType);
13         }
14 
15         public static MediaTypeHeaderValue DefaultMediaType {
16             get {
17                 return mediaType;
18             }
19         }
20 
21         public override bool CanReadType(Type type) {
22             return CanReadTypeCore(type);
23         }
24 
25         public override bool CanWriteType(Type type) {
26             return CanReadTypeCore(type);
27         }
28 
29         public override Task<object> ReadFromStreamAsync(Type type, Stream stream, HttpContent content, IFormatterLogger formatterLogger) {
30             var tcs = new TaskCompletionSource<object>();
31 
32             try {
33                 object result = Model.Deserialize(stream, null, type);
34                 tcs.SetResult(result);
35             } catch (Exception ex) {
36                 tcs.SetException(ex);
37             }
38 
39             return tcs.Task;
40         }
41 
42         public override Task WriteToStreamAsync(Type type, object value, Stream stream, HttpContent content, TransportContext transportContext) {
43             var tcs = new TaskCompletionSource<object>();
44 
45             try {
46                 Model.Serialize(stream, value);
47                 tcs.SetResult(null);
48             } catch (Exception ex) {
49                 tcs.SetException(ex);
50             }
51 
52             return tcs.Task;
53         }
54 
55         private static RuntimeTypeModel CreateTypeModel() {
56             var typeModel = TypeModel.Create();
57             typeModel.UseImplicitZeroDefaults = false;
58             return typeModel;
59         }
60 
61         private static bool CanReadTypeCore(Type type) {
62             return true;
63         }
64     }
View Code

 

至此, 服务端完成了, 可以请求一下试试效果. 记得要加请求头: Accept ,值为:application/x-protobuf

 

3, PCL 实体类

一般,我都会将数据实体(POCO)单独分隔成一个类库, 方便各个项目使用, 因为这些POCO类库不涉及任何业务逻辑.

现在,在手机端遇到了一个问题, 用这些类库,将服务器上收到的数据反序列化,无论是 json 还是 protobuf , 都会报错.

跟踪了一下, 大至都是因为 : 加到类上的 Serializable;  加到属性上的 Required/StringLength 等 特性 造成的. 因为这些特性在 PCL 类库里是不受支持的.

将这些东西去掉当然是不可能的, 那怎么办呢 ?

如果是基于DbFirst / ModelFirst 的 EF 类库,那好办, 新增一个对应的PCL类库, 把 原库下面的 TT 模板复制一份到PCL库中, 改一下就可以了.

如果是 CodeFirst 的类库 , 那就有点小复杂:

1, 还是新建一个对应的 PCL 类库.

2, 将原库中的所有类文件都复制一份到PCL类库中.

3, 添加一个 attributes 目录, 将类库中所有用到的,不被PCL支持的特性 重新定义一遍, 比如 StringLengthAttribute:

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading.Tasks;
 6 
 7 namespace System.ComponentModel.DataAnnotations {
 8     public class StringLengthAttribute : Attribute {
 9 
10         public StringLengthAttribute(int max) {
11         }
12 
13     }
14 }

注意, 命名空间一定要和原来的一样.

在 Xamarin.Form 使用 ProtoBuf, 提升APP的体验档次_第3张图片

 

4, 将这些PCL类库拿去代替原库,在手机中使用.

 

4, 手机端的 protobuf 类库

Protobuf-net 是有 PCL 版本的, 只不过在 NuGet 中添加的 Protobuf-net 是无法安装到 PCL 库中的, 需要先把 Protobuf-net 下载来, 然后手工添加引用:

在 Xamarin.Form 使用 ProtoBuf, 提升APP的体验档次_第4张图片

不要添加非 PCL 版的 protobuf-net 到 PCL 类库中, 运行会报错.

 

5, WebApi Client 设定

1,添加上面的 ProtoBufFormatter.cs 到手机端的PCL类库中.

2, 在 ReadAsAsync 方法中使用 ProtoBufFormatter :

 1             var a = await this.GetResult(token);
 2             var reason = "";
 3             HttpStatusCode? status = null;
 4             if (a != null) {
 5                 if (a.IsSuccessStatusCode) {
 6                     if (this.SupportProtoBuf) {
 7                         return await a.Content.ReadAsAsync<T>(new[] { new ProtoBufFormatter() });
 8                     } else
 9                         return await a.Content.ReadAsAsync<T>();
10                 } else {
11                     reason = a.ReasonPhrase;
12                     status = a.StatusCode;
13                 }
14             }

具体请参考: 

https://github.com/gruan01/LbcTest/blob/master/Lbc.WebApi/MethodBase.cs

 

 

OK, 以上是在 Xamarin.Form 中使用 Protobuf 的全部过程.

你可能感兴趣的:(protobuf)