Retrofit & OkHttp系列(一)

【Android】网络编程之OKHTTP与Retrofit框架 fastmock可以让你在没有后端程序的情况下能真实地在线模拟ajax请求,你可以用fatmock实现项目初期纯前端的效果演示,也可以用fastmock实现开发中的数据模拟从而实现前后端分离。同样的在okhttp中我们需要切换线程到主线程来设置数据到控件上,说明okhttp框架只帮我们解决了构建子线程的问题,线程切换还是需要自己实现。Retrofit框架看上去已经很完美了,但是因为它帮我们完成了数据解析,我们无法捕获数据解析是否成功,所以当数据解析失败时通过。通过接口和注解来描述我们的网络请求,然后通过。 阅读详情

乱七八糟的个人感慨(个人浅见,勿喷!!)

不特别愿意使用Retrofit框架,每加一个接口都需要到接口里定义方法,还要添加相关请求实现,每次请求都要做这么几步,私下里也曾想过封装成泛型,实践后发现有点问题,注解Method返回的Call这里会出问题,不能取到明确类型。

这个框架你不能说个人用着感觉不方便就放弃,不去了解,但从团队考虑队友都会用,而你不会这就是你的不对了,所以一定要会熟练使用Retrofit+Okhttp框架,至于RX系列目前还没到那种必须会的程度,自行考量吧。

目前呢很多人说你怎么还在用xutils 、volley这些过时的框架啊?我只想说:能被很多人使用过的技术,说明他也有闪光点,这点谁都不能抹杀,很多新兴的框架出现,比现有的框架还要完美,这并不是说现有框架就是过时了,一个项目用什么框架取决于项目大小、团队配合、项目维护成本等因素。最近有些人再吹RX+Dagger2+Retrofit2+Okhttp3+MVP,听上去是那么回事,让人感觉你很nb,但是你在想想,如果我的项目只有三个接口5个简单的界面,再采用这一套框架,代码要多些多少呢?开发周期又会增加多少呢?如果你突然辞职了,公司招人不会RX 、Dagger2这些框架咋办呢?这些都是技术成本,都是你为公司留下的。作为程序员仅仅考虑代码写得漂亮,有没有考虑过产出的效率问题呢?有没有为公司考虑过呢?

最后在说一下框架过时这个问题,每个流行过的框架,他的代码设计思想、模式等技术核心都值得我们去学习,如果你想说他过时了,就看不起它了,那么我想请问你两三个问题:你很牛逼么?你是大牛你能写出这样的框架么?请把你写好的托管在github的框架地址给我一个,可以么?


Retrofit

配置开发环境
Retrofit requires at minimum Java 7 or Android 2.3.

要求开发环境JDK1.7+ android SDK 2.3+,目前Retrofit出了多个版本,项目导入建议使用release版本,最新版本自行跟踪项目托管地址

//github地址: https://github.com/square/retrofit

compile 'com.squareup.retrofit2:retrofit:2.1.0'


基本的请求流程

导入项目后,通过ServiceGenerator方法将创建HTTP客户端包括头定义的授权,调用自己定义的接口注解的方法获取返回值。关于这里要注意区分1.9版本与2.0+是有区别的,个人使用2.0版本这里就不提1.x版本相关知识,如想了解更多请参考这里(下面提到的Retrofit都是只Retrofit2.0别咬文嚼字啊)

// https://futurestud.io/tutorials/android-basic-authentication-with-retrofit

在2.0以后Retrofit不再依赖Gson,使用需要自己添加依赖(当然你也可以使用Jackson)

compile 'com.squareup.retrofit2:converter-gson:2.1.0'

请求流程概要分为以下几步(这里以发送验证码为例)

  • 定义响应实体类ResponseBean.java

  • 定义接口SendAuthCodeService

/**
 * Created by idea on 2016/11/18.
 */
public interface SendAuthCodeService {

    @POST
    Call<ResponseBean> sendAuthCode();
}
  • 定义Service

public class ServiceGenerator {

    public static final String API_BASE_URL = "";

    private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();

    private static Retrofit.Builder builder =
            new Retrofit.Builder()
                    .baseUrl(API_BASE_URL).....略..

    public static <S> S createService(Class<S> serviceClass, final String phone) {

          //................略过拦截器处理..........................

        OkHttpClient client = httpClient.build();
        Retrofit retrofit = builder.client(client).build();
        return retrofit.create(serviceClass);
    }
}
  • 调用(Call的方法有异步和同步,使用时各取所需)

    public void sendAuthCode(String phone){
        SendAuthCodeService sendAuthCodeService =
                ServiceGenerator.createService(SendAuthCodeService.class, "15828331414");
        Call<ResponseBean > responseBean = sendAuthCodeService.sendAuthCode();
        responseBean.enqueue(new Callback<ResponseBean>(){
            @Override
            public void onResponse(Call<ResponseBean> call, Response<ResponseBean> response) {

            }

            @Override
            public void onFailure(Call<ResponseBean> call, Throwable t) {

            }
        });
    }

