`
一江春水邀明月
  • 浏览: 79014 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

在Android 开发中使用Protobuf的实践和经验分享

 
阅读更多

 

版权所有, 如需转载请保留链接  http://wangbt5191-hotmail-com.iteye.com/blog/1914408/

 

Android 下使用ProtoBuff的实践和心得。

在最近的 Android 客户端项目中, 我们由于节省流量和减少序列化和反序列化运算开销的考虑, 我们选择了Protobuff 作为中间传输的序列化的工具。

为了规避编译ProtobufSchema的麻烦, 我们使用第三方的开源包 ProtostuffRuntime 来使用Runntime的Protobuf Schema.

1. 依赖引入:

          <dependency>
				<groupId>com.google.protobuf</groupId>
				<artifactId>protobuf-java</artifactId>
				<version>2.4.1</version>
			</dependency>
			<dependency>
				<groupId>com.dyuproject.protostuff </groupId>
				<artifactId>protostuff-core</artifactId>
				<version>1.0.7</version>
			</dependency>

                        <dependency>
				<groupId>com.dyuproject.protostuff </groupId>
				<artifactId>protostuff-runtime</artifactId>
				<version>1.0.7</version>
			</dependency>

2. 使用Protobuf 做对象序列化反序列化  

 
       private Charset charset = Charset.forName(&quot;ISO-8859-1&quot;);

	public  P getObj(String s, Class typeClass){

		Schema schema = RuntimeSchema.getSchema(typeClass);

		P obj =null;
		try {
			obj = targetType.newInstance();
		} catch (InstantiationException e) {
		  //FIXME

		} catch (IllegalAccessException e) {
		  //FIXME

		}
		ProtostuffIOUtil.mergeFrom(Base64.decode(s.getBytes(charset), Base64.DEFAULT), obj, schema);
		return obj;
	}

	public String convert2String(Object o){

	    if(o == null || &quot;&quot;.equals(o)){
	        return null;//TODO: consider to throw exception
	    }
	    Class typeClass=  o.getClass();
		Schema schema = RuntimeSchema.getSchema(typeClass);
		LinkedBuffer buffer = getApplicationBuffer();
		try
		   {
		       byte[] protostuff = ProtostuffIOUtil.toByteArray(o, schema, buffer);

		       return new String(Base64.encode(protostuff, Base64.DEFAULT),charset);
		   }
		   finally
		   {
		       buffer.clear();
		   }


	}


	private LinkedBuffer getApplicationBuffer() {
		return LinkedBuffer.allocate(1024);
	}
}

3. Android Dalvik JVM 实现中的坑 

但是且慢, 这样的代码在Server端代码做Junit Test的时候, 和Android 本地的代码Test的时候做本地的Java 对象的序列化到字符串, 再反序列化回Object 都没有什么问题, 但是联调的时候出问题了, 无论怎么调试Android 客户端使用这个代码逻辑去反序列化都不能成功。开始我们怀疑是Http 协议传输的时候, 字符编码的问题, 并在这个上面打转了大半天的时间。 后来突然发现这个是Android JVM 实现中的一个坑。 也或者说ProtostuffRuntime 的没考虑到的Case。  在解释这个问题之前, 我们先看看ProtostuffRuntime 如何生成运行期的protobuf Schema的。 也就是RuntimeSchema.getSchema(typeClass)这里, 到底发生了什么。 它的基本流程是:

1.  获取当前typeClass 的所有Super Class2. 对SuperClass 调用 getDeclaredFields 方法获取Field 列表, 并做一些过滤( 比如transient 修饰的字段是需要过滤的), 把这些Field 列表中field按顺序以 这样的键值对的形式放到HashMap中


3. 重复第二部, 从最根的SuperClass 一直做到当前的typeClass。

4. 然后根据HashMap进行编译出protobuf 的Schema。

我们发现问题出在第二步, Android 下getDeclaredFields 方法返回的Field 列表顺序和我们在类里面定义的不一样。 它是做过字母排序的。 我们知道Protobuf 的序列化中所需要的Schema 是对类下面的Field顺序强依赖的。而在我们的Server端的调式中, 我们发现我们的Field顺序是和我们在类中定义的是一样的。

StackOverflow 上也有类似的讨论, JDK 1.6 以上这个顺序才保证是和类定义中的顺序是一致的, 而在早期版本中这个顺序是没保证的, 是根据各JDK 的实现自己做的。 也就是说 ProtostuffRuntime  其实只能保证在JDK1.6 以上能正确运行。 更别说Android这样的非正统的JVM 系统了。
问题找到了, 那怎么解决呢?

 

4. 定制RuntimeSchema的生成逻辑

 

   这里要做的事情是需要把Android 的上最终push 到hashMap  中的顺序保证和Java 类中定义的顺序一样就可以了。 好吧, 这个只有借助Annotation了。



 @Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD })
public @interface FieldOrder {
	public int order() default 0;
}




我们定义个这样的Annotation, 然后改写

 

static void fill(Map<String,java.lang.reflect.Field> fieldMap, Class<?> typeClass)
  {
      if(Object.class!=typeClass.getSuperclass())
          fill(fieldMap, typeClass.getSuperclass());
      List<java.lang.reflect.Field> fieldList = new ArrayList<java.lang.reflect.Field>();



      for(java.lang.reflect.Field f : typeClass.getDeclaredFields())
      {
          int mod = f.getModifiers();
          if(!Modifier.isStatic(mod) && !Modifier.isTransient(mod))
        	  fieldList.add(f);

      }

      final String className = typeClass.getName();

      Collections.sort(fieldList, new java.util.Comparator<java.lang.reflect.Field>(){

    	  public int compare(java.lang.reflect.Field lField, java.lang.reflect.Field rField){

    		  if(lField.getAnnotation(FieldOrder.class) == null || rField.getAnnotation(FieldOrder.class)==null)
    			   throw new RuntimeException("Class " + className + " " +  lField.getName() + " or " + rField.getName() + " not set order.");
    		  return lField.getAnnotation(FieldOrder.class).order() - rField.getAnnotation(FieldOrder.class).order();

    	  }
      });

      for(java.lang.reflect.Field f: fieldList){
    	  fieldMap.put(f.getName(), f);
      }

  }

 


上面的实例代码中Collections.sort改写后添加的逻辑, 也就是根据Annotation, 在使用field 列表put 到FieldMap 之前最一次根据Annotation 定义的顺序进行一次排序。


public class SearchObject {

    @FieldOrder(order = 1 ) private int currentPage;

    @FieldOrder(order = 2 ) private int pageSize;

....


5. 反思Protobuf的应用场景

 

我们知道Protobuf 对兼容字段差异的兼容可以做到的是, 如果有一个Bean Class 有10个字段序列化方S方和反序列化方DS 方的字段, 假如有Bean Class 在某一边做了升级, 添加了第11 个field, 如果这第11 个field 在field 列表的末尾, 那么是没问 题的。 如果在中间添加这个字段, 那将会导致在另一边无法做反序列化。

从上面第三节里面, 我们看Protobuf的生成Protobuf Schema的流程我们知道, 假如有个类ClassA extends SuperClassA

如果ClassA 中有FieldA1, FieldA2. SuperClassA 中有FieldsSA1, FieldsSA2。 那么最终编译到Schema 中的顺序会是

FieldsSA1, FieldsSA2, FieldA1, FieldA2.    如果我们对SuperClassA上新增FieldSA3, 那么顺序会是  FieldsSA1, FieldsSA2, FieldsSA2, FieldA1, FieldA2. 如果另外一端因为Class 未升级, 那么编译的顺序还是FieldsSA1, FieldsSA2, FieldA1, FieldA2. 那么将会导致无法正确反序列化。

用一句话总结就是, 如果Super Class中新增字段了, 必须两端程序同时升级。 而如果在子类中新增字段, 并且增加在字段列表中的最后一个, 那么是不需要另外一端跟着升级的。


这里我们可以看到protobuf 的运行高效性所带来的一些问题。 相应的这类问题是不会存在在使用Json 格式转换服务上。

 

 

最后, 相关的更改见附件

分享到:
评论
4 楼 一江春水邀明月 2014-05-21  
read_act 写道
那个依赖引入的xml是往哪写入?   RuntimeSchema  是哪的类?


求源代码

在pom.xml里面, 这个是maven 管理的工程
3 楼 read_act 2014-03-04  
那个依赖引入的xml是往哪写入?   RuntimeSchema  是哪的类?


求源代码
2 楼 aa87963014 2014-02-27  
aa87963014 写道
lz,这种方式貌似不行了。


sorry ,我弄错了.
另外@Tag实现了你说的这个功能.
1 楼 aa87963014 2014-02-27  
lz,这种方式貌似不行了。

相关推荐

    android studio Kotlin中使用 GRPC和protobuf

    本教程将详细介绍如何在Android Studio的Kotlin环境中使用gRPC和protobuf进行通信。 gRPC是一个高性能、开源的RPC框架,它基于HTTP/2协议,支持多种编程语言,包括Java和Kotlin。gRPC允许服务端和客户端之间通过...

    android protobuf 使用demo

    在Android中,protobuf的一个关键优势是它的序列化速度比JSON或XML快很多,同时生成的二进制格式更紧凑,对网络传输友好。这对于移动应用来说尤其重要,因为它可以减少网络带宽消耗,提高用户体验。 在实际项目中,...

    Android中基于protobuf的socket通信的实例

    在Android开发中,protobuf(Protocol Buffers)是一种高效的数据序列化协议,由Google开发,用于结构化数据的序列化和反序列化。它比XML、JSON等格式更小、更快,且更简单。本实例将深入探讨如何在Android应用中...

    protobuf_android_sample,使用protobuf的示例android应用程序.zip

    总结起来,这个开源项目为Android开发者提供了一个实践protobuf的实例,帮助他们理解如何在Android应用中集成protobuf,实现高效的数据序列化和反序列化。通过研究这个项目,你可以学习到如何定义protobuf消息,如何...

    protobuf3.20.1 for android

    为了在Android项目中使用这些库,开发者需要将其添加到项目的依赖中,确保在编译和运行时能正确链接和加载protobuf库。 总之,protobuf3.20.1是Android应用开发中的强大工具,尤其在处理大量数据和跨平台通信时,它...

    在erlang项目中使用protobuf例子

    标题中的“在erlang项目中使用protobuf例子”指的是在Erlang编程环境中使用Protocol Buffers(protobuf)这一数据序列化工具。protobuf是由Google开发的一种高效、跨语言的数据表示和序列化格式,它允许开发者定义...

    protobuf for unity 在unity中使用protobuf工程示例

    protobuf for unity 在unity中使用protobuf工程示例,数据的序列化和反序列化工程示例

    游戏开发中的protobuf自动解码DEMO

    在游戏开发中,protobuf(Protocol Buffers)是一种广泛使用的数据序列化协议,它由Google开发,旨在提供一种高效、简洁的方式来定义数据结构,并且能在多种编程语言之间进行数据交换。protobuf的主要优势在于其紧凑...

    Unity 中使用Protobuf进行序列化和反序列化的Demo

    这个Demo展示了Unity中使用Protobuf的基本流程,但在实际应用中,你可能还需要考虑错误处理、数据验证以及如何在不同的游戏场景中有效地使用序列化数据。Protobuf的强大之处在于其灵活性和性能,使得它成为Unity开发...

    Websocek笔记三 egret+skynet使用protobuf

    在“Websocket笔记三 egret + skynet使用 protobuf”的场景中,Egret游戏客户端可能需要与Skynet服务器进行高效的数据通信。protobuf作为数据交换格式,可以在Egret的TypeScript代码中定义消息结构,然后在Skynet...

    编译android版本protobuf

    脚本中描述了如何编译protobuf,包括了ubuntu和android版本

    0535-极智开发-解读protobuf及caffe中使用protobuf

    0535_极智开发_解读protobuf及caffe中使用protobuf

    C++使用protobuf 作为网络消息协议

    protobuf是一种结构化的数据表示方式,它允许开发者定义数据结构(称为.proto文件),然后自动生成能够在各种编程语言中使用的代码,用于将这些数据结构序列化为二进制格式或反序列化回原结构。这种方式非常适合网络...

    protobuf中文学习文档

    3. **API设计**: 在RESTful API中,protobuf作为消息格式可以提供高效的序列化和反序列化。 **六、protobuf工具链** 1. **protoc编译器**: 主要用于将.proto文件转换为目标语言的源代码。 2. **protobuf库**: 提供...

    在Unity中使用ProtoBuf

    在Unity中使用ProtoBuf的工具,使用protoc将写好的proto 3文件生成csharp使用的代码,在网络传输中用着再好不过了

    使用protobuf和gRPC实现消息订阅系统

    在终端使用以下指令在python环境中安装grpc工具: sudo pip3 install grpcio-tools 1.2 proto文件的编写和处理 Protobuf是一套类似Json或者XML的数据传输格式和规范,用于不同应用或进程之间进行通信时使用。通信时...

    windows protobuf android 编译.doc

    为了能够在 Windows 平台上为 Android 编译 Protobuf,我们需要配置 Android NDK(Native Development Kit)环境。具体步骤如下: 1. **下载并安装 Android NDK**:访问 Android 官方网站下载适合 Windows 系统的 ...

    Windows环境使用google protobuf实现简单的例子

    这对于理解protobuf的工作原理和在实际项目中使用protobuf具有指导意义。通过学习这个例子,开发者可以进一步探索protobuf的高级特性,如服务定义和服务调用,以及如何在不同语言之间进行数据交换。同时,对于熟悉...

Global site tag (gtag.js) - Google Analytics