OkHttp + Retrofit使用示例。从引入依赖,编写接口,到发起网络请求。

引入依赖

引入依赖,使用Retrofit2。

implementation 'com.squareup.retrofit2:retrofit:2.1.0'
implementation 'com.squareup.retrofit2:converter-gson:2.1.0'
implementation 'com.squareup.retrofit2:adapter-rxjava:2.1.0'

查询 @Query

例如 URL https://base_url/backend-service/config?env=dev,问号后面属于查询内容。 不论是GET或POST,都要用@Query这个注解。否则会报异常。

URL填充与拼接

单纯 URL 填充可以用@Path注解。 例如下面这个post请求。

@POST("user-service/user/{uid}/token/refresh")
Call<RefreshTokenResp> refreshToken(@Path("uid") String uid, @Query("token") String token);

GET带有查询的参数

public interface CfgService {

    @GET("backend-service/config")
    Call<ServerCfgResp> getServerCfg(@Query("env") String env);
}

POST,带有查询的参数和body

public interface UserService {

    @POST("user-service/login")
    Call<LoginResp> login(@Query("lenovoST") String token, @Query("realm") String realm,
                            @Body RequestBody body);

    @POST("user-service/logout")
    Call<CommonEntity> logout(@Query("token") String token, @Body RequestBody body);
}

调用的时候要创建RequestBody;先调查好后台接受的body类型。

Map<String, String> map = new HashMap<>();
        map.put("system", "Android");
        map.put("phoneBrand", Build.BRAND);
        map.put("modelNum", Build.MODEL);
        Gson gson = new Gson();
        String bodyJson = gson.toJson(map);
        RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), bodyJson);

初始化OkHttpClient;这里信任所有的SSL证书(正式环境不建议这么做)。

private CfgService cfgService;

    public void initService() {
        SSLSocketFactory sslSocketFactory = null;
        try {
            sslSocketFactory = SSLUtils.getSSLSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
        }
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        if (sslSocketFactory != null) {
            Log.d(TAG, "sslSocketFactory != null");
            builder.sslSocketFactory(sslSocketFactory);
        } else {
            Log.w(TAG, "sslSocketFactory == null");
        }
        builder.hostnameVerifier(new HostnameVerifier() {
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true; // 强制返回true
            }
        });
        OkHttpClient lenClient = builder.build();
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(ServerCfg.HOST_URL)
                .client(lenClient)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
        cfgService = retrofit.create(CfgService.class);
    }

调用网络请求

mNetworkManager.getUserApi().login(mLenovoToken, ServerCfg.RID, requestBody).enqueue(new Callback<LoginResp>() {
            @Override
            public void onResponse(Call<LoginResp> call, final Response<LoginResp> response) {
                //...
            }

            @Override
            public void onFailure(Call<LoginResp> call, final Throwable t) {
                //...
            }
        });

信任所有服务器的ssl

并不推荐这么做

public class SSLUtils {
    /**
 * @return 信任所有服务器
 */
    public static SSLSocketFactory getSSLSocketFactory() throws Exception {
        SSLSocketFactory sslSocketFactory = null;
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, new TrustManager[]{createTrustAllManager()}, new SecureRandom());
        sslSocketFactory = sslContext.getSocketFactory();
        return sslSocketFactory;
    }

    public static X509TrustManager createTrustAllManager() {
        X509TrustManager tm = null;
        try {
            tm = new X509TrustManager() {
                public void checkClientTrusted(X509Certificate[] chain, String authType)
                        throws CertificateException {
                    //do nothing,接受任意客户端证书
                }

                public void checkServerTrusted(X509Certificate[] chain, String authType)
                        throws CertificateException {
                    //do nothing,接受任意服务端证书
                }

                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[0];
                }
            };
        } catch (Exception e) {

        }
        return tm;
    }

}

service 使用 io.reactivex.Observable

import io.reactivex.Observable; // 这个是rx2的包
// ---

    /**
 * 用户反馈接口
 *
 * @param content 用户输入的反馈内容
 */
    @POST("feedbackAction")
    Observable<UserFeedback> userFeedback(@Query("appVersion") String appVersion,
                                          @Query("phoneModel") String phoneModel,
                                          @Query("phoneOsVersion") String osVersion,
                                          @Query("submitContent") String content);

示例 - Retrofit2,RxJava2

引入依赖

implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
    implementation 'io.reactivex.rxjava2:rxjava:2.2.8'

定义interface

import java.util.Map;

import io.reactivex.Observable;
import retrofit2.http.Field;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Query;

/**
 * RustDrone后台接口
 * Created on 2019-5-17
 */
public interface RustDroneCommonService {

    /**
 * 用户反馈接口
 *
 * @param content 用户输入的反馈内容
 */
    @FormUrlEncoded
    @POST("feedbackAction")
    Observable<FeedbackResp> userFeedback(@Field("appVersion") String appVersion,
                                          @Field("phoneModel") String phoneModel,
                                          @Field("phoneOsVersion") String osVersion,
                                          @Field("submitContent") String content,
                                          @FieldMap Map<String, String> map);

    /**
 * 获取手机验证码
 *
 * @param mobile 手机号
 */
    @GET("verifyCode")
    Observable<PhoneCodeResp> getPhoneCode(@Query("mobile") String mobile, @Query("oprType") int type);
}

调用接口

import io.reactivex.Observer;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;

