Retrofit2+Rxjava2+Rxandroid+okhttp3+Lifecycle 的MVP網絡框架,精簡Google官方AAC框架,實現APP生命週期的管理

一.介紹

目前使用較爲廣泛的網絡請求框架 MVP+Retrofit2+okhttp3+Rxjava2,我於2017年也加入了使用行列,在網上找了許多案例,實際項目開發中解決了一些所謂的坑,總結了些內容與大家共享一下。

1.什麼是MVP?

在圖中有三個模塊view(界面),presenter(控制層),model(數據源)。他們在這個項目中中擔任什麼角色呢?

2. MVP運行的過程

  • Model: 數據層,負責與網絡層和數據庫層的邏輯交互。
  • View: UI層,顯示數據, 並向Presenter報告用戶行爲。
  • Presenter: 從Model拿數據,應用到UI層,管理UI的狀態,響應用戶的行爲。
  •  用戶在view層告訴presenter我要數據
  • presenter告訴model我要數據
  • model訪問網絡得到了數據再通知presenter給你我取到的數據
  • presenter 處理好數據 再把數據傳遞給view
  • 最後view將拿到的數據顯示出來給用戶觀看

3.MVP和MVC的區別

 

     MVC首先就是理解比較容易,技術含量不高,這對開發和維護來說成本較低也易於維護與修改。表現層與業務層分離各司其職,對開發來說很有利,但是MVC的每個構件在使用之前都需要經過徹底的測試,代碼難以複用。

     在MVP裏,Presenter完全把Model和View進行了分離,主要的程序邏輯在Presenter裏實現,而且Presenter與具體的view是沒有一點關聯的,而是通過定義好的接口進行交互,從而使得在變更View的同時可以保持Presenter不變,可以複用。

       在MVP模式裏,View只應該有簡單的Set/Get方法,用戶輸入和設置顯示的內容,除此不應該有更多的內容,絕不允許直接訪問Model,這就是與MVC最大的不同之處。

二.框架的搭建

1.搭建框架的依賴

採用Retrofit2+Rxjava2+Rxandroid+okhttp3 搭建網絡請求框架

implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'

2.創建工具類:RetrofitUtils、OkHttp3Utils

RetrofitUtils和OkHttp3Utils的特性:

  1. 使用okhttp3作爲請求接口;
  2. 以觀察者模式創建實例;
  3. 使用gson作爲數據轉換器;
  4. 添加各種攔截器,如日誌攔截,請求頭攔截,請求參數攔截等等
  5. 開啓數據緩存,無網絡時可從緩存讀取數據;
  6. 輔助類靜態方法獲取OkHttp3Utils實例。

詳細代碼如下:

RetrofitUtils工具類封裝

封裝可以設置多個BaseUrl,應對項目對接多業務方的需求

public abstract class RetrofitUtils {
    private Retrofit mRetrofit = null;
    private Retrofit mRetrofit2 = null;
    private OkHttpClient mOkHttpClient;

    /**
     * 獲取Retrofit對象
     *
     * @return
     */
    public Retrofit getRetrofit() {
        if (null == mRetrofit) {
            if (null == mOkHttpClient) {
                OkHttp3Utils okHttp3Utils = new OkHttp3Utils();
                mOkHttpClient = okHttp3Utils.getOkHttpClient();
            }
            mRetrofit = new Retrofit.Builder()
                    .baseUrl(BaseUrlUtil.BaseServiceUrl)
                    .addConverterFactory(new NullOnEmptyConverterFactory())
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .client(mOkHttpClient)
                    .build();
        }
        return mRetrofit;
    }

    /**
     * 獲取Retrofit對象
     *這個主要是爲了應對多個BaseUrl而準備的
     * @return
     */
    public Retrofit getRetrofit2() {
        if (null == mRetrofit2) {
            if (null == mOkHttpClient) {
                OkHttp3Utils okHttp3Utils = new OkHttp3Utils();
                mOkHttpClient = okHttp3Utils.getOkHttpClient();
            }
            mRetrofit2 = new Retrofit.Builder()
                    .baseUrl(BaseUrlUtil.BaseServiceUrl2)
                    .addConverterFactory(new NullOnEmptyConverterFactory())
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .client(mOkHttpClient)
                    .build();
        }
        return mRetrofit2;
    }


    public class NullOnEmptyConverterFactory extends Converter.Factory {
        @Override
        public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
            final Converter<ResponseBody, ?> delegate = retrofit.nextResponseBodyConverter(this, type, annotations);
            return new Converter<ResponseBody, Object>() {
                @Override
                public Object convert(ResponseBody body) throws IOException {
                    if (body.contentLength() == 0) return null;
                    return delegate.convert(body);
                }
            };
        }
    }

}

 OkHttp3Utils工具類封裝

