RxJava+MVP+Retrofit+Dagger2+Okhttp大雜燴

hearsnow 8年前發布 | 21K 次閱讀 Retrofit Dagger RxJava Node.js 開發

“你并不滿足逡巡在我的海洋,渴望占據我一片高地,可我飄著白云的高地,還筑著城墻。–kurny ”

這幾年來android的網絡請求技術層出不窮,網絡請求從最初的HttpClient,HttpURLConnection到Volley,OkHttp,Retrofit。但是如果直接使用,每個網絡請求都會重復很多相同的代碼,這顯然不是一個老司機需要的。接下來我們就講講網絡請求封裝那點事。

主要利用以下技術點

- Retrofit 2 Retrofit官網

- OkHttp OkHttp官網

- RxJava RxJava官網

- Dagger2 Dagger2

- MVP開發模式 參考谷歌官方的MVP項目

下面來介紹一下知識點由于篇幅限制,只簡單介紹一下相關概念)并且講解一下如何封裝我們日常用的網絡請求框架。

1.Retrofit2入門其實相當簡單,官網上給了最基礎的用法,相信大家看一遍就會用了。這里介紹下Retrofit2和Okhttp的關系,以及retrofit的面向接口的設計。

Retrofit.Builder builder = new Retrofit.Builder();
        builder.client(okHttpClient)
                .baseUrl(ApiService.SERVER_URL)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
 ``` 

 Retrofit2實際上是對Okhttp做了一層封裝,把網絡請求都交給給了Okhttp,我們只需要通過簡單的配置就能使用retrofit來進行網絡請求。

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

 GitHubService service = retrofit.create(GitHubService.class);

 ```
 然后我們就可以愉快地 Call<List<Repo>> call = service.listRepos(user);接下來調用call.enqueue,產生兩個回調函數onResponse和onFailure,我們在這里做相應的處理。

 這里我推薦[鴻洋的 Retrofit2 完全解析 探索與okhttp之間的關系](http://blog.csdn.net/lmj623565791/article/details/51304204)


2.RxJava,一個在 Java VM 上使用可觀測的序列來組成異步的、基于事件的程序的庫。這個有點拗口,其實大家只要去理解訂閱者觀察者模式(Observables發出一系列事件,Subscribers處理這些事件),事件驅動,異步這幾個概念,然后再去看RxJava的語法,多敲多練,很快就能上手。

RxJava 的異步實現,是通過一種擴展的觀察者模式來實現的。從純Java的觀點來看,RxJava Observable類源自于經典的觀察者模式。
它添加了三個缺少的功能:

 - 生產者在沒有更多數據可用時能夠發出通知信號:onCompleted事件。
 - 生產者在發生錯誤時能夠發出通知信號:onError()事件。
 - RxJava Observables能夠組合而不是嵌套,從而避免開發者陷入回調的地獄。

那么我們什么時候使用觀察者模式(題外話)?

 - 當你的架構有兩個實體類,一個依賴另外一個,你想讓它們互不影響或者是獨立復用它們。
 - 當一個變化的對象通知那些與它自身變化相關聯的未知數量的對象時。
 - 當一個變化的對象通知那些無需推斷具體的對象是誰。

RxJava的觀察者模式:
 Observable (被觀察者)、 Observer (觀察者)、 subscribe (訂閱)。Observable 和 Observer 通過 subscribe() 方法實現訂閱關系,從而 Observable 可以在需要的時候發出事件來通知 Observer。

RxJava的回調方法主要有三個,onNext(),onError(),onCompleted()。
 - onNext() 對于Subscribler我們可以理解為接收數據。
 - onCompleted() 觀測的事件的隊列任務都完成了,當不再有onNext()發射數據時,onCompleted事件被觸發。
 - onError() 當事件異常時響應此方法,一旦此方法被觸發,隊列自動終止,不再發射任何數據。
  (其實onCompleted()和onError()我們可以理解為網絡請求框架中的onSuccess()和onError(),一個是服務器響應成功,一個是響應失敗,這兩個方法同時只有一個能夠被執行,onCompleted()和onError()同理,onNext()可以理解為客戶端接收數據,不同的是服務器必須一次性返回響應信息,而RxJava可以一個一個數據返回或者一次性返回整個列表之類的)

這里我推薦[扔物線](https://gank.io/post/560e15be2dca930e00da1083)和[大頭鬼的深入淺出RxJava序列](http://blog.csdn.net/lzyzsd/article/details/41833541)。

3.Dagger2,它的大名字想必都知道,但是否都用過它那就另當別論了。它是一個依賴注入框架,又叫控制反轉(IOC),方便我們在大型項目上解耦,各層對象的調用完全面向接口,更好地寫出單元測試,有利于我們對大型項目的維護。   
以前在我們的項目中,引用其它類中中的方法,我們肯定是A a = new A();a.doSomething();如果這樣的話我們在寫單元測試的時候還要保證A實例有沒有初始化成功,同時代碼中也耦合了A這個類。引入Dagger2后,我們有Inject這個方法負責把A類注入到某個Activity中。同時我們也要理解這里面的@Commponent(負責連接某個類和Activity的連接),@Module(生產各個要注入的實例),@Provides(對外提供實例方法的注解),@Scope and @Singleton(劃分作用域的,標記當前對象使用范圍)這些概念,這些在下面的框架的封裝中都會提現出來,希望大家不要從入門到放棄。
這里我推薦[泡在網上的日子詳解Dagger2](http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0519/2892.html)

4.MVP模式:相信大家很多都是MVC模式過來的,那時我們的在Fragment和Activity中,不僅負責了View試圖層,還在里面寫了一堆業務邏輯,網絡請求數據,一個復雜的類里面上千行代碼,再加上一些別人寫的一些不優雅的代碼,我的天吶,根本沒法寫單元測試,誰維護誰頭疼,總有一種想推翻重寫的想法,但那是不現實的,于是MVP模式應運而生,具體大家參考[谷歌的官方demo](https://github.com/googlesamples/android-architecture/tree/todo-mvp),看一遍就都明白了,比我三言兩語講的清楚。

上面廢話太多,下面來進入封裝階段。


---------------------------------------  
先來看一下項目結構:

   ![Alt text](http://ww3.sinaimg.cn/large/9e17bee5gw1f9yw8xvk8bj20l20r0n1t.jpg =500x700)   
   項目結構看起來還是很清晰的,data包下面的local和remote分別是處理本地存儲和遠程獲取數據的,PreferenceManger也可以負責數據持久化處理,di包下面的主要負責對okhttp的攔截處理以及生產響應的okhttp模塊(主要是應用dagger2)。
   下面關于如何封裝我們來說個5毛錢的。  
   1.初始化retrofit,添加對RxJava的適配  

   ```
   @Provides
    @Singleton
    public Retrofit provideRestAdapter(OkHttpClient okHttpClient) {
        Retrofit.Builder builder = new Retrofit.Builder();
        builder.client(okHttpClient)
                .baseUrl(ApiService.SERVER_URL)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create());
        return builder.build();
    }
   ```  
   2.Service接口的處理

   ```
   public interface ApiService {
    String SERVER_URL = "http://127.0.0.1:3000/";

    @FormUrlEncoded
    @POST("/api/v1/authproject/login")
    Observable<BaseResponse<User>> login(@Field("phone") String username, @Field("password") String password);

    @FormUrlEncoded
    @POST("/api/v1/authproject/{id}/modify_activity")
    Observable<BaseResponse<CommonInfo>> modifyActivity(@Path("id")String id,@Field("user_id") String user_id, @Field("access_token") String access_token);

    @GET("/api/v1/authproject/{id}")
    Observable<BaseResponse<ActivityInfo>> getActivityInfo(@Path("id")String id,String user_id,@Query("access_token") String access_token);

}

   ```
   這個可以具體參考retrofit的官方文檔

  3.回調函數

   ```
   public interface SimpleCallback<T> {
    void onStart();
    void onNext(T t);
    void onComplete();
}


   ```
   onStart主要是發起請求時,onNext()是請求有結果之前,onComplete()是請求完成。


   4.數據統一請求分發


    public class BaseResponseFunc<T> implements Func1<BaseResponse<T>, Observable<T>> {


    @Override
    public Observable<T> call(BaseResponse<T> tBaseResponse) {
        //遇到非200錯誤統一處理,將BaseResponse轉換成您想要的對象
        if (tBaseResponse.getStatus_code() != 200) {
            return Observable.error(new Throwable(tBaseResponse.getStatus_msg()));
        }else{
            return Observable.just(tBaseResponse.getData());
        }
    }
}


   5.json數據統一返回格式

   ```
   public class BaseResponse<T> {

    private int status_code;
    private String status_msg;
    private T data;


    public int getStatus_code() {
        return status_code;
    }

    public void setStatus_code(int status_code) {
        this.status_code = status_code;
    }

    public String getStatus_msg() {
        return status_msg;
    }

    public void setStatus_msg(String status_msg) {
        this.status_msg = status_msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
   ```
   6.自定義dialog,錯誤提示,取消網絡請求,回調結果由ExceptionSubscriber統一處理

   ```
  public class ExceptionSubscriber<T> extends Subscriber<T> implements ProgressCancelListener{

    private SimpleCallback<T> simpleCallback;
    private Application application;

    private ProgressDialogHandler mProgressDialogHandler;

    private Context context;

    private Boolean isShow = true;


    public ExceptionSubscriber(SimpleCallback simpleCallback, Application application, Context context, Boolean isShow) {
        this.simpleCallback = simpleCallback;
        this.application = application;
        this.context = context;

        mProgressDialogHandler = new ProgressDialogHandler(context
                , this, true);
        this.isShow = isShow;
    }

    public ExceptionSubscriber(SimpleCallback simpleCallback, Application application, Context context) {
        this.simpleCallback = simpleCallback;
        this.application = application;
        this.context = context;

        mProgressDialogHandler = new ProgressDialogHandler(context
                , this, true);
    }


    private void showProgressDialog() {
        if (mProgressDialogHandler != null) {
            mProgressDialogHandler.obtainMessage(ProgressDialogHandler.SHOW_PROGRESS_DIALOG).sendToTarget();
        }
    }

    private void dismissProgressDialog() {
        if (mProgressDialogHandler != null) {
            mProgressDialogHandler.obtainMessage(ProgressDialogHandler.DISMISS_PROGRESS_DIALOG).sendToTarget();
            mProgressDialogHandler = null;
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        if (simpleCallback != null)
            simpleCallback.onStart();
        if (isShow)
            showProgressDialog();
    }

    @Override
    public void onCompleted() {
        if (simpleCallback != null)
            simpleCallback.onComplete();
        dismissProgressDialog();

    }

    @Override
    public void onError(Throwable e) {
        try {
            System.out.println("-------"+e.getMessage());
            e.printStackTrace();
            if (e instanceof SocketTimeoutException) {
                Toast.makeText(application, "網絡中斷,請檢查您的網絡狀態", Toast.LENGTH_SHORT).show();
            } else if (e instanceof ConnectException) {
                Toast.makeText(application, "網絡中斷,請檢查您的網絡狀態", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(application, "" + e.getMessage(), Toast.LENGTH_SHORT).show();
            }
            if (simpleCallback != null)
                simpleCallback.onComplete();
            dismissProgressDialog();
        } catch (Throwable el) {
            dismissProgressDialog();
            el.printStackTrace();
        }


    }

    @Override
    public void onNext(T t) {
        if (simpleCallback != null)
            simpleCallback.onNext(t);
    }

    @Override
    public void onCancelProgress() {
        if (!this.isUnsubscribed()) {
            this.unsubscribe();
        }
    }
}


   ```
   在Subscriber的四個回調函數中,onStart中實現加載框,onError中實現統一的錯誤處理,onNext中傳出一個泛型的對象做后續處理,onComplete完成網絡請求。

   7.攔截器的處理  

   ```
    public OkHttpClient provideOkHttpClient() {
        final OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                Request.Builder requestBuilder = request.newBuilder();
                Request newRequest = null;
                if (request.body() instanceof FormBody) {
                    FormBody.Builder newFormBody = new FormBody.Builder();
                    FormBody oidFormBody = (FormBody) request.body();
                    for (int i = 0; i < oidFormBody.size(); i++) {
                        newFormBody.addEncoded(oidFormBody.encodedName(i), oidFormBody.encodedValue(i));
                    }
                    String url = request.url().encodedPath();
                    newFormBody.addEncoded("timestamp", CommonUtils.getTimestamp());
                    newFormBody.addEncoded("signature", SecerityUtils.getUnsignedContent(newFormBody));
                    requestBuilder.method(request.method(), newFormBody.build());
                } else {//GET請求
                    String url = String.valueOf(request.url());
                    int index1 = url.indexOf("?");
                    int index2 = url.indexOf("=");
                    if (index1 != -1 && index2 != -1) {
                        requestBuilder.url(request.url() + "&signature="+SecerityUtils.getUnsignedContent(String.valueOf(request.url()), CommonUtils.getTimestamp())+"&timestamp=" + CommonUtils.getTimestamp());
                    } else {
                        requestBuilder.url(request.url() + "?signature="+SecerityUtils.getUnsignedContent(String.valueOf(request.url()), CommonUtils.getTimestamp())+ "&timestamp=" + CommonUtils.getTimestamp());
                    }
                }
                newRequest = requestBuilder.build();
                return chain.proceed(newRequest);
            }
        });


       if (BuildConfig.DEBUG) {
        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);
        builder.addInterceptor(logging);
          }

        builder.connectTimeout(60 * 1000, TimeUnit.MILLISECONDS)
                .readTimeout(60 * 1000, TimeUnit.MILLISECONDS);


        return builder.build();
    }

   ```
   為了不需要每一個請求都添加時間戳和signature,需要對所有的請求統一添加,上面的例子是之前一個項目中用到的get請求和post請求。對于post請求,我們攔截body參數,對于get請求,我們截取?后面的參數。然后生成相應的簽名。這樣大大方便了我們對統一參數的管理。

   8.發起http請求

   ```
      public void login(final String username, final String password) {
        apiManager.login((Activity) loginView, username, password, new SimpleCallback<User>() {
            @Override
            public void onStart() {

            }

            @Override
            public void onNext(User user) {
                loginView.loginSuccess();
            }

            @Override
            public void onComplete() {

            }
        },true);
    }
   ```

通過對應的回調函數反饋到activity或者fragment中來處理相關的UI。

9.對于相應的module,我們都可以通過注解到響應的類中,只需要在這里添加對應的Component

@Singleton @Component(modules = {AppModule.class, ApiModule.class}) public interface AppComponent { LoginComponent plus(LoginModule loginModule); }

然后在Activity或者fragment中提供響應的注入即可。

@Override public void setupActivityComponent() { AuthApplication.get(this).getAppComponent().plus(new LoginModule(this)).inject(this);

} ``` [Github上的代碼倉庫](https://github.com/xzwc/AndroidProject/tree/master/AuthProject)

總結:通過dagger2,我們可以輕松地將其它模塊注入到我們所需要的類中,然后對網絡請求統一的參數處理,返回結果的統一處理,可以很方便的處理我們的網絡請求。

##后記

通過這個框架,我們可以進行基礎的網絡請求,get,post,put,delete等基礎的網絡請求,對網絡請求錯誤和沒有網絡的情況做了統一的拋出處理,對加載對話框(菊花)也做了統一的封裝,可以很好的自定義加載對話框和控制網絡請求是否加載對話框,數據持久化。與此同時,可以對網絡請求對統一的攔截處理,方便我們添加統一的請求參數,比如token,時間戳,簽名等。 由于項目中我們采用阿里云的OSS的上傳方案,以及最近經常加班,時間有限,框架還存在著很多不完善的地方,比如對cookie的處理,上傳,下載的處理,失敗后的retry等等,后續會繼續完善。

由于本項目涉及到的知識點比較多,看起來比較凌亂,忘諒解,上面說的很多都是學習中的體會,希望大家慢慢體會,當你們親身經歷后讀起來就很easy了。

 

來自:http://blog.iliyun.net/2016/11/20/框架封裝/

 

 本文由用戶 hearsnow 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!