请求携带token和令牌验证

每个app账户登录基本都会有这么一个token值,作为访问凭证,每次访问都要携带,Okhttp有拦截器这么个功能可以帮我们实现,至于拦截器这块知识稍后再提。下面是相关实现代码块

 public static <S> S createService(Class<S> serviceClass, final String authToken) {
        if (authToken != null) {
            httpClient.addInterceptor(new Interceptor() {
                @Override
                public Response intercept(Interceptor.Chain chain) throws IOException {
                    Request original = chain.request();

                    // Request customization: add request headers
                    Request.Builder requestBuilder = original.newBuilder()
                            .header("Authorization", authToken)
                            .method(original.method(), original.body());

                    Request request = requestBuilder.build();
                    return chain.proceed(request);
                }
            });
        }

经常我们登录缓存了token,而每次需要同步用户信息就可以用到这个玩意儿了,代码调用示例如下(OAuth 这块略过,知识点都差不多。)

UserService userService =  
    ServiceGenerator.create(UserService.class, "auth-token");
Call<User> call = userService.getUserInformation();  
User user = call.execute().body();  

一个key对应1个或多个value的GET请求查询

服务器的api无非就是增删改查,客户端传递参数执行这些操作。传递这些参数在Retrofit框架下该怎么使用呢?且看下面代码块

public interface UserService {

    @GET("/user")
    Call<User> getUserInformation(@Query("userId") long userId);
}

只需要注解Query的字段参数即可,这里补充说明一点:@GET @POST这些作用在方法之上的注解可以含参,用于拼接完整api接口,当@GET或@POST注解的url为全路径时,会直接使用注解的url。(从某个层面来讲,对外只有一个接口,具体访问哪个接口,只需要把这里GET/POST的Path作为参数通过post提交可能会更好一点)

baseUrl = "http://www.baidu.com"

@GET("/user")

requestUrl = "http://www.baidu.com/user"

// 补充:@Url作为参数传递,@Url注解的路径如果为全路径,则直接使用它,否则通过BaseUrl+@Url拼接

如遇到如下需求: hr林妹妹要统计公司员工都是211、985工程院校毕业有多少人?这个查询条件多个值怎么办呢?Retrofit对这种需求也是信手拈来

public interface UserService {  

    @GET("/user")
    Call<Integer> getUserCount(@Query("school") List<Integer> schools);
}

schools: [211,985]

拼接后的Url: http://www.baidu.com/user?school=211&school=985


同步异步请求
同步请求

上面有提到过同步和异步请求,这里简单来了解相关知识。下面是一个同步请求接口定义实例代码

public interface UserService {  

    @GET("/user")
    Call<User> getUserInformation(@Query String token);
}

同步请求的数据处理如下

Call<User> call = userService.getUserInformation("Uid1232342");  
user = call.execute().body();

Warning: 同步请求可能是导致APP在4.0及以上版本崩溃,你可能会得到一个Exception 的异常错误,为了不阻塞UI,一般通过handler message来解决,这种使用方法很不爽的

异步请求

异步请求通过接口回调方式返回结果,也是我们最常用的方式,与同步请求从方法来来比较:excute()、enqueue(Callback)

 Call<ResponseBean > responseBean = sendAuthCodeService.sendAuthCode();
 responseBean.enqueue(new Callback<ResponseBean>(){
      @Override
      public void onResponse(Call<ResponseBean> call, Response<ResponseBean> response) {

      }

      @Override
      public void onFailure(Call<ResponseBean> call, Throwable t) {

     }
  });

如果你需要最原始的响应内容而不是经过映射后的数据,可以通过onResponse()response参数获取(一般情况下用不到)

Response raw = response.raw();

请求参数使用@Body注解发送Object

请求的Body部分不再像以前那样直接key value 这样直接设置键值对,而是把请求参数封装成对象,使用注解 @Body

public interface TaskService {  
    @POST("/tasks")
    Call<Task> createTask(@Body Task task);
}

public class Task {  
    private long id;
    private String text;

    public Task(long id, String text) {
        this.id = id;
        this.text = text;
    }
}

Task task = new Task(1, "my task title");  
Call<Task> call = taskService.createTask(task);  
call.enqueue(new Callback<Task>() {});  

上面这种方式在某种情况下,你会发现请求不成功,一直提示你请求参数格式有问题,这时候可能是服务器端的api可能仅仅支持form表单提交,这里需要@FieldMap注解标签,把对象转换一下


响应参数映射 转换器

请求响应内容JSON数据得映射转换,Retrofit提供了几种,最常用的就是GSON了(Jackson效率最高)

这块我使用的就是GsonConverterFactory,这里需要注意一下,如果你compile的版本是2.0及以下版本是没有这个类的,我用的2.1.0.当然你也可以自定义Converter转换器.

   retrofit = new Retrofit.Builder()
                    .baseUrl(Constact.BASE_URL)
                    .callFactory(OkhttpHelper.getOkhttpClient())
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();

添加请求头部

为请求添加请求头,主要方案有一下三种

  • 静态请求头

  • 动态请求头

  • 请求头拦截器

添加头部请求头静态的方式也分为两种:单一请求头和多个请求头
public interface UserService {  
    @Headers("Cache-Control: max-age=640000")
    @GET("/tasks")
    Call<List<Task>> getTasks();
}

public interface UserService {  
    @Headers({
        "Accept: application/vnd.yourapi.v1.full+json",
        "User-Agent: Your-App-Name"
    })
    @GET("/tasks/{task_id}")
    Call<Task> getTask(@Path("task_id") long taskId);
}
添加拦截器,通过拦截请求添加请求头
OkHttpClient.Builder httpClient = new OkHttpClient.Builder();  
httpClient.addInterceptor(new Interceptor() {  
    @Override
    public Response intercept(Interceptor.Chain chain) throws IOException {
        Request original = chain.request();

        Request request = original.newBuilder()
            .header("User-Agent", "Your-App-Name")
            .header("Accept", "application/vnd.yourapi.v1.full+json")
            .method(original.method(), original.body())
            .build();

        return chain.proceed(request);
    }
}

OkHttpClient client = httpClient.build();  
Retrofit retrofit = new Retrofit.Builder()  
    .baseUrl(API_BASE_URL)
    .addConverterFactory(GsonConverterFactory.create())
    .client(client)
    .build();
添加动态请求头
public interface UserService {  
    @GET("/tasks")
    Call<List<Task>> getTasks(@Header("Content-Range") String contentRange);
}

Retrofit2 与Retrofit1添加请求头截然不同,如果你想使用Retrofit1请忽略这篇blog.


@Query 注解的可选参数

根据查询条件获取请求结果,使用@GET请求注解+@Query/@QueryMap

public interface TaskService {  
    @GET("/tasks")
    Call<List<Task>> getTasks(@Query("sort") String order);
}

如果你使用的 BaseUrl https://your.api.com,调用上面接口的方法,传入参数值=1,拼接后的请求路径就是:

https://your.api.com/tasks?sort=1

注解@Query的参数支持多种类型:int, float, long等

public interface TaskService {  
    @GET("/tasks")
    Call<List<Task>> getTasks(
        @Query("sort") String order,
        @Query("page") Integer page);
}

How to Integrate XML Converter?关于这个xml解析这块略过了(一般情况下不会用到,都JSON+GSON),使用也挺简单一个套路。


Debug 调试请求,打印log日志

在开发中打印网络请求和返回结果是非常重要的,如果你在Retrofit中使用它,你会发现实现该功能的方法已经不可用了。

RestAdapter.Builder builder = new RestAdapter.Builder()  
    .setEndpoint(API_LOCATION)
    .setLogLevel(RestAdapter.LogLevel.FULL) // this is the important line
    .setClient(new OkClient(new OkHttpClient()));

Retrofit 依靠okhttp提供的日志系统,HttpLoggingInterceptor.需要添加相关依赖,使用类似上面提到的添加头部拦截器一样。

个人更喜欢使用Logger库配合输出,所以自定义HttpLoggingInterceptor最好。修改HttpLoggingInterceptor.Logger接口类名定义和log方法调用即可

 public interface Logger {
    void log(String message);

    /** A {@link Logger} defaults output appropriate for the current platform. */
    Logger DEFAULT = new Logger() {
      @Override public void log(String message) {
        Platform.get().log(INFO, message, null);
      }
    };
  }

如何上传文件到服务器

文件上传有很多种方式,先大致列举一二

  • File转String上传

  • 文件流上传

  • byte字节上传

  • 文件上传

上传的文件又分为单一文件和多个文件,在Retrofit下面又该如何编码呢?按照流程还是的定义interface,@Multipart注解方法,@Part注解参数


/**
 * Created by idea on 2016/11/28.
 */
public interface ApiService {

    @Multipart
    @POST("/upload")
    Call<ResponseBean> uploadPhone(@Part("description") RequestBody requestBody,@Part MultipartBody.Part part);
}

RequestBody在这里进行一下拓展,RequestBody源自Okhttp3的一个类,writeTo方法依赖于Okio框架,这块知识有想要了解的自行补脑。

MediaType这里列举三类

  • text/plain; charset=utf-8 //构建字符串请求体

  • application/octet-stream //构建字节请求体 、文件请求体

  • application/json; charset=utf-8 //post上传json
    ……………

通过调用RequestBody定义好的create方法即可。当然获取RequestBody实例方法不止这一种,大致可分为三类

  • create()
/**
 * 此方法在使用Retrofit基本不会用到 post参数传入  
 */
public Request getRequest(){
  Request request = new Request.Builder()  
     .url(baseUrl)  
     .post()
     .build();
  return request;
}
  • FormBody.Builder()
/**
 * 请求表单构建
 */
public RequestBody getRequestBody(){
   RequestBody formBody=new FormBody.Builder()  
                .add("name","idea")  
                .add("sex","1")       
                .build();  
   return request;             
}
  • MultipartBody.Builder()
 /**
  * MultipartBody.builder构建RequestBody
  */
 public void getRequestBody(String imageUrl,String uuid){
        RequestBody multipartBody=new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("uuid", uuid)
                .addFormDataPart("avatar", "20160808324.jpg",      RequestBody.create(MediaType.parse("application/octet-stream"),new File(imageUrl)))
                .addPart(...)
                .build();
        return multipartBody;
    }

addPart(..)方法用于添加RequestBody,Headers和添加自定义Part

下面是一段上传文件的代码块

 public void onUploadImage(String imageUrl,String description,ApiService apiService){
        File file = new File(imageUrl);
        RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
        MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile);
        RequestBody descriptionRequestBody = RequestBody.create(MediaType.parse("multipart/form-data"), description);

        Call<ResponseBean> call = apiService.uploadPhone(descriptionRequestBody,body);
        call.enqueue(new Callback<ResponseBean>() {
            @Override
            public void onResponse(Call<ResponseBean> call,Response<String> response) {

            }

            @Override
            public void onFailure(Call<ResponseBean> call, Throwable t) {

            }
        });
    }

这里使用multipart/form-data,它和其他有什么区别呢?

1.application/x-www-form-urlencoded

这是通过表单发送数据时默认的编码类型。我们没有在from标签中设置enctype属性时默认就是application/x-www-form-urlencoded类型的。application/x-www-form-urlencoded编码类型会把表单中发送的数据编码为名称/值对。这是标准的编码格式。当表单的ACTION为POST的时候,浏览器把form数据封装到http body中,然后发送到服务器。当表单的ACTION为GET的时候,application/x-www-form-urlencoded编码类型会把表单中发送的数据转换成一个字符串(name=coderbolg&key=php),然后把这个字符串附加到URL后面,并用?分割,接着就请求这个新的URL。

2.multipart/form-data

这个是专门用来传输特殊类型数据的,如我们上传的非文本的内容,比如图片或者MP3等。multipart/form-data编码类型会把表单中的发送的数据编码为一条消息,页面上每个表单控件对应消息中的一部分。当表单中有file类型控件并希望它正常工作的话(废话吧)就必须设置成multipart/form-data类型,浏览器会把整个表单以控件为单位分割,并为每个部分加上Content-Disposition(form-data或者file),Content-Type(默认为text/plain),name(控件 name)等信息,并加上分割符(boundary)。

3.text/plain

数据以纯文本形式进行编码,其中不含任何控件或格式字符。

以上资料三点摘自:http://www.fx114.net/qa-163-89638.aspx,平时上传文件使用multipart/form-data基本没问题,如果你想知道更多的HTTP Content-Type相关信息,可以参考下面链接

// http://tool.oschina.net/commons

Retrofit 2 与1.9的差异

Retrofit2与之前版本的具体差异变化自行参考(idea没用过以以前版本的直接2.1.0)

// https://futurestud.io/tutorials/retrofit-2-upgrade-guide-from-1-9

这里补充一点:假设项目你自己引入了高版本的Okhttp versionA,而使用的Retrofit2.x版本底层依赖的Okhttp的版本号versionB,明显Okhttp重复引用,如果versionB 小于你当前引入versionA,当你删除了versionA会发现method找不到等一些列问题,最好的解决办法就是把Retrofit2依赖库改成你当前compile的库

compile ('com.squareup.retrofit2:retrofit:2.1.0') {  
  // exclude Retrofit’s OkHttp peer-dependency module and define your own module import
  exclude module: 'okhttp'
}
compile 'com.squareup.okhttp3:okhttp:3.4.1'  

Retrofit 2 错误异常处理

首先,我们创建一个错误对象,官方给出的ErrorUtils对个人来讲并不太完美,我还是比较喜欢这样

{
    code: 404,
    msg: "服务器连接失败"
}

/**
 * Created by idea on 2016/11/9.
 */
public class AppErrorCode {
    /**
     * 未知错误
     */
    public static final int UNKNOWN = 1000;
    /**
     * 解析错误
     */
    public static final int PARSE_ERROR = 1001;
    /**
     * 网络错误
     */
    public static final int NETWORK_ERROR = 1002;
    /**
     * 协议出错
     */
    public static final int HTTP_ERROR = 1003;
}

/**
 * Created by idea on 2016/11/9.
 */
public class AppException extends Exception {
    private int code;
    private String msg;

    public AppException(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }

    public static AppException handleException(Throwable e) {
        if (e instanceof JsonParseException
                || e instanceof IOException 
                || e instanceof JSONException
                || e instanceof ParseException) {
            return new ApiException(AppErrorCode.PARSE_ERROR, "解析错误");
        } else if (e instanceof ConnectException) {
            return new ApiException(AppErrorCode.NETWORK_ERROR, "连接失败");
        } else if(){
            //.............略......................
        }else{
            return new ApiException(AppErrorCode.UNKNOWN, "未知错误");
        }
    }

}

举个例子,当你convert response数据发生io异常是,只需要这样

    try {
            error = converter.convert(response.errorBody());
        } catch (IOException e) {
            return AppException.handleException(e);
        }

表单提交@Field @FieldMap

表单提交可能存在一个key对应一个或多个值的情况,在Retrofit中编码如下

public interface TaskService {  
    @FormUrlEncoded
    @POST("tasks")
    Call<Task> createTask(@Field("title") String title);
}

public interface TaskService {  
    @FormUrlEncoded
    @POST("tasks")
    Call<List<Task>> createTasks(@Field("title") List<String> titles);
}

当你的参数@Field参数过多了,可以尝试用@FieldMap

public interface UserService {  
    @FormUrlEncoded
    @PUT("user")
    Call<User> update(@FieldMap Map<String, String> fields);
}

Retrofit 2 怎么为每个请求都添加 Query Parameters

第一个想到的就应该是拦截器了,intercept回调方法addQueryParameter

OkHttpClient.Builder httpClient =  
    new OkHttpClient.Builder();
httpClient.addInterceptor(new Interceptor() {  
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();
        HttpUrl originalHttpUrl = original.url();

        HttpUrl url = originalHttpUrl.newBuilder()
                .addQueryParameter("apikey", "your-actual-api-key")
                .build();

        // Request customization: add request headers
        Request.Builder requestBuilder = original.newBuilder()
                .url(url);

        Request request = requestBuilder.build();
        return chain.proceed(request);
    }
});

