本文為菜鳥窩作者劉婷的連載。”商城項目實戰”系列來聊聊仿”京東淘寶的購物商城”如何實現。
140套Android優秀開源項目源碼,領取地址:http://mp.weixin.qq.com/s/afPGHqfdiApALZqHsXbw-A
或歡迎勾搭運營小姐姐(微信 id:BT474849)免費領取哦~
在前面的文章《商城項目實戰 | 6.1 OkHttp 的詳細介紹 網絡請求更加簡單》中已經詳細介紹了 OkHttp 的基本屬性和使用方法了,OkHttp 開源框架給開發者帶來了很多的便利,使用非常方便,但是在項目中網絡請求一般到處都要使用到,如果總是重復相同的代碼,首先代碼不夠簡潔,其次效率也變低了不少。代碼的簡潔性是一個好的開發者所不可缺少的開發特點,如何使得代碼更為的簡潔呢?那就要學會封裝了,本文主要是針對 OkHttp 框架使用的封裝,學會了這個,封裝其他的也都不是問題了。
封裝的意義和好處
1. 封裝的概念
封裝從字面上來理解就是包裝的意思,專業點就是信息隱藏,是指利用抽象數據類型將數據和基于數據的操作封裝在一起,使其構成一個不可分割的獨立實體,數據被保護在抽象數據類型的內部,盡可能地隱藏內部的細節,只保留一些對外接口使之與外部發生聯系。系統的其他對象只能通過包裹在數據外面的已經授權的操作來與這個封裝的對象進行交流和交互。封裝是面向對象的三大特征之一,就是將類的狀態信息隱藏在類的內部,不允許外部程序直接訪問,而通過該類提供的方法來實現對隱藏信息的操作和訪問。
2. 封裝的好處
- 1.良好的封裝能夠減少耦合。
- 2.不必關心具體的實現。
- 3.控制用戶對類的修改和訪問數據的程度,提高安全性。
- 4.可以方便的加入存取控制語句,限制不合理操作。
- 5.代碼更加容易被理解和維護。
OkHttp 的封裝
已經了解了封裝的意義和所帶來的好處,更加要把封裝應用到自己的代碼中去,讓代碼更為得具有簡潔性和易維護性,下面開始 OkHttp 的封裝。
1. 封裝 OkHttp 要實現什么
在做一件事情之前,都要考慮為什么要這樣做,在寫一個項目之前,都要考慮這個項目的需求,在寫一行代碼之前,所要想的是要實現怎么的功能,同樣的,在封裝之前,要思考封裝可以實現什么,可以先列舉出來,下面是我列舉的希望封裝 OkHttp 能夠實現的功能。
- 1.OkHttpClient 可以不重復寫,直接簡單調用 Get 和 Post 方法,尤其是 post 調用時傳遞參數希望可以一步搞定。
- 2.返回的數據可以直接拿來使用,不要自己再過多的處理和序列化。
- 3.有些時候加載數據希望顯示提示加載框,數據加載完后顯示框 dismiss,但是有的時候又不希望提示加載框顯示出來。
- 4.網絡請求失敗后,可以提示錯誤信息,同時對于請求成功和請求失敗后的操作可以自己再擴展。
按照這樣的想法,一步一步來實現 OkHttp 的封裝。
**2. 實現封裝 OkHttp **
在列舉的所要實現的功能中,希望可以返回的數據可以直接拿來使用,另外還要顯示加載的提示框,那么在封裝的 OkHttp 中一定需要對數據進行處理,比如 Json 類型的數據,我們就用 Gson 框架來處理了,而提示對話框的話,這里也使用一款開源的框架 SpotsDialog 。
2.1 添加 Gradle 依賴
因為使用的工具為 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'
}
2.2 封裝 Request
在使用 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;
}
2.3 封裝 Callback
前面已經寫好了 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) ;
}
2.4 對獲取的數據處理和序列化
請求之后就是要處理獲取到的數據了,如果是 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);
}
}
});
}
2.5 Handler 來幫忙
在使用網絡請求開線程的時候一定要注意一個問題,那就是對 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);
}
});
}
2.6 加入提示加載框
之前列舉的功能中希望有時候可以顯示加載框,有時候不顯示,所以這里就需要擴展下之前的 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();
}
}
2.7 寫好 get 和 post 的調用方法
所需要封裝的都已經封裝好了,現在就是要在 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
已經封裝好的 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 請求方法,但是感覺代碼明顯簡潔了不少,也更加的規整,同時調用也方便了很多。
效果圖
最后還是運行下代碼,獲取到效果圖。
[圖片上傳失敗...(image-d9fec5-1565145693331)]
效果圖和之前是一樣的,但是方法已經簡便了很多,封裝可以給我們帶來很多的便利,也讓我們更加靈活的使用 OkHttp 了。