思路:Retrofit 2.0 将请求方法抽象,并且采用注解参数和请求体配置,其实原本它所做的封装已经非常简单了,但是对于实际项目开发中还是需写大量的请求接口和注解的代码,我们在这里除了对Client的初始化封装,还需要对接口定义进行定制,确切到请求类型上。
- 外部使用Retrofit执行一个请求的流程
1.OkHttpClient作为参数设置到Retrofit上
2.Retrofit创建APIService
3.传入参数使用APIService发送请求得到回掉
我们想要定制接口,别且将参数构建简化,所以重点工作是拆分第2步
下面我们一步一步实现封装
------------------------------分.割.线----------------------------------------------
</br>
一、构建OkHttpClient
创建一个用来初始化和配置OkHttpClient的类TBOkHttpClientFactory
使用内部类创建得方式来构建单例和初始化
public void build() {
if (null == okHttpClient) {
synchronized (TBOkHttpClientFactory.class) {
if (null == okHttpClient) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(this.TIMEOUT_CONNECTION, TimeUnit.SECONDS);
builder.readTimeout(this.TIMEOUT_READ, TimeUnit.SECONDS);
builder.writeTimeout(this.TIMEOUT_WRITE, TimeUnit.SECONDS);
if (null != context) {
ClearableCookieJar cookieJar =
new PersistentCookieJar(new SetCookieCache(),
new SharedPrefsCookiePersistor(context));
builder.cookieJar(cookieJar);
}
LogInterceptor logInterceptor = new LogInterceptor();
logInterceptor.setLevel(LogInterceptor.Level.BODY);
builder.addInterceptor(logInterceptor);
TbLog.setDeBug(isDebug);
okHttpClient = builder.build();
}
}
}
}
}
可以看到上面我们得日志拦截器使用的不是Retrofit为我们提供的拦截器而是LogInteceptor,这里为了控制日志开关我们直接拷贝原来得日志拦截器代码,对里面的日志打印方法进行替换。
/**
*这里得log方法原来使用得是System.out.print();我将它替换为我们自己的TbLog类来打印
*/
DEFAULT = new Logger() {
@Override public void log(String message) {
TbLog.printD(TAG,message);
}
};
提供一个外部静态获取Client的方法
public static final OkHttpClient getOkHttpClient() {
if (null == okHttpClient) {
throw new NullPointerException("uh~. When you initializing TBOkHttpClientFactory,you didn't build okHttpClient");
}else{
return okHttpClient;
}
}}
上面对OKHttpClient的构建就基本结束了
</br>
二、对Retrofit的封装,构建TBRetrofitFactory
私有构造,提供单例保证所有请求的一致性
private TBRetrofitFactory(String baseUrl){
Retrofit.Builder builder = new Retrofit.Builder();
builder.client(TBOkHttpClientFactory.getOkHttpClient());
builder.baseUrl(baseUrl);
builder.addConverterFactory(StringConverterFactory.create());
builder.addCallAdapterFactory(RxJavaCallAdapterFactory.create());
retrofit = builder.build();
Log.d("TBRetrofitFactory","Have created TBRetrofitFactory");
}
上面我们看到,在AddConverterFactory的时候我们添加的StringConverterFactory。为什么不用官方给的GsonConverterFactory呢,你想不是每个后台他都在每次请求返回时刚好给你一个Bean,但是除开获取流以外,会返回String是必然的,如果一定要去做转为Bean的功能,我们可以放到CallBack回调处去做,这样适应性更好(提示:在使用GsonConverterFactory时,如果是Bean的转换错误,也不会进入onResponse,会直接进入onFailure方法,这样会混淆网络错误和转换错误,这也是非常蛋疼的)。
将retrofit包这么严实,当然需要将他的本职工作体现出来了,- - !提供一个方法共外部创建APIService。
public <T> T createService(Class<T> clz){
return retrofit.create(clz);
}
对于Client的封装到此就OK了,下面才是真正的重(gan)点(liang)
三、创建TBRetrofitService
先来定义好基础的APIService 其他的Factory都是围绕这些接口来实现的
就叫TBRetrofitService
/**
RESTFUL 模式
@RequestMapping(/users/{name}/{id})
@param url *
@return
*/
@GET
Call<String> get(@Url String url);
···
/**
普通模式
@RequestMapping(/users/?name=xx&id=xx)
@param url
@param map
@return
*/
@GET
Call<String> get(@Url String url, @QueryMap Map<String, Object> map);
···
@POST@FormUrlEncoded//单纯表单
Call<String> postForm(@Url String url, @FieldMap Map<String, Object> map);
···
@POST
@Headers("Content-Type:application/json")
Call<String> postJson(@Url String url, @Body String body);
···
//RequestBody 可以单独设置 content-type
@POST
Call<String> post(@Url String url, @Body RequestBody body);
···
//携带文件和表单
@POSTCall<String> postFormDataFiles(@Url String url, @Body MultipartBody body);
上面的接口定义,几乎满足所有的常用请求
后面需要对请求参数进行封装,并不会直接调用上面的接口,所以我们还需要定义一个外部接口与上面的方法进行对应。
既然是外部直接调用的接口那就叫TBRequestService
void get(String url, Callback<String> callBack);
void get(String url, String[] values, Callback<String> callBack);
void get(String url, Map<String, Object> map, Callback<String> callBack);
void postJson(String url, JSONObject json, Callback<String> callBack);
void postRequestBody(String url, RequestBody body, Callback<String> callBack);
void postFormData(String url, Map<String, Object> map, Callback<String> callBack);
void postFormDataFiles(String url, Map<String, Object> map, List<File> files, MediaType contentType, Callback<String> callBack);
上面的接口中明确指定了参数类型和CallBack<String>返回,下面我们需要一个Factory来具体实现这些方法,并且作为中转最终提交给TBRetrofitService
</br>
四、定义一个请求中转工厂 TBRequestFactory
这里TBRequestFactory 作为参数的中转站,除了实现外部接口方法,还需要将参数封装好递交给TBRetrofitService,需要在提供他本身实例的同时来初始化TBRetrofitService
private TBTBRequestFactory(TBRetrofitFactory rm) {
tBRetrofitService = rm.createService(TBRetrofitService.class);
}
下面是对各个类型接口的参数封装和提交
//RESTFULE 参数拼接
@Override
public void get(String url, String[] values, Callback<String> callBack) {
if(values == null || values.length== 0){
get(url,callBack);
}else{
String va = "";
for (String value : values) {
va += "/" + value;
}
url = url +va;
get(url,callBack);
}
}
···
@Override
public void get(String url, Map<String, Object> map, Callback<String> callBack) {
tBRetrofitService.get(url, map).enqueue(callBack);
}
···
@Override
public void postJson(String url, JSONObject json, Callback<String> callBack){
tBRetrofitService.postJson(url,json.toString()).enqueue(callBack);
}
···
@Override
public void postRequestBody(String url, RequestBody body, Callback<String> callBack){
tBRetrofitService.post(url,body).enqueue(callBack);
}
···
@Override
public void postFormData(String url, Map<String, Object> map, Callback<String> callBack) {
tBRetrofitService.postForm(url, map).enqueue(callBack);
}
···
@Override
public void postFormDataFiles(String url, @Nullable Map<String,Object> map, @NonNull List<File> files, MediaType contentType, Callback<String> callBack) {
if(null == files ){
throw new NullPointerException("files is null!");
}
if(files.size() == 0){
throw new IllegalArgumentException("files size equal 0,You must include at least one file!");
}
MultipartBody.Builder builder = new MultipartBody.Builder();
for(File file : files){
RequestBody body = RequestBody.create(contentType,file);
builder.addFormDataPart("files",file.getName(),body);
}
if(null != map && !map.isEmpty()){
Iterator<Map.Entry<String,Object>> keys = map.entrySet().iterator();
while (keys.hasNext()){
Map.Entry<String,Object> entry = keys.next();
builder.addFormDataPart(entry.getKey(),String.valueOf(entry.getValue()));
}
}
MultipartBody multipartBody = builder.build();
tBRetrofitService.postFormDataFiles(url,multipartBody).enqueue(callBack);
}
这样已经可以使用了,但是...想让它更优(tou)雅(lan)一点
于是就有下面这个类TBRequest,这里就不解释了只是再一次包装,只为了在传参的时候更顺手而已。
- 部分代码
private TBRequest(){
params = new HashMap<>();
request = TBTBRequestFactory.getInstance();
}
···
public TBRequest put(String key, Object value){
params.put(key,value);
return this;
}
public TBRequest setParams(Map<String,Object> params){
this.params = params;
return this;
}
···
private JSONObject map2JSONObject(){ return new JSONObject(params);}
五、测试
好了下面我们来看一下具体使用是不是满足我们的初衷
void GETByRestful() {
TBRequest.create()
.get(API.GITHUB_RESTFUL, new String[]{"Harkben"}, new TBCallBack() {
@Override
public void onSuccess(int code, String body) {
showResult(code + "--" + body);
}
@Override
public void onFailed(String errorMsg) {
showResult(errorMsg);
}
});
}
···
void GETByNormal() {
TBRequest.create()
.put("user", "HarkBen")
.get(API.GITHUB_NORMAL, new TBCallBack() {
@Override
public void onSuccess(int code, String body) {
showResult(code + "--" + body);
} @Override
public void onFailed(String errorMsg) {
showResult(errorMsg);
}
});
}
创建请求,传入Url 参数,回掉,还是很清晰简单的。
有人会问了,说好的2行代码完成文件上传呢
下面我们来看看实际代码
void uploadFile(){
File file = new File(parentPath+"291733413425432.png");
TBRequest.create()
.postFormDataFile(API.UPLOADFILE, file, new TBCallBack() {
@Override
public void onSuccess(int code, String body) {
showResult(code+"--"+body);
}
@Override
public void onFailed(String errorMsg) {
showResult("onFailed--"+errorMsg);
}
});
}
- 1.将文件作为参数提交
- 2.得到请求结果
搞定!
你要跟我死磕,说创建文件也算一步,那么我只能.......拉你出去 Biu!Biu!Biu!
- PS:这里我们设置参数时都是用Map作为参数封装的(GET-RESTFULE例外),GET、POST { JSON、 纯文本表单 、表单-文件+参数 }
除了调用put(key,value)方法以外还可以使用
TBRequest.create() .setParams(map);
我之前有遇到过这样得问题,前端传文件总是传不上去,后台接受请求后总是转型失败,所以这次专门看了很多后台开发的代码,- -!比较混乱,他们也解释不清楚需要在请求时具体携带怎么样的头部信息。所以这自个儿按最基础的写法来写接口,都测一测。这里要谢谢我的后台小明(真名哦)同学教我搭了一个SpringBoot微框架,非常简单好用。
下面是接收文件的后端接口代码,没有使用注解转换,这样方便测试
@RequestMapping("/upload")
@ResponseBody
public String handleFileUpload(MultipartHttpServletRequest request){
printLn(request.getParameter("name"));
printLn(request.getParameter("arg"));
printLn(request.getParameter("gender"));
List<MultipartFile> files = request.getFiles("files");
System.out.println("files.seize()=="+files.size());
for(int i=0;i<files.size();i++){
MultipartFile multipartFile =files.get(i);
try {
File file = new File("E:/image/" +multipartFile.getOriginalFilename());
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
out.write(multipartFile.getBytes());
out.flush();
out.close();
}catch(FileNotFoundException e) {
e.printStackTrace();
return "上传失败,"+e.getMessage();
}catch (IOException e) {
e.printStackTrace();
return "上传失败,"+e.getMessage();
}
}
return files.size()+"个文件上传成功";
}
}
FINISH
一个Android开发的小司机