Retrofit 2 Url语法

个人觉得这里没什么好理解,参考实例看看就明白了


Retrofit 2 如何从服务器下载文件

从服务器下载文件主要分为以下几个步凑(个人更喜欢用开源库进行下载,一般采用三级缓存+动态权限)

  • 如何改造Request
// option 1: a resource relative to your base URL
@GET("/resource/example.zip")
Call<ResponseBody> downloadFileWithFixedUrl();

// option 2: using a dynamic URL
@GET
Call<ResponseBody> downloadFileWithDynamicUrlSync(@Url String fileUrl);  
  • 如何调用请求
FileDownloadService downloadService = ServiceGenerator.create(FileDownloadService.class);

Call<ResponseBody> call = downloadService.downloadFileWithDynamicUrlSync(fileUrl);

call.enqueue(new Callback<ResponseBody>() {  
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        if (response.isSuccess()) {
            Log.d(TAG, "server contacted and has file");

            boolean writtenToDisk = writeResponseBodyToDisk(response.body());

            Log.d(TAG, "file download was a success? " + writtenToDisk);
        } else {
            Log.d(TAG, "server contact failed");
        }
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        Log.e(TAG, "error");
    }
});
  • 如何缓存文件
private boolean writeResponseBodyToDisk(ResponseBody body) {  
    try {
        // todo change the file location/name according to your needs
        File futureStudioIconFile = new File(getExternalFilesDir(null) + File.separator + "Future Studio Icon.png");

        InputStream inputStream = null;
        OutputStream outputStream = null;

        try {
            byte[] fileReader = new byte[4096];

            long fileSize = body.contentLength();
            long fileSizeDownloaded = 0;

            inputStream = body.byteStream();
            outputStream = new FileOutputStream(futureStudioIconFile);

            while (true) {
                int read = inputStream.read(fileReader);

                if (read == -1) {
                    break;
                }

                outputStream.write(fileReader, 0, read);

                fileSizeDownloaded += read;

                Log.d(TAG, "file download: " + fileSizeDownloaded + " of " + fileSize);
            }

            outputStream.flush();

            return true;
        } catch (IOException e) {
            return false;
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }

            if (outputStream != null) {
                outputStream.close();
            }
        }
    } catch (IOException e) {
        return false;
    }
}
  • 下载大文件使用@Streaming