自定義攔截器,可以按照自己的需求設置請求頭的參數,同時對cookies做了自動化管理,對cookers管理更方便

public class OkHttp3Utils {
    private OkHttpClient mOkHttpClient;
    Activity activity = AppManager.topActivity();
    private Handler updateHandler = new Handler() {
        @Override
        public void dispatchMessage(Message msg) {
            super.dispatchMessage(msg);
            if (msg.what == 401) {
                //401 token失效
                if (activity != null && !activity.isDestroyed()) {
                    try {
                        PreferenceHelper.write(PreferenceHelper.DEFAULT_FILE_NAME, AppConfig.PREFER_TOKEN_TAG, "");
                        DialogView dialogView = new DialogView(activity, 180, 180, R.layout.my_dialog, R.style.dialog) {
                            @Override
                            public void isdismiss(int tag) {
                                if (tag == DialogView.CANCEL_BUTTON_CLICK) {

                                }
                            }
                        };
                        dialogView.showdialog2("溫馨提示", "登錄失效,請重新登錄", "去登錄", "");
                    } catch (Exception es) {
                        es.printStackTrace();
                    }
                }
            } else if (msg.what == 300) {
                Toast.makeText(activity, "暫無網絡", Toast.LENGTH_SHORT).show();
            }
        }
    };

    //設置緩存目錄
    private File cacheDirectory = new File(MyApplication.getInstance().getApplicationContext().getCacheDir().getAbsolutePath(), "MyCache");
    private Cache cache = new Cache(cacheDirectory, 10 * 1024 * 1024);

    /**
     * 獲取OkHttpClient對象
     *
     * @return
     */
    public OkHttpClient getOkHttpClient() {

        if (null == mOkHttpClient) {

            //同樣okhttp3後也使用build設計模式
            mOkHttpClient = new OkHttpClient.Builder()
                    //添加攔截器
                    .addInterceptor(new MyIntercepter())
                    //設置一個自動管理cookies的管理器
                    .cookieJar(new CookiesManager())
                    //添加網絡連接器
//                    .addNetworkInterceptor(new CookiesInterceptor(MyApplication.getInstance().getApplicationContext()))
                    //設置請求讀寫的超時時間
                    .connectTimeout(30, TimeUnit.SECONDS)
                    .writeTimeout(30, TimeUnit.SECONDS)
                    .readTimeout(30, TimeUnit.SECONDS)
                    .cache(cache)//設置緩存
                    .retryOnConnectionFailure(true)//自動重試
                    .build();
        }
        return mOkHttpClient;
    }

    /**
     * 攔截器
     */
    private class MyIntercepter implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();

            if (!isNetworkReachable(MyApplication.instance.getApplicationContext())) {
                updateHandler.sendEmptyMessage(300);
                request = request.newBuilder()
                        .cacheControl(CacheControl.FORCE_CACHE)//無網絡時只從緩存中讀取
                        .build();
            }
            Request.Builder RequestBuilder = request.newBuilder();
            Request build;

            build = RequestBuilder
                    .removeHeader("User-Agent")
                    .addHeader("User-Agent", getUserAgent())
                    .addHeader("Authorization", "")
                    .build();

            Response response = chain.proceed(build);
            int code = response.code();
            //對個別鏈接地址做處理
            HttpUrl url = response.request().url();
            System.out.println("我的網址"+url);
            updateHandler.sendEmptyMessage(code);
            if (code == 401) {
                //跳轉到登錄頁面
                updateHandler.sendEmptyMessage(401);
            } else if (code == 402) {
                //跳轉到開戶審覈中界面
                updateHandler.sendEmptyMessage(402);
            } else if (code == 403) {
                //跳轉到開戶界面
                updateHandler.sendEmptyMessage(403);
            }
            return response;
        }
    }

    private static String getUserAgent() {
        String userAgent = "";
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            try {
                userAgent = WebSettings.getDefaultUserAgent(MyApplication.getInstance().getApplicationContext());
            } catch (Exception e) {
                userAgent = System.getProperty("http.agent");
            }
        } else {
            userAgent = System.getProperty("http.agent");
        }
        StringBuffer sb = new StringBuffer();
        for (int i = 0, length = userAgent.length(); i < length; i++) {
            char c = userAgent.charAt(i);
            if (c <= '\u001f' || c >= '\u007f') {
                sb.append(String.format("\\u%04x", (int) c));
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    /**
     * 自動管理Cookies
     */
    private class CookiesManager implements CookieJar {
        private final PersistentCookieStore cookieStore = new PersistentCookieStore(MyApplication.getInstance().getApplicationContext());

        @Override
        public void saveFromResponse(HttpUrl url, List<Cookie> cookies) {
            if (cookies != null && cookies.size() > 0) {
                for (Cookie item : cookies) {
                    cookieStore.add(url, item);
                }
            }
        }

        @Override
        public List<Cookie> loadForRequest(HttpUrl url) {
            List<Cookie> cookies = cookieStore.get(url);
            return cookies;
        }
    }

    /**
     * 判斷網絡是否可用
     *
     * @param context Context對象
     */
    @RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE)
    public Boolean isNetworkReachable(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo current = cm.getActiveNetworkInfo();
        if (current == null) {
            return false;
        }
        return (current.isAvailable());
    }
}

 線程切換操作的封裝

