这一段时间,除了流行的React Native
之外,还有不少流行的框架如Retrofit
,Rx
等等。
在Android API4.4之后,Google官方使用了square公司推出的okHttp替换了HttpClient的请求方式。后来square公司又推出了基于okHttp的网络请求框架:Retrofit。
Retrofit采用注解的方式进行配置相关信息,如地址,请求头,请求方式等等,另外又添加了Gson解析库的接口,接入流行的框架Rx,因为提供了配置基础设置的接口,因此存在良好的扩展性。
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(url)
// .addConverterFactory(FastJsonConverterFactory.create())
// .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(httpClient.addInterceptor(interceptor).addNetworkInterceptor(netInterceptor).build())
.build();
其中,addConverterFactory
方法是用来配置Gson解析库,addCallAdapterFactory
方法是用来添加Rx的适配器,通过client
可以来配置基础内容,如拦截器(配置统一的请求头,获取指定的相应头),缓存配置等等。
通过retrofit.create(Class clazz)
获取网络接口,clazz
为Interface类型,Interface中采用注解的方式去请求网络,方式如下:
GET请求方式:
@Headers("namev:alue")
// @Headers({"key:value","key:value"})
@GET("URL")
Call getConnection(@Query("param") String param) ;
GET请求方式(用来Encode):
@GET("URL")
Call getConnection(@QueryMap(encode = true) Map<String, String> param);
POST请求方式(JSON请求体):
@POST("URL")
Call getConnection(@Body("param") Object p1) ;
POST请求方式(键值对):
@POST("URL")
Call<ResponseBody> getConnection(@FieldMap Map<String, String> params);
ResponseBody
是未采用ConverterFactory
的
结果,因此需要通过API手动解析;而采用ConverterFactory
之后,则可以将ResponseBody
自动解析为指定的Object,eg:User:
@GET("URL")
Call getConnection(@Query("param") String param) ;
因此我们只需要建立java类:
public class HttpUtil{
publi Retrofit mRetrofit
public HttpUtil(String baseUrl, Interceptor interceptor){
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
mRetrofit = new Retrofit.Builder()
.baseUrl(baseUrl)
// .addConverterFactory(JsonConverterFactory.create())
.client(httpClient.addInterceptor(interceptor).builder())
.build();
}
public T getServer(Class clazz){
return mRetrofit.create(clazz);
}
}
public interface UserServer{
@GET("URL")
Call getConnection(@Query("param") String param);
// @GET("URL")
// Call getConnection(@Query("param") String param);
}
使用步骤:通过配置addConverterFactory
,我们就能直接通过接口调用得到json解析的返回值,否则会返回Call,需要手动处理网络请求,通过
同步:call.execute().body().string();
异步:call.enqueue(new Callback(){...});
获取网络响应:
ResponseBody response = new HttpUtil("http://baidu.com",new Interceptor(){
@Override
public Response intercept(Chain chain) throws IOException {
return chain.proceed(chain.request());
}
}).create(UserServer.class).getConnection("param");
Rx是ReactiveX-响应式编程的简称,是一个函数库或者是开源框架。可以参考ReactiveX了解他的一些基本概念。
在Retrofit的简介中,我们提到有一个addCallAdapterFactory
的基本配置,是用来做Rx的桥接器使用的。通过该设置,我们的Retrofit代码接口就可以华丽的变身为:
public interface UserServer{
@GET("URL")
Observable getConnection(@Query("param") String param);
}
也就是说,我们顺利的建立了可观测对象(Observables),通过Observables提供的Api,可以对它进行类似于工厂的自动化生产一样的链式操作,大量的减轻了我们的代码编写工作和阅读维护工作。尤其在异步线程的处理和对原数据组合变化上,更是为我们提供了便利的处理机制。
关于Rx,更多的使用方式在官网上都有对应的API讲解,在这里就不再赘述了,我们只需要记住几个关键点:
1. Observable:可观测对象
2. Operators: 操作符API的总称
3. Scheduler: 线程
demo地址:RxRetrofitFrameWork
Retrofit的核心还是okhttp,因此很我们对Retrofit的基本设置也其实操作到了okhttp。针对于不同的业务,网络请求大致有几点要求,总结如下:
1. 常用的信息头,如:网络状态、系统类型以及版本、令牌......
2. 缓存类型的设定:如服务器判别式缓存响应,无网络状态响应等等
3. 信息传输方式如Gzip的格式的特殊化处理
4. log日志的打印
如果还有更多的通用要求,欢迎留言。
因此针对以上几点,我们需要配置不同的拦截器去处理这件事情:
通过addInterceptor
添加的Interceptor主要是用于处理请求的,在Response intercept(Chain chain)
方法中,我们能够添加统一信息头;而通过addNetworkInterceptor
添加的Interceptor主要是用于处理由网络请求得到的非缓存结果的响应,也就是说,如果我们得到的是缓存响应,则不会经过由addNetworkInterceptor
添加的Interceptor处理,只会通过addInterceptor
添加的Interceptor处理。另外设置缓存目录和大小,设置响应失败的重试策略,也是根据业务需要去设定。因此,我们将这里的封装做为最底层的支持,提供网络请求器,生成请求句柄:
public class HttpManager {
private Retrofit mRetrofit;
private OkHttpClient mHttpClient;
private NetServer mNetServer;
public HttpManager(String url, Interceptor interceptor, Interceptor netInterceptor){
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.cache(new Cache(new File(NetApplication.getInstance().getApplicationContext().getCacheDir(), "caches"), 1024 * 1024 * 100));
mHttpClient = builder.addInterceptor(interceptor).addNetworkInterceptor(netInterceptor).retryOnConnectionFailure(true).build();
mRetrofit = new Retrofit.Builder()
.baseUrl(url)
.client(mHttpClient)
.build();
mNetServer = mRetrofit.create(NetServer.class);
}
public NetServer getServer(){
return mNetServer;
}
}
接下来是业务层的相关配置:
public class NetProxy {
private HttpManager mManger;
private String BASE_URL = "";
public NetProxy(){
mManger = new HttpManager(BASE_URL, new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request originalRequest = chain.request();
Request.Builder builder = originalRequest.newBuilder();
// HttpUrl originalHttpUrl = original.url();
// String queryString = originalHttpUrl.encodeQuery(); // 获取url中的参数部分
// String path = originalHttpUrl.url().getPath(); // 获取相对地址
// Buffer buffer = new Buffer();
// builder.body().writeTo(buffer);
// String requestContent = buffer.readUtf8(); // 用于post请求获取form表单内容
builder.addHeader("key", "value");
builder.addHeader("Accept-Encoding", "gzip");
Request request = builder.build();
return chain.proceed(request);
}
}, new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Response response = chain.proceed(request);
Response.Builder builder = response.newBuilder();
if (response.header("Content-Encoding", "").contains("gzip")){
BufferedSource bufferedSource = Okio.buffer(new GzipSource(response.body().source()));
String temStr = bufferedSource.readUtf8();
bufferedSource.close();
ResponseBody body = ResponseBody.create(MediaType.parse("application/json"), temStr);
builder.body(body);
}else{
BufferedSource bufferedSource = Okio.buffer(response.body().source());
String temStr =bufferedSource.readUtf8();
bufferedSource.close();
ResponseBody body = ResponseBody.create(MediaType.parse("application/json"), temStr);
builder.body(body);
}
return builder.build();
}
});
}
public NetServer getNetServer(){
return mManger.getServer();
}
}
一个商业级别的App通常是有着大量的接口Api的,因此当接口越多时,越难以维护。因此我们可以利用FieldMap
和QueryMap
进行封装处理,统一成一个请求接口,传递不同的参数值,因此我们的NetServer
如下:
public interface NetServer {
@GET
Call<ResponseBody> getRequest(@Url String url, @QueryMap Map<String, String> map);
@FormUrlEncoded
@POST
Call<ResponseBody> postRequest(@Url String url, @QueryMap Map<String, String> queryMap, @FieldMap Map<String, String> postMap);
}
这里没有文件的上传,欢迎提供代码进行补全。
每个Api都有着不同的请求参数长度,请求参数类型和请求地址,因此我们将这些内容整合为动态的参数对象,组成url,map的形式,方便我们接口类型的统一化管理。
现在我们的网络请求句柄创建好了,底层相应的配置也做好了,现在需要开启我们的请求过程。在这里注意,为什么没有选择RxAdapter和JSONConvertFactory,第一是因为如果使用了JSONConvertFactory,我们的返回类型则不是统一的Call
,而是指定的Bean;第二是因为很有可能,我们需要在IO线程处理一些信息,如果使用了RxAdapter则我们通过接口就直接创建了一个完整的Observable,不利于代码的添加。
创建监听器和协议:
public interface NetResultCallback {
void onStart();
void onSuccess(E data);
void onFailed(int code, String msg);
void cancel();
}
public class BaseServiceResult {
public int ret;
public String msg;
public T data;
@Override
public String toString() {
return "BaseServiceResult{" +
"ret=" + ret +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}
public interface ErrorCode {
int CODE_OK = 0;
int CODE_TIME_OUT = -5000;
int CODE_NET_ERROR = -5001;
int CODE_RESPONSE_EMPTY = -5002;
}
补全网络请求过程:
public void excute(final String mUrl, final Map mQueryMap, final Map mPostMap, final MethodType mMethodType, final Class mClazz){
Observable.create(new Observable.OnSubscribe>() {
@Override
public void call(Subscriber super BaseServiceResult> subscriber) {
Call responseBody = new NetProxy(). getNetServer().getRequest(mUrl, mQueryMap);
// Call responseBody = new NetProxy().getNetServer().postRequest(mUrl, mPostMap, mQueryMap);
try {
String strJSON = responseBody.execute().body().string();
BaseServiceResult baseServiceResult = JSON.>parseObject(strJSON, BaseServiceResult.class);
if (baseServiceResult.data != null) {
if (baseServiceResult.data instanceof JSONObject) {
baseServiceResult.data = JSON.parseObject(((JSONObject) baseServiceResult.data).toJSONString(), mClazz);
} else if (baseServiceResult.data instanceof JSONArray) {
baseServiceResult.data = (T) JSON.parseArray(((JSONArray) baseServiceResult.data).toJSONString(), mClazz);
}
}
subscriber.onNext(baseServiceResult);
subscriber.onCompleted();
} catch (Exception e) {
e.printStackTrace();
subscriber.onError(e);
}
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(getSubscrobe());
}
private Subscriber> getSubscrobe(final NetResultCallback mCallback){
Subscriber> mSubscriber = new Subscriber>() {
@Override
public void onStart() {
super.onStart();
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
if (mCallback != null) {
mCallback.onFailed(ErrorCode.CODE_TIME_OUT, NetApplication.getInstance().getString(R.string.connect_time_out));
Log.e(TAG, "onError: " + e.getMessage());
}
}
@Override
public void onNext(BaseServiceResult tBaseServiceResult) {
if (mCallback != null) {
if (tBaseServiceResult == null){
mCallback.onFailed(ErrorCode.CODE_RESPONSE_EMPTY, NetApplication.getInstance().getString(R.string.connect_time_out));
return;
}
Log.i(TAG, "onNext: " + tBaseServiceResult.toString());
if (tBaseServiceResult.ret == ErrorCode.CODE_OK) {
mCallback.onSuccess(tBaseServiceResult.data);
}else{
mCallback.onFailed(tBaseServiceResult.ret, tBaseServiceResult.msg);
}
}
}
};
return mSubscriber;
现在基本流程已经跑通,网络部分的基础已经搭建好,然而这样的执行代码还是有着大量重复的感觉,因此我们需要对执行代码进行二次封装,由上面的方法来看,我们发现就相当于是一次下厨,准备好了原材料如:mUrl,mCallback,mClazz等等,就能够直接执行,因此我们将这些准备和执行视为一个整体,具备了基本属性和基本行为动作,顺便参照我们Rx的链式模式,我们封装如下:
public class NetProcessor {
private NetResultCallback mCallback;
private Map mQueryMap;
private Map mPostMap;
private String mUrl;
private Class mClazz;
private @MethodType int mMethodType;
private boolean mNeedRetry = true;
private Subscriber> mSubscriber;
private NetServer mServer;
private static final String TAG = "NetProcessor";
public static NetProcessor get(){
NetProcessor netProcessor = new NetProcessor();
return netProcessor;
}
public static NetProcessor post(){
NetProcessor netProcessor = new NetProcessor();
netProcessor.mPostMap = new HashMap();
return netProcessor;
}
private NetProcessor(){
mQueryMap = new HashMap<>();
mServer = Controller.getInstance().getmNetProxy().getNetServer();
}
public NetProcessor putParam(String key, String value) {
mQueryMap.put(key, value);
return this;
}
public NetProcessor postParam(String key, String value){
if (mPostMap == null){
synchronized (this){
mPostMap = new HashMap();
}
}
mPostMap.put(key, value);
return this;
}
public NetProcessor onQueryMap(Map queryMap) {
this.mQueryMap.putAll(queryMap);
return this;
}
public NetProcessor onPostMap(Map postMap) {
if (mPostMap == null){
mPostMap.putAll(postMap);
}
return this;
}
public NetProcessor onUrl(String url) {
this.mUrl = url;
return this;
}
public NetProcessor onCallback(NetResultCallback callback) {
this.mCallback = callback;
return this;
}
public NetProcessor onClazz(Class clazz){
this.mClazz = clazz;
return this;
}
public NetProcessor onRetry(boolean needRetry){
this.mNeedRetry = needRetry;
return this;
}
public NetProcessor excute() {
mCallback.onStart();
Observable.create(new Observable.OnSubscribe>() {
@Override
public void call(Subscriber super BaseServiceResult> subscriber) {
Call responseBody = null;
switch (mMethodType){
case MethodType.METHOD_GET:
responseBody = mServer.getRequest(mUrl, mQueryMap);
break;
case MethodType.METHOD_POST:
responseBody = mServer.postRequest(mUrl, mPostMap, mQueryMap);
break;
}
try {
String strJSON = responseBody.execute().body().string();
BaseServiceResult baseServiceResult = JSON.>parseObject(strJSON, BaseServiceResult.class);
if (baseServiceResult.data != null) {
if (baseServiceResult.data instanceof JSONObject) {
baseServiceResult.data = JSON.parseObject(((JSONObject) baseServiceResult.data).toJSONString(), mClazz);
} else if (baseServiceResult.data instanceof JSONArray) {
baseServiceResult.data = (T) JSON.parseArray(((JSONArray) baseServiceResult.data).toJSONString(), mClazz);
}
}
subscriber.onNext(baseServiceResult);
subscriber.onCompleted();
} catch (Exception e) {
e.printStackTrace();
subscriber.onError(e);
}
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.retryWhen(new Func1, Observable>>() {
@Override
public Observable> call(Observable extends Throwable> observable) {
return observable.flatMap(new Func1>() {
@Override public Observable> call(Throwable error) {
if (mNeedRetry) {
mNeedRetry = false;
// For IOExceptions, we retry
if (error instanceof IOException) {
return Observable.just(null);
}
}
// For anything else, don't retry
return Observable.error(error);
}
});
}
})
.subscribe(getSubscrobe());
return this;
}
public void cancel(){
if (mSubscriber != null && !mSubscriber.isUnsubscribed()){
mSubscriber.unsubscribe();
mCallback.cancel();
}
}
private Subscriber> getSubscrobe(){
mSubscriber = new Subscriber>() {
@Override
public void onStart() {
super.onStart();
}
@Override
public void onCompleted() {
}
@Override
public void onError(Throwable e) {
if (mCallback != null) {
mCallback.onFailed(ErrorCode.CODE_TIME_OUT, NetApplication.getInstance().getString(R.string.connect_time_out));
Log.e(TAG, "onError: " + e.getMessage());
}
}
@Override
public void onNext(BaseServiceResult tBaseServiceResult) {
if (mCallback != null) {
if (tBaseServiceResult == null){
mCallback.onFailed(ErrorCode.CODE_RESPONSE_EMPTY, NetApplication.getInstance().getString(R.string.connect_time_out));
return;
}
Log.i(TAG, "onNext: " + tBaseServiceResult.toString());
if (tBaseServiceResult.ret == ErrorCode.CODE_OK) {
mCallback.onSuccess(tBaseServiceResult.data);
}else{
mCallback.onFailed(tBaseServiceResult.ret, tBaseServiceResult.msg);
}
}
}
};
return mSubscriber;
}
@IntDef({MethodType.METHOD_GET, MethodType.METHOD_POST})
public @interface MethodType {
// GET请求
int METHOD_GET = 0;
// POST请求
int METHOD_POST = 1;
}
private String getQuertString(){
if (mQueryMap == null){
return "";
}
List list = new ArrayList<>();
QueryString query = null;
Set set = mQueryMap.keySet();
StringBuilder queryBuilder = new StringBuilder();
for (String key : set) {
query = new QueryString(key, mQueryMap.get(key));
list.add(query);
}
Collections.sort(list);
int size = list.size();
try {
for (int i = 0; i < size; i++) {
queryBuilder.append(list.get(i).toStringAddAnd( i == 0 ? false : true));
}
} catch (Exception e) {
e.printStackTrace();
}
return queryBuilder.toString();
}
private String getPostString(){
if (mPostMap == null){
return "";
}
Set set = mPostMap.keySet();
StringBuilder postBuilder = new StringBuilder();
for (String key : set) {
try {
postBuilder.append("&").append(key).append("=").append(URLEncoder.encode(mPostMap.get(key), "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
postBuilder.deleteCharAt(0);
return postBuilder.toString();
}
}
应用如下:
public class UserBeanModel {
public static NetProcessor getUser(NetResultCallback callback){
Map params = new HashMap<>();
params.put("name", "username");
params.put("psw", "userpsw");
return NetProcessor.get()
.onCallback(callback)
.onRetry(true)
.onClazz(UserBean.class)
.onUrl("/get/user")
.onQueryMap(params)
.excute();
}
}
最后通过 UserBeanModel.getUser(new NetResultCallback())
执行就能得到相应的结果。
关于RxJava2和XML的结合,详见Retrofit接入RxJava2的使用以及XML在Retrofit中的使用。