@Streaming
@GET
Call<ResponseBody> downloadFileWithDynamicUrlAsync(@Url String fileUrl);  

final FileDownloadService downloadService =  
                ServiceGenerator.create(FileDownloadService.class);

new AsyncTask<Void, Long, Void>() {  
   @Override
   protected Void doInBackground(Void... voids) {
       Call<ResponseBody> call = downloadService.downloadFileWithDynamicUrlSync(fileUrl);
       call.enqueue(new Callback<ResponseBody>() {
           @Override
           public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
               if (response.isSuccess()) {
                   Log.d(TAG, "server contacted and has file");

                   boolean writtenToDisk = writeResponseBodyToDisk(response.body());

                   Log.d(TAG, "file download was a success? " + writtenToDisk);
               }
               else {
                   Log.d(TAG, "server contact failed");
               }
           }
       return null;
   }
}.execute();    

Retrofit 2 — 取消 Requests

当前界面在请求中,突然要结束当前界面亦或者是有新的请求进来,需要取消之前的请求,此时可以调用Call提供的方法

new Callback<ResponseBody>() {  
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                Log.d(TAG, "request success");
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                if(!call.isCanceled()){
                    call.cancel();
                }
                //..........................

            }
        };
Retrofit 2如何在运行时动态改变BaseUrl