public class BaseNetWork extends RetrofitUtils{

    /**https://github.com/r17171709/Retrofit2Demo
     * 插入觀察者
     * @param observable
     * @param observer
     * @param <T>
     */
    public  <T> void setSubscribe(Observable<T> observable, Observer<T> observer) {
        observable.subscribeOn(Schedulers.io())
                .subscribeOn(Schedulers.newThread())//子線程訪問網絡
                .observeOn(AndroidSchedulers.mainThread())//回調到主線程
                .subscribe(observer);
    }
}

下面就是實體接口的調用

public class UserNetWork extends BaseNetWork {

    protected final NetService service = getRetrofit().create(NetService.class);

    private interface NetService {

        //獲取首頁輪播圖
        @GET("api/AppPubilc/get_lunbotu")
        Observable<LunBoTuEntity> toGetLunBoTuEntity();

    }

    //首頁輪播圖
    public void toGetLunBoTuEntity(Observer<LunBoTuEntity> observer) {
        setSubscribe(service.toGetLunBoTuEntity(), observer);
    }

}

以上就是Retrofit2+Rxjava2+Rxandroid+okhttp3的高度封裝的網絡框架,自定義攔截器可以攔截請求地址,動態添加請求頭裏的參數,同時對網絡請求響應code碼做相應的操作。面對一個項目對接多個業務方,存在多個BaseUrl,該網絡框架封裝了可以設置多個BaseUrl的。

那麼Retrofit2網絡框架的網絡框架搭建完了,下面來看一下MVP架構的設計吧,不要走開!

下面是項目的整體架構圖

創建BaseView基類,用於添加自定義回調,根據需求可做擴展,此處只封裝了些最爲常用的方法

public interface BaseView {

    void showLoadingDialog(String msg);

    void dismissLoadingDialog();
    /**
     * 顯示錯誤信息
     *
     * @param msg
     */
    void showError(String msg);
    /**
     * 錯誤碼
     */
    void onErrorCode(BaseModel model);
}

 創建Presenter基類,提供M層和V層通訊橋樑

public interface BasePresenter {
    //默認初始化
    void start();

    //Activity關閉把view對象置爲空
    void detach();

    //將網絡請求的每一個disposable添加進入CompositeDisposable,再退出時候一併註銷
    void addDisposable(Disposable subscription);

    //註銷所有請求
    void unDisposable();

}

創建一個PresenterImpl,用於統一處理網絡請求的生命週期,在activity退出時統一註銷觀察者模式,解綁觀察者的情況下調用unDisposable()統一解綁,防止Rx造成的內存泄漏。

/**
 * 總控制層
 * @param <V>
 */
public abstract class BasePresenterImpl<V extends BaseView> implements BasePresenter {
    protected V view;
    public BasePresenterImpl(V view) {
        this.view = view;
        start();
    }

    @Override
    public void detach() {
        this.view = null;
        unDisposable();
    }

    @Override
    public void start() {

    }


    //將所有正在處理的Subscription都添加到CompositeSubscription中。統一退出的時候註銷觀察
    private CompositeDisposable mCompositeDisposable;

    /**
     * 將Disposable添加
     *
     * @param subscription
     */
    @Override
    public void addDisposable(Disposable subscription) {
        //csb 如果解綁了的話添加 sb 需要新的實例否則綁定時無效的
        if (mCompositeDisposable == null || mCompositeDisposable.isDisposed()) {
            mCompositeDisposable = new CompositeDisposable();
        }
        mCompositeDisposable.add(subscription);
    }

