不必再写proto文件——ProtoUtils详解

Protobuf不易用

关于Protobuf协议的介绍可以参看我之前的日志一篇搞定Java序列化 中的protobuf篇
相对于其他数据格式如json、xml, protobuf有着数据量小,解析快的显著优势
但是这意味着开发者需要写proto文件,需要了解proto message的语法,并且目前IDE还不支持proto的智能提示,十分不便
基于此,我开发了一个ProtoUtils工程
(先放上GitHub地址

ProtoUtils的用途

ProtoUtils是一个Java工程,集成了写proto文件、proto转化为java类的功能
开发者不需要关注proto的写法,而是直接参照json的方式写java数据类,拷贝到这个工程里
然后配置好输出参数,运行后可以直接获得对接proto协议的java类,再拷贝回你希望使用的工程里

ProtoUtils的用法

more >>

一篇搞定Java序列化

序列化

可以理解为一种数据对象和二进制串的转化协议,用于数据持久化和数据传输

Serializable

用法

  1. 数据对象实现java.io.Serializable,否则会抛出NotSerializableException
  2. 通过ObjectOutputStream和ObjectInputStream对对象进行序列化和反序列化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Override
public <T> void write(OutputStream output, T bean) throws Exception {
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(output);
oos.writeObject(bean);
} catch (IOException e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(oos);
}
}
@Override
public <T> T read(InputStream input, Class<T> entityClass) throws Exception {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(input);
return (T) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
} finally {
IOUtils.closeQuietly(ois);
}
}

more >>

Retrofit+RxJava实战日志(5)-如何获取缓存

首先OKHttp启用缓存目录

1
2
3
4
5
OkHttpClient mOkHttpClient = new OkHttpClient();
File cacheDirectory = new File(context.getApplicationContext()
.getCacheDir().getAbsolutePath(), "HttpCache"); //指定缓存路径
Cache cache = new Cache(cacheDirectory, 20 * 1024 * 1024); //指定缓存大小
mOkHttpClient.setCache(cache);

Retrofit可以用@POST、@GET注解定义POST方法和GET方法
在参数中用@Header定义请求头部。

参考HTTP协议的内容(传送门:HTTP协议笔记)
POST方法没有缓存;
头部Cache-Control设为max-age=0时则不会使用缓存而请求服务器;
为if-only-cache时只查询缓存而不会请求服务器,max-stale可以配合设置缓存失效时间

以之前写的Retrofit+RxJava实战日志(2)-基本使用为例:

1
2
3
4
5
6
7
8
public abstract class BaseApi {
//设缓存有效期为两个星期
protected static final long CACHE_STALE_SEC = 60 * 60 * 24 * 14;
//查询缓存的Cache-Control设置
protected static final String CACHE_CONTROL_CACHE = "only-if-cached, max-stale=" + CACHE_STALE_SEC;
//查询网络的Cache-Control设置
protected static final String CACHE_CONTROL_NETWORK = "max-age=0";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class UserApi extends BaseApi{
//定义接口
private interface UserService {
//POST方法没有缓存,适用于更新操作的接口
@FormUrlEncoded
@POST("user/user_updateUserName")
Observable<BaseResp> updateUserName(@Field("userName") String userName);
//用@Header设置Cache-Control
@GET("user/user_queryProfile")
Observable<UserProfileResp> queryProfile(@Header("Cache-Control") String cacheControl,
@Query("userId") int userId);
}
protected static final UserService service = getRetrofit().create(UserService.class);
//更新用户名接口
public static Observable<BaseResp> updateUserName(String userName){
return service.updateUserName(userName);
}
//查询用户信息网络接口
public static Observable<UserProfileResp> queryProfile(int userId){
return service.queryProfile(CACHE_CONTROL_NETWORK, userId);
}
//查询用户信息缓存接口
public static Observable<UserProfileResp> queryProfileCache(int userId){
return service.queryProfile(CACHE_CONTROL_CACHE, userId);
}
}

如此在需要查询缓存的时候调用queryProfileCache,在需要网络数据时调用queryProfile即可。

Retrofit+RxJava实战日志(4)-Gson解析空字符串的问题

在我做的项目中,服务器经常会用空字符串 “” 作为返回结果表示空值
但这在Gson当中就会遇到问题,如果这项数据的类型不是字符串,Gson解析就会报错
我们希望程序可以自动将空字符串解析为对应类型的空值,比如整型就解析为0,List型就解析为一个Empty List

这个问题可以说是我用Retrofit+Gson以来最大的一个坑,以至于我在研究时差不多都要把源码看完了

提一件离奇的事是,Gson在用整型解析空字符串时,报的居然是”Inavalid double”的错误
经过研究源码后发现,Gson会优先尝试解析为整型,解析失败并不会报错误,
继续尝试解析为double型,再失败才会报错,所以得到了”Inavalid double”

more >>

Retrofit+RxJava实战日志(3)-网络异常处理

上一篇文章写了Retrofit和RxJava的基本使用,最后的subscriber并没有写具体实现
成功的返回通常对应了各种不同的处理,而异常返回通常有着统一的处理
因此这一篇单独讲讲异常处理

Retrofit本身会抛出HttpException,Gson解析会抛出解析异常,
此外我们还应该处理与服务器约定好的“异常”,即上一篇提到的返回数据中result字段值不会0的情况

这里要先解决一个问题,就是Gson构建的对象,通过注解定义key名,以变量的类型定value的类型,
但如果同样的key在不同情况下属于不同的数据类型,就会出问题。
比如上一篇文章提及,服务器返回格式是

1
2
3
4
{
"result":"结果代号,0表示成功",
"msg":"成功返回时是消息数据列表,失败时是异常消息文本"
}

那么msg究竟应该定义为String,还是一个List呢

more >>

Retrofit+RxJava实战日志(2)-基本使用

首先是抽象的基类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public abstract class BaseApi {
public static final String API_SERVER = "服务器地址"
private static final OkHttpClient mOkHttpClient = new OkHttpClient();
private static Retrofit mRetrofit;
protected static Retrofit getRetrofit() {
if (Retrofit == null) {
Context context = Application.getInstance().getApplicationContext();
//设定30秒超时
mOkHttpClient.setConnectTimeout(30, TimeUnit.SECONDS);
//设置拦截器,以用于自定义Cookies的设置
mOkHttpClient.networkInterceptors()
.add(new CookiesInterceptor(context));
//设置缓存目录
File cacheDirectory = new File(context.getCacheDir()
.getAbsolutePath(), "HttpCache");
Cache cache = new Cache(cacheDirectory, 20 * 1024 * 1024);
mOkHttpClient.setCache(cache);
//构建Retrofit
mRetrofit = new Retrofit.Builder()
//配置服务器路径
.baseUrl(API_SERVER + "/")
//设置日期解析格式,这样可以直接解析Date类型
.setDateFormat("yyyy-MM-dd HH:mm:ss")
//配置转化库,默认是Gson
.addConverterFactory(ResponseConverterFactory.create())
//配置回调库,采用RxJava
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
//设置OKHttpClient为网络客户端
.client(mOkHttpClient)
.build();
}
return mRetrofit;
}
}

然后是Cookies拦截器

more >>

Retrofit+RxJava实战日志(1)-在Android Studio中配置

在build.gradle中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//加入retrolambda需要的plugin声明
apply plugin: 'me.tatarka.retrolambda'
//retrolambda的编译路径依赖
buildscript {
dependencies {
classpath 'me.tatarka:gradle-retrolambda:3.1.0'
}
}
//让IDE使用用Java8语法解析
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
//编译RxJava
compile 'io.reactivex:rxjava:1.0.14'
//编译RxAndroid
compile 'io.reactivex:rxandroid:1.0.1'
//编译Retrofit及其相关库,包括Gson
compile 'com.squareup.retrofit:retrofit:2.0.0-beta2'
compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2'
compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2'
compile 'com.google.code.gson:gson:2.4'

Retrolambda是借用Java8的语法特性,需要配置好Java8
安装好Java8后在File > Project structure > SDK Location中配置Java8路径

RxJava的个人概述

关于RxJava,网上已经有很多优秀的技术文章了,我当初也是看着这些上手的
先放出传送门:
给 Android 开发者的 RxJava 详解:http://gank.io/post/560e15be2dca930e00da1083
深入浅出RxJava:http://blog.csdn.net/lzyzsd/article/category/2767743
那些年我们错过的响应式编程:http://www.devtf.cn/?p=174
RxJava Essentials 中文翻译版:http://rxjava.yuxingxin.com/

然后谈谈我自己在使用RxJava后的感受:

响应式编程是一种异步数据流交互的编程范式,而RxJava就是基于事件操作异步数据流在Java上实现的库
核心的理念是将一切都当做数据流来看待,各种变量,用户输入,数据结构,缓存等等
而Rx库提供了高度抽象的函数来操作流,创建、流入流出、过滤、合并、映射等等各种变换
不仅如此,Rx库还使得异步操作,和错误处理变得非常简洁。

使用了RxJava后明显的好处就是
1解决了回调地狱(就是很多层的回调嵌套)的困扰
2切换线程变得方便
3配合Java8的新特性Retrolambda,代码会进一步简洁
更深层次的优势是,加深代码的抽象,使得我们更专注于业务逻辑而不是具体实现细节
并且事件驱动型的系统可以解耦成多个组件,使代码具有更好的扩展性和容错性

但是也有一些难处
1要用好响应式编程,就得训练好基于流的编程思维,而这与过去有着较大的差异
这会导致重构过去的代码难度较大,以及常常会因为思维转变得不纯粹而写出“半成品”
2由于Rx库的操作符高度抽象,使用是很方便但深入理解的成本会很高
3由于Rx库很抽象,很年轻,使用时会有许多需要留意的坑,加上Retrolambda会带来一些性能上的影响

但既然是优秀的东西,花些学习成本使用也是值得的

HTTP协议笔记

HTTP协议是基于TCP协议的上层应用协议

特点1无连接:每次连接只处理一个请求,处理完即断开连接
特点2无状态:每次连接需要的信息都要重传

RESTful:一种流行的互联网软件设计架构 含义是资源表现层状态转化,包括三部分

  1. 定义直观简短的资源地址URI (应当是名词,动作放到参数里)
  2. 定义传输的资源:Web服务接受与返回的互联网媒体类型,如JSON,XML等
  3. 对资源的操作方法:GET获取资源 POST/PUT更新资源 DELETE删除资源

more >>