1. 程式人生 > >Android 基於Retrofit+Rxjava搭建的簡單易用的網路架構

Android 基於Retrofit+Rxjava搭建的簡單易用的網路架構

裝逼開始之前,為大家推薦兩篇文章,用來更好的學習Retrofit和Rxjava。在這裡我們要感謝網際網路裝逼行業勇於獻身,甘於奉獻的的大嬸們。我僅代表個人,給您們跪舔了。

---------------這是分割線---------------

首先配置支援的gradle檔案:

    compile 'io.reactivex:rxjava:1.1.3'
    compile 'io.reactivex:rxandroid:1.1.0'
    compile 'com.squareup.retrofit2:retrofit:2.0.1'
    compile 'com.squareup.retrofit2:converter-gson:2.0.1'
    compile 'com.squareup.retrofit2:adapter-rxjava:2.0.1'

截止至本文發表前,上述支援庫都是最新版本。

貼出Retrofit官網的GET請求示例:GitHubService介面

public interface GitHubService {
  @GET("users/{user}/repos")
  Call<List<Repo>> listRepos(@Path("user") String user);
}

網路請求部分程式碼為:

        String baseUrl = "https://*******";

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        GitHubService service = retrofit.create(GitHubService.class);
        Call<List<XXXEntity>> repos = service.listRepos("octocat");
        repos.enqueue(new Callback<XXXEntity>() {
            @Override
            public void onResponse(Call<XXXEntity> call, Response<XXXEntity> response) {
                Log.i("onResponse",response.body().toString());
            }

            @Override
            public void onFailure(Call<XXXEntity> call, Throwable t) {
                Log.i("onResponse",t.getMessage());
            }
        });
baseUrl 為網路請求地址

addConverterFactory(GsonConverterFactory.create())為設定json解析方式為Gson。

retrofit.create採用動態代理的方式獲取GitHubService物件,並通過Callback獲取返回的資料。

---------------當Retrofit與Rxjava相遇----------------

Retrofit.Builder需要呼叫

.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
方法,表示用Rxjava做回撥,即Rxjava所講的觀察者模式。那麼網路請求介面就需要改成如下方式:
Observable<List<Repo>> listRepos(@Path("user") String user);
網路請求部分的程式碼則更改為:
        String baseUrl = "https://*******";

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        service.listRepos("octocat")
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<XXXEntity>() {
                    @Override
                    public void onCompleted() {
                        Log.i("onCompleted","onCompleted");
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.i("onError",e.toString());
                    }

                    @Override
                    public void onNext(XXXEntity movieEntity) {
                        Log.i("onCompleted",movieEntity.toString());
                    }
                });
如果仔細閱讀過上述兩篇文章,不難理解這部分程式碼的含義,由於篇幅原因,這裡就不在重複了。

當然如此這般的結合,無法滿足對程式碼有深度潔癖的騷年們。接下來,在此基礎上,對程式碼做整體的封裝。

專案目錄結構如下:

首先封裝網路請求類HTTPHelper.java

    /**
     * 這一部分配置常量,可以抽取出常量類
     */
    private static final String BASE_PATH = "http://www.weather.com.cn/";//訪問的地址
    private static final long DEFAULT_TIMEOUT = 5000;//預設超時時間(毫秒)


    private Retrofit mRetrofit;
    private INetService mNetService;

    private HTTPHelper(){
        OkHttpClient.Builder okHttpClient = new OkHttpClient.Builder();
        okHttpClient.addInterceptor(new HTTPInterceptor());
        okHttpClient.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS);
        mRetrofit = new Retrofit.Builder()
                .client(okHttpClient.build())
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .baseUrl(BASE_PATH)
                .build();

        mNetService = mRetrofit.create(INetService.class);

    }

    /**
     * 單例控制器
     */
    private static class SingletonHolder{
        private static final HTTPHelper INSTANCE = new HTTPHelper();
    }

    /**
     * 獲取單例物件
     * @return
     */
    public static HTTPHelper getInstance(){
        return SingletonHolder.INSTANCE;
    }