    /**
     * 在界面退出等需要解綁觀察者的情況下調用此方法統一解綁,防止Rx造成的內存泄漏
     */
    @Override
    public void unDisposable() {
        if (mCompositeDisposable != null) {
            mCompositeDisposable.dispose();
        }
    }

}

創建實體類基類,統一處理後臺接口返回的數據,做統一處理

public class TradeSimpleResult implements Serializable{

  /**
   * Success : false
   * StatusCode : 500
   * Message : 處理失敗
   * ErrorInfo : {"ErrorMessage":"請輸入真實的身份證姓名信息","ErrorCode":"-1"}
   */

  private boolean Success;
  private int StatusCode;
  private String Message;
  private ErrorInfoBean ErrorInfo;

  public boolean isSuccess() {
    return Success;
  }

  public void setSuccess(boolean Success) {
    this.Success = Success;
  }

  public int getStatusCode() {
    return StatusCode;
  }

  public void setStatusCode(int StatusCode) {
    this.StatusCode = StatusCode;
  }

  public String getMessage() {
    return Message;
  }

  public void setMessage(String Message) {
    this.Message = Message;
  }

  public ErrorInfoBean getErrorInfo() {
    return ErrorInfo;
  }

  public void setErrorInfo(ErrorInfoBean ErrorInfo) {
    this.ErrorInfo = ErrorInfo;
  }

  public static class ErrorInfoBean {
    /**
     * ErrorMessage : 請輸入真實的身份證姓名信息
     * ErrorCode : -1
     */

    private String ErrorMessage;
    private String ErrorCode;

    public String getErrorMessage() {
      return ErrorMessage;
    }

    public void setErrorMessage(String ErrorMessage) {
      this.ErrorMessage = ErrorMessage;
    }

    public String getErrorCode() {
      return ErrorCode;
    }

    public void setErrorCode(String ErrorCode) {
      this.ErrorCode = ErrorCode;
    }
  }
}

好了,以上就是MVP架構的搭建以及接口返回數據的統一處理。代碼比較多,但是前後邏輯是很連貫的。那麼下面就來做一個實際的接口請求看看效果。

public class MainActivity extends LifecycleBaseActivity<TestContact.presenter> implements TestContact.view {

    private TextView textView;
    private HashMap<Object, Object> map;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        map = new HashMap<>();
        initView();
        initData();

    }

    private void initData() {
        presenter.getData(map, "first");
    }

    private void initView() {
        textView = (TextView) findViewById(R.id.main_text);
    }

    /**
     * 初始化presenter
     *
     * @return 對應的presenter
     */
    @Override
    public TestContact.presenter initPresenter() {
        return new TestPresenter(this, MainActivity.this);
    }

    /**
     * 設置數據
     * 刷新界面
     *
     * @param lunBoTuEntity 數據源
     */
    @Override
    public void setData(LunBoTuEntity lunBoTuEntity, String tag) {
        if ("LunBoTu".equals(tag)) {
            String imageUrl = lunBoTuEntity.getResult().getList().get(0).getImageUrl();
            System.out.println("圖片地址:" + imageUrl);
        }
    }

    @Override
    public void ErrorData(Throwable e) {

    }

    @Override
    public void showLoadingDialog(String msg) {
        textView.setText(msg);
    }

    @Override
    public void dismissLoadingDialog() {

    }

}

以上的LifecycleBaseActivity 和 LifecycleBaseFragment大家可以在下面的鏈接地址裏面去看,這個是Goolge官方架構AAC(Android Architecture Component)的生命週期管理框架,Lifecycle類持有Activity 或 Fragment等組件的生命週期信息,並且允許其他對象觀察這些信息。Lifecycle內部使用了兩個枚舉來跟蹤其關聯組件的生命週期狀態:Event和State。祥見下面分析。可以通過調用Lifecycle類的 addObserver() 方法來添加觀察者,如下

  getLifecycle().addObserver(new TestLifeCycle());

我在LifecycleBaseActivity做了部分處理,使用起來更加的便捷、易懂。

現在Retrofit2+Rxjava2+Rxandroid+okhttp3+Lifecycle 的MVP網絡框架,結合了Google官方AAC框架,實現APP生命週期的管理整體架構就做好了,文章裏涉及帶網絡框架、MVP架構和生命週期的管理,那麼一套完整的App框架就搭建好了。

2019.05.09功能新增:

1.新增token過期自動刷新token,刷新後再請求一次接口的功能

github地址: https://github.com/zengweitao/Treasure

簡書地址:https://www.jianshu.com/p/ac0eeadb6151

源碼下載地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章