一般情况下我们使用Retrofit2时都会进行一下封装,把BaseUrl封装进去。在某种情况下(上传文件服务器和表单提交服务器不在一起),就需要我们改变BaseUrl,具体做法如下changeApiBaseUrl()

public class ServiceGenerator {  
    public static String apiBaseUrl = "http://futurestud.io/api";
    private static Retrofit retrofit;

    private static Retrofit.Builder builder =
            new Retrofit.Builder()
                    .addConverterFactory(GsonConverterFactory.create())
                    .baseUrl(apiBaseUrl);

    private static OkHttpClient.Builder httpClient =
            new OkHttpClient.Builder();

    // No need to instantiate this class.
    private ServiceGenerator() {
    }

    public static void changeApiBaseUrl(String newApiBaseUrl) {
        apiBaseUrl = newApiBaseUrl;

        builder = new Retrofit.Builder()
                        .addConverterFactory(GsonConverterFactory.create())
                        .baseUrl(apiBaseUrl);
    }

    public static <S> S createService(Class<S> serviceClass, AccessToken token) {
        String authToken = token.getTokenType().concat(token.getAccessToken());
        return createService(serviceClass, authToken);
    }

    // more methods
    // ...
}

请求的Path Parameters

如果你的url的路径参数是正确的,但注解的路径参数含有一个空字符串会导致错误的请求url