RustDroneDataCenter.getCenter().getCommonService().userFeedback(BuildConfig.VERSION_NAME,
                    Build.MODEL.replace(" ", "-"), Build.VERSION.RELEASE, fd, ext)
                    .subscribeOn(Schedulers.newThread())
                    .observeOn(AndroidSchedulers.mainThread())
                    .subscribe(new Observer<FeedbackResp>() {
                        @Override
                        public void onSubscribe(Disposable d) {
//                            LL.dn(TAG, "onSubscribe: " + d);
                        }

                        @Override
                        public void onNext(FeedbackResp feedbackResp) {
                            LL.dn(TAG, "onNext: " + feedbackResp);
                            if (feedbackResp.getCode() == 0) {
                                popSubmitSuccessDialog();
                            } else {
                                LL.e("上传用户反馈失败");
                                mPbLayout.setVisibility(View.GONE);
                                popRetryDialog();
                            }
                        }

                        @Override
                        public void onError(Throwable e) {
                            LL.e("上传用户反馈失败 code: " + e);
                            mPbLayout.setVisibility(View.GONE);
                            popRetryDialog();
                        }

                        @Override
                        public void onComplete() {
                            mPbLayout.setVisibility(View.GONE);
                            LL.dn(TAG, "onComplete: 上传结束");
                        }
                    });

OkHttp 相关面试题:

1. 简述一下 OkHttp 的优势

OkHttp 是一个非常优秀的网络请求框架,已被谷歌加入到 Android 的源码中。目前比较流行的 Retrofit 也是默认使用 OkHttp 的。

  • 易使用、易扩展。
  • 支持 HTTP/2 协议,允许对同一主机的所有请求共用同一个 socket 连接。
  • 如果 HTTP/2 不可用, 使用连接池复用减少请求延迟。
  • 支持 GZIP,减小了下载大小。
  • 支持缓存处理,可以避免重复请求。
  • 如果你的服务有多个 IP 地址,当第一次连接失败,OkHttp 会尝试备用地址。
  • OkHttp 还处理了代理服务器问题和SSL握手失败问题。

2. 讲一下 okhttp 的主要工作流程

第一步:创建 Request 和 OkHttpClicent 对象,然后将 Request 封装成 Call 对象,然后调用 enqueue() 方法执行异步请求;

第二步:Dispatcher 的 enqueue(AsyncCall) 和 promoteAndExecute() 方法,enqueue(AsyncCall) 有两个作用:一是添加 AsyncCall 到预执行队列 readyAsyncCalls,二是设置同一Host的连接计数器;promoteAndExecute() 负责真正对 AsyncCall 进行资源的调度:对 readyAsyncCalls 进行迭代循环,如果正在执行的队列 size不超过 64 且同一Host 的连接计数器的值不超过 5,就将这个请求放入到 runningAsyncCalls。然后遍历runningAsyncCalls,挨个执行里面的请求;

第三步:AsyncCall 对象把自己作为任务交到线程池执行,提交成功后就 finish 掉;

3. OkHttp 的 Interceptor 类

官网:拦截器是 Okhttp 中提供的一种强大机制,它可以实现网络监听、请求、以及响应重写、请求失败重试等功能。

  • RetryAndFollowUpInterceptor:重试和失败重定向拦截器
  • BridgeInterceptor:桥接拦截器,处理一些必须的请求头信息的拦截器
  • CacheInterceptor:缓存拦截器,用于处理缓存
  • ConnectInterceptor:连接拦截器,建立可用的连接,是 CallServerInterceptor 的基本
  • CallServerInterceptor:请求服务器拦截器将 http 请求写进 IO 流当中,并且从 IO 流中读取响应 Response

4. 请简述如何使用 OKHttp 发起网络请求?

使用 OKHttp 发起网络请求的步骤如下:

  • 新建 OKHttpClient
  • 根据请求的 URL 新建 Request 对象 请求过程中所使用的参数放在 Request 对象的 RequestBody 中
  • 使用 Request 对象新建 Call 对象
  • 同步请求执行 call.execute(),异步请求执行 call.enqueue
  • 获取请求执行结果对象 Response 并解析

5. 如何使用 OKHttp 对 Cookie 进行处理?

使用OKHttp进行Cookie处理,有两种方式:

第一种是手动处理,在 callback 回调的 response 对象中,通过 response.headers() 可以获取所有头信息,包括 Cookie 信息。这时我们可以将 Cookie 保存到本地,下次发起网络请求时再将 Cookie 信息加在头部。

第二种是使用 OKHttp 提供的 Cookjar,具体代码如下:

builder = new OkHttpClient.Builder();  
builder.cookieJar(new CookieJar() {  
    private final HashMap<String, List<Cookie>> cookieStore = new HashMap<String, List<Cookie>>();  
  
    @Override  
    public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {  
        cookieStore.put(url.host(), cookies);  
    }  
  
    @Override  
    public List<Cookie> loadForRequest(HttpUrl url) {  
        List<Cookie> cookies = cookieStore.get(url.host());  
        return cookies != null ? cookies : new ArrayList<Cookie>();  
    }  
});  
okHttpClient = builder.build();  

其中,saveFromResponse() 会在接收服务器响应时执行,这时可以保存 Cookie 信息,loadForRequest() 会在发起网络请求时执行,这是可以添加已保存的 Cookie 信息。

6. OkHttp 对于网络请求都有哪些优化,如何实现的?

OKHttp 相比原生的 HttpUrlConnection 主要做了如下优化:

  • 支持HTTP2/SPDY
  • socket自动选择最好路线,并支持自动重连
  • 拥有自动维护的socket连接池,减少握手次数,降低响应延迟
  • 拥有队列线程池,轻松写并发
  • 拥有Interceptors轻松处理请求与响应(比如透明GZIP压缩,LOGGING)
  • 基于Headers的缓存策略,避免重复的网络请求

Android开发:框架源码解析视频参考