我們需要將網路請求回來的字串轉換成javaBean物件,所以引入Rxjava的map轉換機制。程式碼如下:
    private class HttpResultFunc<T> implements Func1<IModel, T> {
        @Override
        public T call(IModel iModel) {
            if (iModel == null){
                try {
                    throw new Exception("result model is null");
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return (T)iModel;
        }
    }
到此,配置階段結束,下面將是Rxjava的非同步訂閱請求網路並返回操作。為了方便Activity、Fragment等介面處理資料,封裝Subscriber介面:
package demo.myframework.common;

import rx.Subscriber;

/**
 * @Author: lizhipeng
 * @Data: 16/4/12 下午4:17
 * @Description: 自定義調閱者以及結果監聽介面
 */
public class ResultSubscriber<T> extends Subscriber<T> {
    private int mRequestType;
    private OnResultListener<T> mListener = null;

    /**
     * 自定義訂閱,引數用來區分網路介面,以用來在不同介面操作過程中,處理不同的邏輯
     * @param requestType
     */
    public ResultSubscriber(int requestType) {
        this.mRequestType = requestType;
        mListener = new OnResultListener<T>() {
            @Override
            public void onStart(int requestType) {
            }
            @Override
            public void onCompleted(int requestType) {
            }
            @Override
            public void onError(Throwable e, int requestType) {
            }
            @Override
            public void onNext(T t, int requestType) {
            }
        };
    }

    @Override
    public void onStart() {
        mListener.onStart(mRequestType);
    }

    @Override
    public void onCompleted() {
        mListener.onCompleted(mRequestType);
    }

    @Override
    public void onError(Throwable e) {
        if (e != null){
            mListener.onError(e,mRequestType);
        }
    }

    @Override
    public void onNext(T t) {
        mListener.onNext(t,mRequestType);
    }

    /**
     * 設定訂閱監聽器
     * @param listener
     */
    public void setOnResultListener(OnResultListener listener){
        if (listener != null){
            mListener = listener;
        }
    }

    /**
     * 訂閱的監聽器
     * @param <T>
     */
    public interface OnResultListener<T> {
        /**
         * 網路請求訂閱開始
         */
        void onStart(int requestType);
        /**
         * 網路請求完成
         */
        void onCompleted(int requestType);
        /**
         * 網路請求錯誤
         */
        void onError(Throwable e,int requestType);
        /**
         * 處理請求結果
         */
        void onNext(T t,int requestType);
    }
}
已天氣訪問為例網路請求介面為:
package demo.myframework.interfaces;

import demo.myframework.model.WeatherModel;
import retrofit2.http.GET;
import retrofit2.http.Path;
import rx.Observable;

/**
 * @Author: lizhipeng
 * @Data: 16/4/12 下午2:57
 * @Description:  網路請求介面
 */
public interface INetService {
    @GET("data/cityinfo/{city_id}.html")
    Observable<WeatherModel> getWeather(@Path("city_id") String city);
}

HTTPHelper.java 中 網路請求資料方法的實現為:

    /**
     * 獲取網路資料的方法
     * @param cityId
     */
    public void getWeather(String cityId, int resultType, ResultSubscriber.OnResultListener listener){
        ResultSubscriber<IModel> subscriber = new ResultSubscriber<>(resultType);
        subscriber.setOnResultListener(listener);
        mNetService.getWeather(cityId)
                .map(new HttpResultFunc<WeatherModel>())
                .subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(subscriber);
    }
MainActivity實現OnResultListener介面,點選按鈕呼叫方法為
       HTTPHelper.getInstance().getWeather("101010300",CODE,MainActivity.this);
一下是activity的全部程式碼:
package demo.myframework.activity;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import demo.myframework.R;
import demo.myframework.common.ResultSubscriber;
import demo.myframework.http.HTTPHelper;
import demo.myframework.model.IModel;
import demo.myframework.model.WeatherModel;

public class MainActivity extends AppCompatActivity implements ResultSubscriber.OnResultListener<IModel>{
    private static final String TAG = "MainActivity";
    private static final int CODE = 1;

    private Button mButton;
    private TextView mTextView;



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initView();
        initData();
    }

    private void initData() {
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i(TAG,"onClick");
                HTTPHelper.getInstance().getWeather("101010300",CODE,MainActivity.this);
            }
        });
    }

    private void initView() {
        mButton = (Button) findViewById(R.id.button);
        mTextView = (TextView) findViewById(R.id.textview);
    }

    private void setResultSubscriber(){

    }

    /**
     * 網路請求前呼叫,通常顯示Progressialog
     * @param requestType
     */
    @Override
    public void onStart(int requestType) {
        Log.i(TAG,"onStart");
    }

    /**
     * 網路請求完成呼叫,通常銷燬Progressialog
     * @param requestType
     */
    @Override
    public void onCompleted(int requestType) {
        Log.i(TAG,"onCompleted");
    }

    /**
     * 網路請求錯誤後呼叫
     * @param e
     * @param requestType
     */
    @Override
    public void onError(Throwable e, int requestType) {
        Log.i(TAG,"onError");
    }

    /**
     * onNext 方法中處理請求下來的資料
     * @param iModel
     * @param requestType
     */
    @Override
    public void onNext(IModel iModel, int requestType) {
        Log.i(TAG,"onNext");
        if (requestType == CODE){
            mTextView.setText(((WeatherModel)iModel).getWeatherinfo().toString());
        }
    }
}
另為了方便測試聯調,實現OKhttp的過濾介面。一下程式碼分別為過濾器和javaBean的實現:

HTTPInterceptor.java

package demo.myframework.http;

import android.util.Log;

import java.io.IOException;

import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

/**
 * @Author: lizhipeng
 * @Data: 16/4/12 下午5:19
 * @Description: 定義http攔截器,用於設定http協議和日誌除錯
 */
public class HTTPInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        //封裝headers
        Request request = chain.request().newBuilder()
                .addHeader("Content-Type", "application/json") //新增請求頭資訊
                .build();
//        Headers headers = request.headers();
        String requestUrl = request.url().toString(); //獲取請求url地址
        String methodStr = request.method(); //獲取請求方式
        RequestBody body = request.body(); //獲取請求body
        String bodyStr = (body==null?"":body.toString());
        //列印Request資料
        Log.i("HTTP-Interceptor","requestUrl=====>"+requestUrl);
        Log.i("HTTP-Interceptor","requestMethod=====>"+methodStr);
        Log.i("HTTP-Interceptor","requestBody=====>"+body);

        Response response = chain.proceed(request);
        return response;
    }
}

WeatherModel.java

package demo.myframework.model;

/**
 * @Author: lizhipeng
 * @Data: 16/4/12 下午3:16
 * @Description:  天氣資訊模板
 */
public class WeatherModel implements IModel{

    /**
     * city : 朝陽
     * cityid : 101010300
     * temp1 : -2℃
     * temp2 : 16℃
     * weather : 晴
     * img1 : d0.gif
     * img2 : n0.gif
     * ptime : 18:00
     */

    private WeatherinfoBean weatherinfo;

    public WeatherinfoBean getWeatherinfo() {
        return weatherinfo;
    }

    public void setWeatherinfo(WeatherinfoBean weatherinfo) {
        this.weatherinfo = weatherinfo;
    }

    public static class WeatherinfoBean {
        private String city;
        private String cityid;
        private String temp1;
        private String temp2;
        private String weather;
        private String img1;
        private String img2;
        private String ptime;

        public String getCity() {
            return city;
        }

        public void setCity(String city) {
            this.city = city;
        }

        public String getCityid() {
            return cityid;
        }

        public void setCityid(String cityid) {
            this.cityid = cityid;
        }

        public String getTemp1() {
            return temp1;
        }

        public void setTemp1(String temp1) {
            this.temp1 = temp1;
        }

        public String getTemp2() {
            return temp2;
        }

        public void setTemp2(String temp2) {
            this.temp2 = temp2;
        }

        public String getWeather() {
            return weather;
        }

        public void setWeather(String weather) {
            this.weather = weather;
        }

        public String getImg1() {
            return img1;
        }

        public void setImg1(String img1) {
            this.img1 = img1;
        }

        public String getImg2() {
            return img2;
        }

        public void setImg2(String img2) {
            this.img2 = img2;
        }

        public String getPtime() {
            return ptime;
        }

        public void setPtime(String ptime) {
            this.ptime = ptime;
        }

        @Override
        public String toString() {
            return "WeatherinfoBean{" +
                    "city='" + city + '\'' +
                    ", cityid='" + cityid + '\'' +
                    ", temp1='" + temp1 + '\'' +
                    ", temp2='" + temp2 + '\'' +
                    ", weather='" + weather + '\'' +
                    ", img1='" + img1 + '\'' +
                    ", img2='" + img2 + '\'' +
                    ", ptime='" + ptime + '\'' +
                    '}';
        }
    }

}
註釋已經在程式碼中詳細說明,就不做太多解釋了,著重說一點,ResultSubscriber.java中的mRequestType是用來在連續多次請求網路介面時,需要在介面中做不同的處理而區分的屬性。使用時,最好也為每個介面配置為對應的唯一值。

如果你有更好的實現方式,請與我聯絡。