JSON - 可能每个人都知道这个轻量的数据格式几乎被用在了所有的现代服务器中。相对于过去流行的一些东西,如可怕的XML,它更轻量,更可读,对开发更友好。JSON是语言独立的数据格式,但解析和格式转化,比如转为Java对象,耗费了我们的时间和内存资源。
几天以前,Facebook宣布,在它的Android app中的数据处理部分获得了巨大的性能提升。那与 几乎在整个app中丢弃JSON格式,而用FlatBuffers代替有关。请参考这篇文章来了解一些关于FlatBuffers的基本知识,以及从JSON转换到它的结果。
尽管结果看上去非常好,但乍一看实现方式不是特别明显。Facebook也没有说太多。那也就是为什么我想在这篇文章中展示我们可以如何使用FlatBuffers。
FlatBuffers
简单地说,FlatBuffers是Google的一个跨平台序列化库,特别为游戏开发创建以及,如Facebook所展示的那样,遵循Android中的流畅的及响应式UI的16ms规则。
但是注意了,在你扔掉一切,将你的所有数据迁移到FlatBuffers之前,请先确认你需要这样做。有时候对性能的影响将是极其微小的,而有时候数据安全性比计算速度上几十毫秒的差异重要得多。
什么使FlatBuffers如此高效?
- 由于平坦的二进制缓冲区,访问序列化后的数据时无需解析,即使是对于层次式的数据。多亏于这一点,我们不需要初始化解析器(这意味着构造复杂的字段映射),以及解析数据,这也耗费时间。
- FlatBuffers数据不需要分配多于缓冲区本身所用大小的内存。我们不需要像在JSON中那样,为解析后的层次式数据分配额外的对象。
要获取真实的数字可以参考关于向FlatBuffers迁移的facebook的文章,或者Google文档。
实现
这篇文章将描述在Android app中使用FlatBuffers的最简单的方式:
- 在app之外的某个地方将JSON数据转换为FlatBUffers格式(比如将二进制文件以文件的形式传送,或直接从API返回)。
- 通过flatc (FlatBuffer编译器) 手动产生数据模型(Java 类)。
- JSON文件有一些限制 (不能使用null字段,Data格式被解析为一个String)。
可能在未来我们将准备更复杂的方案。
FlatBuffers编译器
首先我们要先获取flatc - FlatBuffers编译器。它可以通过位于Google的flatbuffers仓库中的源代码来构建。让我们下载/clone它。整个的构建过程在FlatBuffers构建文档中描述。如果你是Mac用户则你需要做的是:
- 打开下载的代码中的
\{extract directory}\build\XcodeFlatBuffers.xcodeproj
这个文件。 - 通过点击Play按钮或按下⌘ + R键来运行flatc scheme(应该被选为默认选项)。
- flatc可执行文件将出现在工程的根目录。
现在我们可以使用schema编译器(以Java,C#,Python,GO和C++语言)来为给定的模式产生模型类,以及将JSON转换为FlatBuffer二进制文件了。
模式文件
现在我们必须要准备模式文件了,它定义了我们想要 反-/序列化 的数据结构。这个模式将被flatc用于创建Java模型以及从JSON到FlatBuffer二进制文件的转换。
这是我们的JSON文件的一部分:
{
"repos": [
{
"id": 27149168,
"name": "acai",
"full_name": "google/acai",
"owner": {
"login": "google",
"id": 1342004,
...
"type": "Organization",
"site_admin": false
},
"private": false,
"html_url": "https://github.com/google/acai",
"description": "Testing library for JUnit4 and Guice.",
...
"watchers": 21,
"default_branch": "master"
},
...
]
}
repos_json_fragment.json 位于GitHub
可以在这里获取完整的版本。它是通过调用如下的Github API所获数据经过了一点修改的版本:
https://api.github.com/users/google/repos.
编写FlatBuffer模式文件的文档非常好,因而在这里我就不再深入说明了。在我们的例子中模式文件也不是非常复杂。我们所要做的所有事情就是创建3个表:ReposList
,Repo
和User
,并定义root_type
。下面是这个模式文件的重要部分:
table ReposList {
repos : [Repo];
}
table Repo {
id : long;
name : string;
full_name : string;
owner : User;
//...
labels_url : string (deprecated);
releases_url : string (deprecated);
}
table User {
login : string;
id : long;
avatar_url : string;
gravatar_id : string;
//...
site_admin : bool;
}
root_type ReposList;
repos_schema_fragment.fbs 位于GitHub
完整的模式文件在这里。
FlatBuffers数据文件
很好,现在我们所需要做的就是将repos_json.json
转换为FlatBuffers二进制文件,并产生Java模型,其可以以Java友好的方式表示我们的数据(这个操作所需的所有的所有文件可以在我们的repository获取):
$ ./flatc -j -b repos_schema.fbs repos_json.json
如果一切顺利,这将产生如下的文件:
- repos_json.bin (将被重命名为repos_flat.bin)
- Repos/Repo.java
- Repos/ReposList.java
- Repos/User.java
Android app
现在让我们来创建我们的示例app,来看下FlatBuffers格式是如何在实际中使用的。这是它的截屏:
ProgressBar将仅被用于展示不正确的数据处理(在UI线程中)可以如何影响用户界面的流畅度。
我们的app的app/build.gradle
文件看起来如下面这样:
apply plugin: 'com.android.application'
apply plugin: 'com.jakewharton.hugo'
android {
compileSdkVersion 22
buildToolsVersion "23.0.0 rc2"
defaultConfig {
applicationId "frogermcs.io.flatbuffs"
minSdkVersion 15
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:22.2.1'
compile 'com.google.code.gson:gson:2.3.1'
compile 'com.jakewharton:butterknife:7.0.1'
compile 'io.reactivex:rxjava:1.0.10'
compile 'io.reactivex:rxandroid:1.0.0'
}
当然,在我们的例子中没必要使用Rx或ButterKnife,但为什么不让这个app更好一点呢