本文为菜鸟窝作者刘婷的连载。”商城项目实战”系列来聊聊仿”京东淘宝的购物商城”如何实现。
在前面的文章《商城项目实战 | 6.1 OkHttp 的详细介绍 网络请求更加简单》中已经详细介绍了 OkHttp 的基本属性和使用方法了,OkHttp 开源框架给开发者带来了很多的便利,使用非常方便,但是在项目中网络请求一般到处都要使用到,如果总是重复相同的代码,首先代码不够简洁,其次效率也变低了不少。代码的简洁性是一个好的开发者所不可缺少的开发特点,如何使得代码更为的简洁呢?那就要学会封装了,本文主要是针对 OkHttp 框架使用的封装,学会了这个,封装其他的也都不是问题了。
封装从字面上来理解就是包装的意思,专业点就是信息隐藏,是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。系统的其他对象只能通过包裹在数据外面的已经授权的操作来与这个封装的对象进行交流和交互。封装是面向对象的三大特征之一,就是将类的状态信息隐藏在类的内部,不允许外部程序直接访问,而通过该类提供的方法来实现对隐藏信息的操作和访问。
已经了解了封装的意义和所带来的好处,更加要把封装应用到自己的代码中去,让代码更为得具有简洁性和易维护性,下面开始 OkHttp 的封装。
在做一件事情之前,都要考虑为什么要这样做,在写一个项目之前,都要考虑这个项目的需求,在写一行代码之前,所要想的是要实现怎么的功能,同样的,在封装之前,要思考封装可以实现什么,可以先列举出来,下面是我列举的希望封装 OkHttp 能够实现的功能。
OkHttpClient 可以不重复写,直接简单调用 Get 和 Post 方法,尤其是 post 调用时传递参数希望可以一步搞定。返回的数据可以直接拿来使用,不要自己再过多的处理和序列化。有些时候加载数据希望显示提示加载框,数据加载完后显示框 dismiss,但是有的时候又不希望提示加载框显示出来。网络请求失败后,可以提示错误信息,同时对于请求成功和请求失败后的操作可以自己再扩展。 按照这样的想法,一步一步来实现 OkHttp 的封装。在列举的所要实现的功能中,希望可以返回的数据可以直接拿来使用,另外还要显示加载的提示框,那么在封装的 OkHttp 中一定需要对数据进行处理,比如 Json 类型的数据,我们就用 Gson 框架来处理了,而提示对话框的话,这里也使用一款开源的框架 SpotsDialog 。
因为使用的工具为 Android Studio 2.3.0,所使用的集成工具为 gradle,所以在使用第三方开源框架时,添加依赖是永远必不可少的。
dependencies { compile fileTree(include: ['*.jar'], dir: 'libs') androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.2.0' compile 'com.android.support.constraint:constraint-layout:1.0.1' testCompile 'junit:junit:4.12' compile 'com.daimajia.slider:library:1.1.5@aar' compile 'com.squareup.picasso:picasso:2.5.2' compile 'com.nineoldandroids:library:2.4.0' compile 'com.android.support:support-v4:25.2.0' compile 'com.android.support:recyclerview-v7:25.2.0' compile 'com.android.support:cardview-v7:25.2.0' compile 'com.squareup.okhttp3:okhttp:3.6.0' compile 'com.google.code.gson:gson:2.8.0' compile 'com.github.d-max:spots-dialog:0.7' }在使用 OkHttp 的时候,所调用的 get 和 post 方法虽然有所不同,但是其实很多的代码还是重复的,有必要把重复的代码提取出来,另外这里主要是讲解异步请求的方法,所以封装的也是针对于异步网络请求的。 创建好 OkHttpHelper 类,添加构造函数 OkHttpHelper(),首先就是在这里创建 OkHttpClient,进行简单的设置,同时 OkHttpHelper 的调用也要写好来,后期要使用 OkHttpHelper 时,就直接调 getInstance() 方法就好了。
private static OkHttpHelper mInstance; static { mInstance = new OkHttpHelper(); } public static OkHttpHelper getInstance(){ return mInstance; } private OkHttpHelper(){ mHttpClient = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10,TimeUnit.SECONDS) .writeTimeout(30,TimeUnit.SECONDS) .build(); }至于 get 和 post 还是需要分区下的,直接使用枚举。
enum HttpMethodType{ GET, POST, }然后就是 Request 的创建,根据 get 和 post 的不同来相应创建 Request ,后期我们可以直接传入区分参数 HttpMethodType 调用这一个函数就可以了。代码如下。
private Request buildRequest(String url,HttpMethodType methodType,Map<String,Object> params){ Request.Builder builder = new Request.Builder() .url(url); if (methodType == HttpMethodType.POST){ RequestBody body = builderFormData(params); builder.post(body); } else if(methodType == HttpMethodType.GET){ url = buildUrlParams(url,params); builder.url(url); builder.get(); } return builder.build(); } private RequestBody builderFormData(Map<String,Object> params){ FormBody.Builder builder = new FormBody.Builder(); if(params !=null){ for (Map.Entry<String,Object> entry :params.entrySet() ){ builder.add(entry.getKey(),entry.getValue()==null?"":entry.getValue().toString()); } } private String buildUrlParams(String url ,Map<String,Object> params) { if(params == null) params = new HashMap<>(1); StringBuffer sb = new StringBuffer(); for (Map.Entry<String, Object> entry : params.entrySet()) { sb.append(entry.getKey() + "=" + (entry.getValue()==null?"":entry.getValue().toString())); sb.append("&"); } String s = sb.toString(); if (s.endsWith("&")) { s = s.substring(0, s.length() - 1); } if(url.indexOf("?")>0){ url = url +"&"+s; }else{ url = url +"?"+s; } return url; }前面已经写好了 Request 了,下面就是要考虑网络请求之后的回调了,主要是针对于网络请求成功、网络请求失败以及请求成功但是遇到错误了的情况,定义 abstract 抽象类 BaseCallback。
public abstract class BaseCallback <T> { public Type mType; static Type getSuperclassTypeParameter(Class<?> subclass) { Type superclass = subclass.getGenericSuperclass(); if (superclass instanceof Class) { throw new RuntimeException("Missing type parameter."); } ParameterizedType parameterized = (ParameterizedType) superclass; return $Gson$Types.canonicalize(parameterized.getActualTypeArguments()[0]); } public BaseCallback() { mType = getSuperclassTypeParameter(getClass()); } public abstract void onBeforeRequest(Request request); public abstract void onFailure(Request request, Exception e) ; /** *请求成功时调用此方法 * @param response */ public abstract void onResponse(Response response); /** * * 状态码大于200,小于300 时调用此方法 * @param response * @param t * @throws IOException */ public abstract void onSuccess(Response response,T t) ; /** * 状态码400,404,403,500等时调用此方法 * @param response * @param code * @param e */ public abstract void onError(Response response, int code,Exception e) ; }请求之后就是要处理获取到的数据了,如果是 String 类型的数据,就不用过多处理,但是如果是 Json 格式的数据,就要序列化了,处理数据主要是请求成功并且没有出现错误的时候对获取的数据进行处理,所以也要用到之前写好的 BaseCallback 了。
当然先要在构造函数 OkHttpHelper() 中初始化 Gson。
Gson mGson = new Gson(); public void request(final Request request,final BaseCallback callback){ callback.onBeforeRequest(request); mHttpClient.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { callbackFailure(callback, request, e); } @Override public void onResponse(Call call,Response response) throws IOException { callbackResponse(callback,response); if(response.isSuccessful()) { String resultStr = response.body().string(); Log.d(TAG, "result=" + resultStr); if (callback.mType == String.class){ callbackSuccess(callback,response,resultStr); } else { try { Object obj = mGson.fromJson(resultStr, callback.mType); callbackSuccess(callback,response,obj); } catch (com.google.gson.JsonParseException e){ // Json解析的错误 callback.onError(response,response.code(),e); } } } else { callbackError(callback,response,null); } } }); }在使用网络请求开线程的时候一定要注意一个问题,那就是对 View 的操作,必须在主线程中,所以就需要 Handler 来帮忙了。
当然先要在构造函数 OkHttpHelper() 中初始化 Handler。
Handler mHandler = new Handler(Looper.getMainLooper()); private void callbackSuccess(final BaseCallback callback , final Response response, final Object obj ){ mHandler.post(new Runnable() { @Override public void run() { callback.onSuccess(response, obj); } }); } private void callbackError(final BaseCallback callback , final Response response, final Exception e ){ mHandler.post(new Runnable() { @Override public void run() { callback.onError(response,response.code(),e); } }); } private void callbackFailure(final BaseCallback callback , final Request request, final IOException e ){ mHandler.post(new Runnable() { @Override public void run() { callback.onFailure(request,e); } }); } private void callbackResponse(final BaseCallback callback , final Response response ){ mHandler.post(new Runnable() { @Override public void run() { callback.onResponse(response); } }); }之前列举的功能中希望有时候可以显示加载框,有时候不显示,所以这里就需要扩展下之前的 BaseCallback,写一个带有提示加载框的 SpotsCallBack,并且在里面处理提示框的显示和隐藏。
public abstract class SpotsCallBack<T> extends SimpleCallback<T> { private SpotsDialog mDialog; public SpotsCallBack(Context context){ super(context); initSpotsDialog(); } private void initSpotsDialog(){ mDialog = new SpotsDialog(mContext,"拼命加载中..."); } public void showDialog(){ mDialog.show(); } public void dismissDialog(){ mDialog.dismiss(); } public void setLoadMessage(int resId){ mDialog.setMessage(mContext.getString(resId)); } @Override public void onBeforeRequest(Request request) { showDialog(); } @Override public void onResponse(Response response) { dismissDialog(); } @Override public void onFailure(Request request, Exception e) { dismissDialog(); super.onFailure(request, e); } @Override public void onError(Response response, int code, Exception e) { dismissDialog(); } }所需要封装的都已经封装好了,现在就是要在 OkHttpHelper 中写好相应的 get 和 post 的调用方法,也就是函数。
public void get(String url,Map<String,Object> param,BaseCallback callback){ Request request = buildGetRequest(url,param); request(request,callback); } public void get(String url,BaseCallback callback){ get(url,null,callback); } public void post(String url,Map<String,Object> param, BaseCallback callback){ Request request = buildPostRequest(url,param); request(request,callback); }到这里就基本实现了我之前列举的功能了,如果还需要其他的功能的话,可以自行封装其他的。
已经封装好的 OkHttp 要实际运用起来,看下是不是优化了很多,在之前的文章《商城项目实战 | 6.1 OkHttp 的详细介绍 网络请求更加简单》中使用 OkHttp 来实现炫酷轮播广告,现在我们就用封装好的 OkHttp 来实现同样的功能。
下面是封装前的网络请求方法,这里使用的是 get 请求。
private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); switch (msg.what){ case INIT_SLIDER_TYPE: initSlider(); break; } } }; private void getBannerData() { String url ="http://112.124.22.238:8081/course_api/banner/query?type=1"; OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url(url) .build(); Call call = client.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Toast.makeText(getActivity(),e.getMessage().toString(),Toast.LENGTH_SHORT).show(); } @Override public void onResponse(Call call, Response response) throws IOException { if(response.isSuccessful()){ Type type = new TypeToken<List<BannerInfo>>(){}.getType(); Gson gson = new Gson(); List<BannerInfo> list= gson.fromJson(response.body().string(),type); for (BannerInfo bannerInfo:list) { listBanner.add(bannerInfo); } handler.sendEmptyMessage(INIT_SLIDER_TYPE); }else { Toast.makeText(getActivity(),"IOException",Toast.LENGTH_SHORT).show(); } } }); }下面是封装后的网络请求处理。
private void getBannerData() { String url ="http://112.124.22.238:8081/course_api/banner/query?type=1"; httpHelper.get(url, new SpotsCallBack<List<BannerInfo>>(getActivity()){ @Override public void onSuccess(Response response, List<BannerInfo> banners) { listBanner = banners; initSlider(); } @Override public void onError(Response response, int code, Exception e) { Toast.makeText(getActivity(),code+e.toString(),Toast.LENGTH_SHORT).show(); } @Override public void onFailure(Request request, Exception e) { super.onFailure(request, e); Toast.makeText(getActivity(),e.toString(),Toast.LENGTH_SHORT).show(); } }); }还是使用的 get 请求方法,但是感觉代码明显简洁了不少,也更加的规整,同时调用也方便了很多。
最后还是运行下代码,获取到效果图。
OkHttp 更多的用法可以参考 Github 源码 。