最近的几个个人小项目中多次用到了 OkHttp3 这个库,但是又有项目会用到 HttpURLConnection 来构造 Http 请求,而且网络请求代码的逻辑大体相同,多次去写相似的代码烦不胜烦。所以,为了小型项目的使用,特意总结了适用个人小项目的 OkHttp3 封装类写法。
既然要使用封装类,必须保证接口中包含 Http 请求的常用功能,这样才能方便更换不同的网络连接库实现。具体到本例中,接口应该对应访问网络时使用到的 Http 协议的相关内容:
客户端:发起请求,获取回应 网络请求:可以包裹协议要求的请求头、请求体等 服务器回应:服务器返回的回应内容,包括状态码、回应体等 那么,对应到接口分别就是:
IHttpClient IRequest IResponse 而不同的网络库有不同的实现方式,比如要使用 OkHttp3 的实现就可以有:
OkHttpClientImpl OkHttpRequestImpl OkHttpResponseImpl 然后,在各个实现类中调用 OkHttp3 的相应的功能,UML 图如下所示:
最后,在做了多个简单的练手项目后,发现小项目里面请求和回应的结构大体相同,而客户端则各不相同。为了省事,可以使用通用的请求和回应类(BaseRequest 和 BaseResponse),在不同客户端的实现中为它们对应分配不同的实现。则 OkHttp3 的简单封装则可以是如下结构:
客户端接口很简单,只需要实现 GET 和 POST 方法即可。另外,还要注意运行设置是否使用缓存。
1
2
3
4
5
public interface IHttpClient {
IResponse get ( @NonNull IRequest request , boolean forceCache );
IResponse post ( @NonNull IRequest request , boolean forceCache );
}
具体的实现类中,则要注意必须包含一个 OkHttpClient 实例以方便操作,同时客户端要保证网络连接达成,那就要有实际的执行代码。而执行代码在 get 和 post 方法中都会用到,不妨提取出公共的方法。实现类的大致结构为:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class OkHttpClientImpl implements IHttpClient {
OkHttpClient mOkHttpClient = new OkHttpClient . Builder ()
. build ();
@Override
public IResponse get ( @NonNull IRequest request , boolean forceCache ) {
// ...
}
@Override
public IResponse post ( @NonNull IRequest request , boolean forceCache ) {
// ...
}
/**
* 请求执行过程
*/
private IResponse execute ( @NonNull Request request ) {
// ...
}
}
这里不会涉及太多缓存的高级用法,所以如此设置即可。
另外,因为 OkHttp 对与缓存功能有很好的封装(使用 CacheControl 类进行设置即可),节省了大量再封装的工作,所以设计代码框架的时候可以不用太着急考虑它。
请求分为 GET 和 POST 两种,可以用常量字符串来标明,还应能够设置请求方法。另外,就是要能够获取 URL 地址,以及设置和获取请求头和请求体。
为了通用和方便,不妨把请求头和请求体都设置为 Map<String, String> 类型。
同时,注意设置方法时,参数字符串必须有所限定,防止恶意使用代码或者误操作。Android 中不太适合使用枚举类型,可以使用注解来实现这一功能。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public interface IRequest {
public static final String POST = "POST" ;
public static final String GET = "GET" ;
void setMethod ( @Method String method );
void setHeaderField ( @NonNull String key , @NonNull String value );
void setBodyField ( @NonNull String key , @NonNull String value );
String getUrl ();
Map < String , String > getHeader ();
Object getBody ();
@Retention ( RetentionPolicy . SOURCE )
@StringDef ({ GET , POST })
@interface Method {
}
}
注意这里的 getBody() 方法返回了 Object 内容,是为了在实现中能够直接返回字符串,节省操作。
回应的结构也很简单,只需保证能够获取结果状态码和内容即可。
1
2
3
4
5
6
7
8
9
10
11
public interface IResponse {
//自定义状态码
public static final int STATE_UNKNOWN_ERROR = 100001 ;
// 状态码
int getCode ();
// 数据体
String getData ();
}
这里贴出在一个练手项目中的 OkHttp3 封装的代码。
要注意到,为了测试等操作方便,会在另外编写一个设置类,用来存放静态的设置参数。
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
36
37
38
39
public class API {
public static final String TEST_GET = "/get?uid=${uid}" ;
public static final String TEST_POST = "/post" ;
/**
* 配置域名信息
*/
public static class Config {
private static final String TEST_DOMAIN = "http://httpbin.org" ;
private static final String RElEASE_DOMAIN = "http://httpbin.org" ;
private static String domain = TEST_DOMAIN ;
private static final CACHE_SIZE = 10 * 1024 * 1024 ;
priavte static final CACHE_DIR = "/cache" ; // 实际项目中不要这么设置
private static final CACHE_FILE_NAME = "httpCache" ;
public static void setDebug ( boolean debug ) {
domain = debug
? TEST_DOMAIN
: RElEASE_DOMAIN ;
}
public static String getDomain () {
return domain ;
}
public static File getCacheFile () {
return new File ( CACHE_DIR , CACHE_FILE_NAME );
}
public static int getCacheSize () {
return CACHE_SIZE ;
}
}
}
客户端实现:
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public class OkHttpClienImpl implements IHttpClient {
private static final OkHttpClient BASIC_CLIENT ;
static {
final Cache cache = new Cache ( Api . Config . getCacheFile (), Api . Config . getCacheSize ());
CLIENT = new OkHttpClient . Builder (). cache ( cache ). build ();
}
@Override
public IResponse get ( @NonNull IRequest request , boolean forceCache ) {
// 指定请求方式
request . setMethod ( IRequest . GET );
// 解析头部
Map < String , String > header = request . getHeader ();
// OkHttp 的 Request.Builder
Request . Builder builder = new Request . Builder ();
for ( String key : header . keySet ()) {
// 组装成 OkHttp 的 Header
builder . header ( key , header . get ( key ));
}
// 获取 url
String url = request . getUrl ();
builder
. url ( url )
. get ();
Request oKRequest = builder . build ();
// 执行 oKRequest
return execute ( oKRequest );
}
@Override
public IResponse post ( @NonNull IRequest request , boolean forceCache ) {
// 指定请求方式
request . setMethod ( IRequest . POST );
MediaType mediaType = MediaType . parse ( "application/json; charset=utf-8" );
RequestBody body = RequestBody . create ( mediaType ,
request
. getBody ()
. toString ());
Map < String , String > header = request . getHeader ();
Request . Builder builder = new Request . Builder ();
for ( String key : header . keySet ()) {
builder . header ( key , header . get ( key ));
}
builder
. url ( request . getUrl ())
. post ( body );
Request oKRequest = builder . build ();
return execute ( oKRequest );
}
/**
* 请求执行过程
*/
private IResponse execute ( @NonNull Request request ) {
BaseResponse commonResponse = new BaseResponse ();
try {
Response response = mOkHttpClient
. newCall ( request )
. execute ();
// 设置状态码
commonResponse . setCode ( response . code ());
String body = response
. body ()
. string ();
// 设置响应数据
commonResponse . setData ( body );
/*Log.d("OkHttpClientImpl" ,String.format("Received response body: %s ",
body));*/
} catch ( IOException e ) {
e . printStackTrace ();
commonResponse . setCode ( BaseResponse . STATE_UNKNOWN_ERROR );
commonResponse . setData ( e . getMessage ());
}
return commonResponse ;
}
}
请求实现:
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class BaseRequest implements IRequest {
private String method = POST ;
private String url ;
private Map < String , String > header ;
private Map < String , Object > body ;
public BaseRequest ( @NonNull String url ) {
this . url = url ;
header = new HashMap <>();
body = new HashMap <>();
header . put ( "Application-Id" , "myTaxiID" );
header . put ( "API-Key" , "myTaxiKey" );
}
@Override
public void setMethod ( @Method String method ) {
this . method = method ;
}
public void setHeader ( @NonNull String key , @NonNull String value ) {
header . put ( key , value );
}
public void setBody ( @NonNull String key , @NonNull String value ) {
body . put ( key , value );
}
@Override
public String getUrl () {
if ( GET . equals ( method )) {
// 组装 Get 请求参数
for ( String key : body . keySet ()) {
url = url . replace ( "${" + key + "}" ,
body
. get ( key )
. toString ());
}
}
return url ;
}
@Override
public Map < String , String > getHeader () {
return header ;
}
@Override
public String getBody () {
if ( body != null ) {
// 组装 POST 方法请求参数
return new Gson (). toJson ( this . body , HashMap . class );
} else {
return "{}" ;
}
}
}
回应实现:
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
class BaseResponse implements IResponse {
// 状态码
private int code ;
// 响应数据
private String data ;
@Override
public String getData () {
return data ;
}
public int getCode () {
return code ;
}
void setCode ( int code ) {
this . code = code ;
}
public void setData ( @NonNull String data ) {
this . data = data ;
}
}
现在,Android 项目中越来越频繁的使用开源库,有些是为了弥补原生代码的不足,有些是为了实现更优美的 UI 效果。但是,对应同一个目标常常有不同的开源库实现,各有优点,甚至会使用不同的底层实现。而实际构建项目的时候常常会在不同库中进行挑选或迁移,这样就要求使用者必须能够熟练的给相应的库进行简单封装。
封装的目的是为了解耦,但是要注意的是封装必定会引入新的逻辑层,以及新的包装代码,必然会增加理解的复杂度,降低运行效率。这就要求,封装的时候抽象出的接口不仅要用泛用性,还要和逻辑上的概念对应,方便理解;同时,封装的代码要尽量简洁。