public interface TaskService {  
    @GET("tasks/{taskId}/subtasks")
    Call<List<Task>> getSubTasks(@Path("taskId") String taskId);
}

taskId传递空值将导致以下url

// https://your.api.url/tasks//subtasks  

不允许您传递null作为路径参数的值,如果你这样做,就会抛出IllegalArgumentException


Retrofit 2响应内容为String

有那么一群人不喜欢使用Gson、Jackson解析,喜欢自己封装一套解析工具,那么返回值需要为String,我们就需要添加返回值的支持(返回他不支持的结果时,就会崩溃)

compile  'com.squareup.retrofit2:converter-scalars:2.1.0'  
private static Retrofit getRetrofit(String url) {
      return new Retrofit.Builder().baseUrl(url)
                //增加返回值为String的支持
                .addConverterFactory(ScalarsConverterFactory.create())
                //增加返回值为Gson的支持(以实体类返回)
                .addConverterFactory(GsonConverterFactory.create())
                //增加返回值为Oservable<T>的支持
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();

使用方面的知识点大概就这么多了,以上内容多数参照https://futurestud.io/tutorials/retrofit-getting-started-and-android-client这里,看完之后做个记录,收获还是不小的。

2020必备:极速入门Retrofit+OkHttp网络框架使用,这一篇就够了 老生常谈 什么是 Retrofit ? Retrofit 早已不是什么新技术了,想必看到这篇博客的大家都早已熟知,这里就不啰嗦了,简单介绍下: Retrofit 是一个针对 Java 和 Android 的设计的 REST 客户机。它通过基于 REST 的 web 服务检索和上传 JSON (或其他结构化数据)变得相对容易。在使用中,您可以配置用于数据序列化的转换器。对于 JSON ,通常使用G... 阅读详情

相关推荐

Retrofit2+OkHttp3+RxJava搭建网络框架

Retrofit2+OkHttp3+RxJava搭建网络框架 和别人分享你的知识,那才是永恒之道。之前跟朋友闲聊的时候,从朋友那里知道了安卓的几个最新的框架,其中就包括本博要说的Retrofit和RxJava。当时听朋友说的有多么多么好,多么多么的高效,本人也是出于对技术的追求,回来后自己网上搜了搜,相关资料还是挺多的,并且好评率极高,自己也受益匪浅。最近刚又开了一个项目,刚好可以那这个项目练

大熊 1万+

Okhttp和Retrofit介绍

android常用网络加载库Okhttp介绍

qq_53077129的博客 1925

Android Retrofit源码分析(一):Retrofit是什么?和OkHttp的区别是什么?为什么需要他?

为什么会出现Retrofit?它的作用是什么?

前期后期 1543

Retrofit+okhttp3使用,附带源码解析

复盘网路请求Retrofit+okhttp的简单使用,据了解,最新技术Retrofit+okhttp+moshi+kotlin协程可实现的网络请求框架更易扩展,健壮。后续还要学习。

1899

Retrofit+OkHttp修改请求路径

前言一、Retrofit拦截器进行HttpUrl重构1、Interceptor2、HttpLoggingInterceptor二、通过反射对Retrofit BaseUrl进行重构1、反射的切入点2、反射修改HttpUrl3、对Retrofit请求方法的缓存进行修改三、修改Retrofit2+Okhttp3的BaseUrl四、使用与测试1、使用2、调试总结 前言 使用Retrofit+Okhtt...

wuyangyang_2000的博客 2811

OkHttp和Retrofit

介绍 OkHttp OkHttp是由Square公司提供的处理网络请求的开源库,有以下特性: 支持Http2.0,对一台机器的所有请求共享同一个socket 内置连接池,支持连接复用,减少延迟 Interceptors(拦截器)轻松处理请求与响应,支持透明的GZIP压缩响应体 请求失败时自动重连 拥有队列线程池,轻松写并发 基于Headers的缓存策略 Retrofit Retr...

android_ErChen的开发之路 1796

【Android面试】OkHttp & Retrofit 专题

OkHttp & Retrofit 专题

studyForMokey的博客 571

《Kotlin系列》之协程搭配Retrofit+OkHttp3网络请求封装(kotlin+retrofit+okhttp3)

上一篇关于MVVM架构的基类封装,这篇会在MVVM的基础上示范使用kotlin+retrofit+okhttp封装的网络框架,里面会涉及到协程的使用,协程异常处理包装。

专注Android ,直到世界尽头 3212

Retrofit+okhttp网络框架介绍

网络框架选择过程目前Github上使用比较多的Android Http库依次是Retrofit,okhttp,android-async-http,okhttp-utils,async-http-client,等等; 中间经过一系列的分析,根据start,更新速度,作者,库的实用性,性能,以及拓展性等等,最终选出了3个PK.(Retrofit,okhttp,okhttp-utils) 其中Ret

JackWaiting 1万+

Retrofit,Okhttp对每个Request统一动态添加header和参数(五)

文/Tamic 地址:http://blog.youkuaiyun.com/sk719887916?viewmode=list HeaderHow to Add header to Every Request 添加统一通用header, 通用参数,Retrofit 2 — How to Add Query Parameters to Every Request

Tamic (大白) 2万+

Gson 修改版 忽略大小写

该资源为 Gson 开源项目的自改版 可以忽略 解析Json 时 Json key的大小写问题。 例如: {adc:&quot;1&quot;,Beta:&quot;1.0.1&quot;,NAME:&quot;张三&quot;} 在解析时候 实体Entity的字段可以是 public String abc ; 也可以是 public String Abc; 也可以是 public String ABC ; 对字段的大小写 没有限制了。

Android OkHttp和Retrofit的区别与联系

一.OkHttp OkHttp是Square开源的轻量级框架,是一款现代、高效、快速的Android版Http client。 1.OkHttp特性: 它的设计和实现的首要目标便是高效,有如下特性: •支持SPDY、连接池、Gzip和Http缓存(记住第一条可直接推及记起下面四条,感觉萌萌哒^_^); •支持SPDY,因此可以同一IP多个连接共享同一个socket(SPDY并不是一种...

残风乱了温柔的博客 6964

从安装Eclipse 开始 开发第一个 CDS View

一篇超级简单快速的 创建CDS VIEW 入门步骤介绍文章。

IT小职员 4248

网络开源框架OkHttp和Retrofit总结分享

网络开源框架OkHttp和Retrofit总结分享                                                                            一、OkHttp框架 ⒈ OkHttp框架简介 Android为我们提供了两种HTTP交互的方式:HttpURLConnection和 Apache HTTP Client,虽然两者都支持H

ustcrding的专栏 1万+

Flink 作业报错——TaskManager连接不上

Flink 作业报错——TaskManager连接不上

lm223的博客 1900

量化实战:如何用Barra多因子模型给你的投资组合做一次‘风险体检’?

本文详细介绍了如何利用Barra多因子模型对投资组合进行全面的风险体检,帮助投资者识别行业和风格因子的风险暴露。通过实战案例和Python代码示例,展示了数据采集、风险归因和组合优化的完整流程,提升投资组合的风险调整收益。

weixin_27215337的博客 323

『Android基础入门』网络编程之OKHTTP与Retrofit框架

网络请求-》java-》OKHTTP数据解析-》java解析-》GSON配合插件解析概述 - OkHttp (square.github.io)HTTP是现代应用程序网络的方式。这就是我们交换数据和媒体的方式。有效地执行 HTTP 会使内容加载速度更快并节省带宽。OkHttp 是一个默认高效的 HTTP 客户端:OkHttp 适用于 Android 5.0+(API 级别 21+)和 Java 8+。框架官网地址 添加网络请求权限添加依赖 由此我们可以知道okhttp框架相较于传统的网络请求的优势:打印测

一个专注于客户端开发的技术er,不定期更新Android,Flutter,HormonyOS等方向的使用心得和感悟 1701

Ubuntu20.04系统Cuckoo sandbox所需环境搭建及安装(超详细图文)

请勿叫我大神,我只是互联网的搬运工~ 前言 本教程以Ubuntu20桌面版作为示范,服务器版同样适用,但可能稍微麻烦一点,服务器版本的话安装Xmanage套装使用可能会相对轻松一些,由于后面会使用到vbox的使用,对vbox命令行操作不熟悉的小伙伴用xmanage可能会相对轻松一点。 提示:以下是本篇文章正文内容,下面案例可供参考 安装ubuntu20桌面版 步骤1:转到https://ubuntu.com/download/desktop下载下载.iso。 步骤2:使用您选择的虚拟机监控程序(VMWa

ElsonHY的博客 1万+

Retrofit + OkHttp配合使用

由于公司项目中的网络框架依然使用volley,最近有打算更换网络框架的打算,而现在Android开发中流行的框架莫过于Retrofit+OKHttp,于是打算学习学习等自己摸索的差不多的时候将其应用到自己的项目当中。 首先是引入到项目中 这里写代码片...

Ren_JX的博客 2475

基于STM32F407与AD9910/AD9959的高频可调DDS信号源设计与实战

本文详细介绍了基于STM32F407微控制器与AD9910/AD9959高性能DDS芯片的高频可调信号源设计与实现。文章从DDS原理入手,阐述了双芯片方案的优势,并重点分享了高频电路设计中的电源、PCB布局布线等关键硬件设计技巧与避坑指南。同时,提供了通过SPI配置DDS芯片及利用Matlab生成任意波形的软件驱动方法,旨在帮助开发者打造一个覆盖10Hz至4200MHz的实用信号发生器。

weixin_36184718的博客 120

第44节:cesium 大雾效果(cesium自带)(含源码+视频)

cesium 大雾效果(cesium自带)

qq_29384639的博客 1702

Android:Retrofit简单说明 & 使用方法解析

FragmentFragment一、什么是Fragment?参考 Fragment 一、什么是Fragment? 参考 1、https://www.jianshu.com/p/f57b7cdb1c99 2、https://www.jianshu.com/p/865e9ae667a0 3、https://www.jianshu.com/p/1f3b646db6d5 4、https://www.jianshu.com/p/0d1ccde17fb3 rxjava 1、https://www.jianshu.com

JMW1407的博客 1万+

(王道计算机组成原理)第二章数据的表示和运算-第三节:运算方法和运算电路 1(基本运算部件)

本文主要介绍了计算机组成原理中的加法器和算术逻辑单元(ALU)的基本概念与实现原理。首先,文章详细讲解了一位全加器的工作原理,包括其输入输出、逻辑表达式及真值表。接着,介绍了并行加法器的两种类型:串行进位和并行进位的加法器,分析了它们的优缺点。随后,文章探讨了带标志位的加法器,说明了如何通过标志位生成逻辑来判断加法结果的状态。最后,文章阐述了ALU的功能及其实现原理,包括ALU的基本结构、控制信号的作用以及在实际考试中的图示表示。通过这些内容,读者可以全面了解加法器和ALU在计算机中的重要作用及其工作原理。

快乐江湖的博客 3837

Android OKHttp与Retrofit与RXJava的使用

我们项目当中的每个app都需要用到网络和服务器进行交互,在Android项目开发中使用HTTP协议完成 通信的话,基本上都要用到OkHttp或者Retrofit。

Happy__snake的博客 2214

上位机图像处理和嵌入式模块部署(开篇)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 图像处理是现实生活当中很实用的一门技术。工业上一般采用的是机器视觉,以传统算法和光源控制为主,部分采用了深度学习技术。而生活当中,则主要以二维码识别、人脸识别、车牌识别和ocr识别为主,这中间深度学习与AI扮演了很重要的角色。过去,市场上的商业收费软件,主要还是以传统设备厂商提供的上位机软件为主,那这一部分是不是结合最新的npu技术,应该做出一些新的东西出来呢?

嵌入式-老费,一个分享专业嵌入式知识的blog 1334

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

网络连接框架 OKHTTP Retrofit RxJAVA

同步需要开启线程,异步不需要开启线程同步和异步都需要跳转到UI线程修改UIRxJava 是一种响应式编程,来创建基于事件的异步操作库。基于事件流的链式调用、逻辑清晰简 洁。RxJava 我的理解是将事件从起点(上游)流向终点(下游),中间有很多卡片对数据进操作并传 递,每个卡片获取上一个卡片传递下来的结果然后对事件进行处理然后将结果传递给下一个卡片, 这样事件就从起点通过卡片一次次传递直到流向终点。......

suiyue010211的博客 1405

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

上一篇: 前端学习CSS网页布局篇(五)
下一篇: Android 动画源码学习篇(一)
AnalyzeSystem
博客等级 码龄11年 204粉丝 114原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值