Android当中的MVP模式插曲-封装OkHttp
Posted 范二er
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android当中的MVP模式插曲-封装OkHttp相关的知识,希望对你有一定的参考价值。
Android 当中的 MVP 模式(三)基于分页列表的封装
Android 当中的 MVP 模式(四)插曲-封装 OkHttp
Android 当中的 MVP 模式(五)封装之后的 OkHttp 工具在 Model 层的使用
Android 当中的 MVP 模式(六)View 层 Activity 的基类— BaseMvpActivity 的封装
摘要:前两篇中使用的网络请求工具是 OkHttp
,并没有经过封装,都是简单的使用 get
请求,并且将错误全部都抛到上层去解决了, 这无形之中增加了上层的编码复杂度,即使要抛向上层,起码也要给一个 errorCode
或者是 errorMsg
吧,所以这边文章就针对 OkHttp
进行封装,然后将封装之后的工具使用到上一小结的 Demo
之中。
官方给的例子
同步方法
OkHttpClient client = new OkHttpClient(); String run(String url) throws IOException Request request = new Request.Builder() .url(url) .build(); Response response = client.newCall(request).execute(); return response.body().string();
异步方法
OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(url) .build(); client.newCall(request).enqueue(new Callback() @Override public void onFailure(Call call, IOException e) @Override public void onResponse(Call call, Response response) throws IOException
对异步方法的分析
结合上面异步方法,稍作分析,涉及到如下几个对象 OkHttpClient
, Request
, Call
,Response
,其他的都一些方法的调用,所以我们的封装应该重点针对这三个对象来进行。
Request
Request
在 Okhttp
当中是抽象出来的一个请求对象,它封装了请求报文信息:请求的 Url
地址,请求的方法(Get Post
等),各种请求头(Content-Type Cookie
)以及可以选择的请求体,一般通过内部的 Builder
类来构建对象,建筑者设计模式。
那么我们这里就针对 Post Get
两种请求方式做封装,但是这里又涉及到一个问题,就是我们还需要参数,用于拼接请求 Url
的参数,举个栗子:
这是搜狐电视剧频道的 API
接口:
http://api.tv.sohu.com/v4/search/channel.json%22%20+%20%22?cid=2&o=1&plat=6&poid=1&api_key=9854b2afa779e1a6bff1962447a09dbd&%22%20+%20%22sver=6.2.0&sysver=4.4.2&partner=47&page=1&page_size=10
这么看可能特别的麻烦,我们把它拆分一下:
String baseUrl=http://api.tv.sohu.com/v4/search/channel.json%22%20+%20%22
然后剩下的都是参数了,以键值对的形式存在:
这些参数拼接在 baseUrl
后面的顺序是没有要求的,不一定要按照上面的顺序来,只要每个参数都按照固定的格式出现就可以。
看上面的完整 Url
可以发现规律,在 baseUrl
后面有一个 ?
, 然后就就是 key1=value1&key2=value2&key3=value3
这种形式的
其实遵循 RESTful API
设计的接口,都会是这种形式,所以这里也利于我们进行封装了。而 key-value
这种形式,就特别适合使用 Map
结构来封装。
说这么多,上代码,首先是对参数进行封装:
RequestParam
/**
* @author:fanyuzeng
* @date: 2017/10/27 13:55
* @desc: 封装url中的参数
*/
public class RequestParams
/**
* 使用@link ConcurrentHashMap是为了保证线程安全
*/
private ConcurrentHashMap<String, String> urlParams = new ConcurrentHashMap<>();
public RequestParams()
public RequestParams(Map<String, String> source)
for (Map.Entry<String, String> entry : source.entrySet())
put(entry.getKey(), entry.getValue());
public RequestParams(String key, String value)
put(key, value);
private void put(String key, String value)
if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value))
urlParams.put(key, value);
public ConcurrentHashMap<String, String> getUrlParams()
return urlParams;
这地方使用 ConcurrentHashMap
就是为了保证线程安全的,这个类使用的是锁分段技术,不同于一般的同步方法或者是同步代码块,它只会锁住其中一个 segment
,其他的 segment
仍然是可以访问的,所以他的效率会比 synchronized
高。
有了 RequestParam
之后,就可以使用它来拼接 url
,有了 url
之后,就可以使用它来构建 Request
对象了。
CommonRequest
1 /**
2 * @author: fanyuzeng
3 * @date: 2017/10/27 14:08
4 * @desc: response for build various kind of @link okhttp3.Request include Get Post upload etc.
5 */
6 public class CommonRequest
7 private static final String TAG = "CommonRequest";
8 /**
9 * create a Get request
10 *
11 * @param baseUrl base url
12 * @param params see @link RequestParams
13 * @return @link Request
14 * @created at 2017/10/27 14:39
15 */
16 public static Request createGetRequest(@NonNull String baseUrl, @Nullable RequestParams params)
17 StringBuilder urlBuilder = new StringBuilder(baseUrl).append("?");
18 if (params != null)
19 //将请求参数合并进url中
20 for (Map.Entry<String, String> entry : params.getUrlParams().entrySet())
21 urlBuilder.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
22
23
24 Log.d(TAG,">> createGetRequest >> " + urlBuilder.toString());
25
26 return new Request.Builder().get().url(urlBuilder.substring(0, urlBuilder.length() - 1)).build();
27
28
29 /**
30 * create a post request
31 *
32 * @param baseUrl base url
33 * @param params see @link RequestParams
34 * @return @link Request
35 * @created at 2017/10/27 14:39
36 */
37 public static Request createPostRequest(@NonNull String baseUrl, @NonNull RequestParams params)
38 FormBody.Builder mFormBodyBuilder = new FormBody.Builder();
39 for (Map.Entry<String, String> entry : params.getUrlParams().entrySet())
40 mFormBodyBuilder.add(entry.getKey(), entry.getValue());
41
42 FormBody formBody = mFormBodyBuilder.build();
43 return new Request.Builder().post(formBody).url(baseUrl).build();
44
45
46
第 16
行的 createGetRequest
方法是用于创建一个 Get
请求,主要就是使用 StringBuilder
进行 Url
的拼接,第 37
行的 createPostRequest
方法是用于创建一个 Post
请求的。 Post
请求是先创建 FormBody
,然后和 baseUrl
一个构造 Request
。
封装到这里, Request
就算是封装完了, 当然这里只封装了 Post Get
,也可以继续封装文件上传和文件下载的Request。
Call
Call
代表的是一个实际的 HTTP
请求,它是链接 Request
和 Response
的桥梁,通过 Request
对象的 newCall
方法可以得到一个 Call
对象,既支持同步获取数据,也支持异步,在上面官方例子里,也可以看出来,在异步回调中,当获取到数据,会将 Response
对象传入 Callback
的 onSuccess
方法中,如果请求没有成功,就会调用 onFailure
方法(Response
下面说)。那么看看 Callback
是什么。
先看看官方的 Callback
是什么 :
public interface Callback
void onFailure(Call call, IOException e);
void onResponse(Call call, Response response) throws IOException;
对,把注释删除了之后,其实就是两个接口,简单的理解成,一个是请求成功时的回调,一个是请求失败时的回调。
那么对这一层的封装思路是这样子的:
一般来说,在上层,我们是需要去处理上面两个回调的,在 onFailure
中,请求失败,应该做什么操作,在 onResponse
中,HTTP
返回的状态码在 [200,300)
之间应该有什么操作,在其他区间又应该有什么操作。那么在这里,我们就创建一个类,去实现这个接口,将基本的处理都在这个类里写好,出错误了,就拿到 erroeCode errorMsg
回调给上层,正确的返回信息,就直接回调给上一层。
那么这里就涉及到我们自定义的一个 Exception
和 Listener
以及实现了 Callback
接口的 CommonCallback
类。
OkHttpException
/**
* @author:fanyuzeng
* @date: 2017/10/27 13:44
* @desc:
*/
public class OkHttpException extends Exception
private int mErrorCode;
private String mErrorMsg;
public OkHttpException(int errorCode, String errorMsg)
this.mErrorCode = errorCode;
this.mErrorMsg = errorMsg;
public int getErrorCode()
return mErrorCode;
public String getErrorMsg()
return mErrorMsg;
DisposeDataListener
/**
* @author:fanyuzeng
* @date: 2017/10/27 13:49
* @desc:
*/
public interface DisposeDataListener
/**
* 请求服务器数据成功时回调的方法
*
* @param responseObj 需要回调到上层的请求结果
*/
void onSuccess(Object responseObj);
/**
* 请求服务器失败时候的回调方法
*
* @param exception 需要回调到上层的错误反馈
*/
void onFailure(OkHttpException exception);
再将这个 Listener
用代理设计模式再封装一层
DisposeDataHandler
/**
* @author:fanyuzeng
* @date: 2017/10/27 13:52
* @desc: 代理模式,使用DisposeDataHandler 代理 DisposeDataListener的操作
*/
public class DisposeDataHandler
public DisposeDataListener mListener;
public Class<?> mClass;
public DisposeDataHandler(DisposeDataListener listener)
mListener = listener;
public DisposeDataHandler(DisposeDataListener listener, Class<?> aClass)
mListener = listener;
mClass = aClass;
public void onSuccess(Object responseObj)
mListener.onSuccess(responseObj);
public void onFailure(OkHttpException exception)
mListener.onFailure(exception);
public Class<?> getClassType()
return mClass;
此处用代理模式,主要是为了优雅(装X)的处理 Class<?>
这个对象,这是用于映射的类型,在调用 Listener
的回到方法之后做判断这个对象是否存在,是,则再映射在返回,否,直接返回。
然后将三面三个类聚合到一起
CommonJsonCallback
/**
* @author:fanyuzeng
* @date: 2017/10/27 14:41
* @desc:
*/
public class CommonJsonCallback implements Callback
private static final String TAG = "CommonJsonCallback";
private static final String MSG_RESULT_EMPTY = "request could not be ececuted";
private static final String MSG_JSON_EMPTY = "json is empty or null";
private static final String MSG_RETURN_CODE = "http return code is not [200,300)";
private static final int NETWORK_ERROR = -1;
private static final int JSON_ERROR = -2;
private Handler mDeliveryHandler = new Handler(Looper.getMainLooper());
private Gson mGson = new Gson();
private DisposeDataHandler mDisposeDataHandler;
public CommonJsonCallback(DisposeDataHandler dataHandler)
mDisposeDataHandler = dataHandler;
@Override
public void onFailure(@NonNull Call call, @NonNull final IOException e)
mDeliveryHandler.post(new Runnable()
@Override
public void run()
mDisposeDataHandler.onFailure(new OkHttpException(NETWORK_ERROR, MSG_RESULT_EMPTY + e.getMessage()));
);
@Override
public void onResponse(@NonNull Call call, @NonNull final Response response) throws IOException
if (!response.isSuccessful())
mDeliveryHandler.post(new Runnable()
@Override
public void run()
mDisposeDataHandler.onFailure(new OkHttpException(NETWORK_ERROR, MSG_RETURN_CODE + response.message()));
);
final String resultJson = response.body().string();
mDeliveryHandler.post(new Runnable()
@Override
public void run()
handleResponse(resultJson);
);
private void handleResponse(String resultJson)
if (TextUtils.isEmpty(resultJson))
mDisposeDataHandler.onFailure(new OkHttpException(NETWORK_ERROR, MSG_JSON_EMPTY));
return;
if (mDisposeDataHandler.getClassType() == null)
mDisposeDataHandler.onSuccess(resultJson);
else
Object mappedDataType = mGson.fromJson(resultJson, mDisposeDataHandler.getClassType());
if (mappedDataType == null)
mDisposeDataHandler.onFailure(new OkHttpException(JSON_ERROR, MSG_JSON_EMPTY));
else
mDisposeDataHandler.onSuccess(mappedDataType);
自我感觉用代理之后,处理对象都是 DisposeHandler
,不会在看到 Listener
Class<?>
,适应起来方便些了。
要注意一点是,在 onResponse
方法中,还是在子线程中的,要及时切换线程。
到这里,就对 Call
这个对象封装完成了。
Response
Response
类封装了响应报文信息:状态吗(200
、404
等)、响应头(Content-Type
、Server
等)以及可选的响应体。可以通过 Call
对象的 execute()
方法获得 Response
对象,异步回调执行 Callback
对象的 onResponse
方法时也可以获取 Response
对象。
这东西人家已经给我们封装好了, 需要什么直接去拿就行, 也不需要在封装。
OkHttpClient
官方文档有这么一句话:
OkHttp performs best when you create a single OkHttpClient instance and reuse it for all of your HTTP calls. This is because each client holds its own connection pool and thread pools. Reusing connections and threads reduces latency and saves memory. Conversely, creating a client for each request wastes resources on idle pools.
翻译一下:当你使用一个全局的 OkHttpClient
,并且重用它发起 HTTP
请求的时候,OkHttp
的能够发挥最 NB
的性能,因为每一个客户端都持有它的连接池和线程池,如果这俩东西可以重用的话,那么就能减少潜在的因素,并且节省内存,相反的,如果为每一个客户端的每一个请求都创建一个 OkHttpClient
,那么就会浪费空闲的连接池和线程池中的资源。
叽叽歪歪这么多,就是说用 OkHttpClient
的时候要用单例模式。
刚开始我是这么设计的:
CommonokHttpClient
/**
* @author:fanyuzeng
* @date: 2017/10/27 15:21
* @desc:
*/
@Deprecated
public class CommonOkHttpClient
private static final int TIME_OUT = 30;
private static OkHttpClient sOkHttpClient;
static
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.hostnameVerifier(new HostnameVerifier()
@Override
public boolean verify(String hostname, SSLSession session)
return true;
);
builder.connectTimeout(TIME_OUT, TimeUnit.SECONDS);
builder.readTimeout(TIME_OUT, TimeUnit.SECONDS);
builder.writeTimeout(TIME_OUT, TimeUnit.SECONDS);
//允许重定向
builder.followRedirects(true);
// TODO: 2017/10/27 https
sOkHttpClient = builder.build();
/**
* 请求服务器数据的方法
*
* @param request Use @link com.project.fanyuzeng.mvpdemo.utils.okhttp.request.CommonRequest to build
* @param handler see @link DisposeDataHandler proxy class
*/
public static void requestServerData(Request request, DisposeDataHandler handler)
sOkHttpClient.newCall(request).enqueue(new CommonJsonCallback(handler));
恩,静态代码块中初始化实例化 OkHttpClient
,我认为饿汉模式没有本质的区别, 但是这种方式比饿汗模式的初始化时间更早。
好吧 ,我承认我懒,不想在整个单例类出来。。
这样写,也没什么问题,但是外界在使用的使用,比较麻烦
- 创建
RequestParams
,涉及到HashMap
的好多put
操作 - 用
RequestParam
去初始化CommonRequest
- 在上层根据请求方式去创建对应的
Request
- 再实例化一个
DisposeHandler
所以只好接着封装吧,分析上面 4
个步骤,其中步骤 1
那是不能再简化了的,因为具体的请求参数肯定是要从外界传进来的,这里涉及到的 HashMap
以及它的 put
操作是不可避免的。步骤 2
和步骤 3
完全是可以封装一下的,步骤 4
也是需要从外外界回调的方法,类似于点击监听的 onClick
方法回调。
所以把 CommonOkHttpClient
给 Deprecated
掉,重新来一个
OkHttpManager
/**
* @author:fanyuzeng
* @date: 2017/10/27 17:57
* @desc:
*/
public class OkHttpManager
private static volatile OkHttpManager sManager;
private OkHttpClient mOkHttpClient;
private OkHttpManager()
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.hostnameVerifier(new HostnameVerifier()
@Override
public boolean verify(String hostname, SSLSession session)
return true;
);
builder.connectTimeout(Constants.HTTP_TIME_OUT, TimeUnit.SECONDS);
builder.readTimeout(Constants.HTTP_TIME_OUT, TimeUnit.SECONDS);
builder.writeTimeout(Constants.HTTP_TIME_OUT, TimeUnit.SECONDS);
//允许重定向
builder.followRedirects(true);
// TODO: 2017/10/27 https
mOkHttpClient = builder.build();
public static OkHttpManager getInstance()
if (sManager == null)
synchronized (OkHttpManager.class)
if (sManager == null)
sManager = new OkHttpManager();
return sManager;
/**
* 使用@link OkHttpClient想服务器端请求数据的方法
* @param method @link Constants#HTTP_GET_METHOD Get方式,@link Constants#HTTP_POST_METHOD Post方式
* @param baseUrl baseUrl
* @param paramsMap 请求url的参数,以键值对的形式存放
* @param handler
*/
public void requestServerData(int method, String baseUrl, HashMap<String, String> paramsMap, DisposeDataHandler handler)
RequestParams requestParams = new RequestParams(paramsMap);
Request request = null;
if (method == Constants.HTTP_GET_METHOD)
request = CommonRequest.createGetRequest(baseUrl, requestParams);
else if (method == Constants.HTTP_POST_METHOD)
request = CommonRequest.createPostRequest(baseUrl, requestParams);
if (request != null)
mOkHttpClient.newCall(request).enqueue(new CommonJsonCallback(handler));
好吧,还是用双重锁模式的单例比较放心 。
到此就封装完了,下面简单的使用一下。
使用姿势
1 OkHttpManager.getInstance().requestServerData(method, url, mPaginationPresenter.getParams(), new DisposeDataHandler(new DisposeDataListener()
2 @Override
3 public void onSuccess(Object responseObj)
4 String responseJson = (String) responseObj;
5 Log.d(TAG, ">> onSuccess >> " + responseJson);
6 mPaginationPresenter.accessSuccess(responseJson);
7
8 @Override
9 public void onFailure(OkHttpException exception)
10 Log.d(TAG, ">> onFailure >> " + exception.getErrorCode());
11 mPaginationPresenter.okHttpError(exception.getErrorCode(), exception.getErrorMsg(), url);
12
13 ,null));
- 没有将
Json
数据映射成实体类, 所以在13
行构造DisposeDataHandler
的时候,第二个 类参数传的是null
。 - 这个例子是结合上一篇请求分页数据来用的,所以这里直接将
Json
数据抛给Presenter
层,让它去处理。 - 第
1
行的mPaginationPresenter.getParams()
就是拿url
中的参数。
由于篇幅的限制,这一篇先到这里,下一篇再把这个封装的 OkHttp
工具用于分页数据的请求。
最后,贴个 AS
中封装之后工具的结构图。
个人博客地址 :CODER FRAMER BIGZ
以上是关于Android当中的MVP模式插曲-封装OkHttp的主要内容,如果未能解决你的问题,请参考以下文章
Android当中的MVP模式封装之后的OkHttp工具在Model层