RxJava+Retrofit+OkHttp3+Dagger2+MVP構建Android項目簡單例子

RxJava+Retrofit+OkHttp3+Dagger2+MVP構建Android項目簡單例子,以前的項目都是用的很老的MVC來做的,當然我覺得MVC在維護方面是比MVP強的,網絡請求框架是用的嚴大神的NoHttp,個人這個網絡請求框架是非常nb的,有機會看到這博客的可以去GitHub上搜一下,非常好用。最近有點時間就開始去接觸最近非常流行的Android開發組合RxJava+Retrofit+OkHttp3+Dagger2+MVP,因為剛上手,所以不是很熟,都是在學習別人的東西,基本上從別人的項目中剝離出來的,在這對那些高手表示感謝。

一、用RxJava+Retrofit+OkHttp3封裝成網絡請求框架:
1、OkHttp3工具類封裝,在okhttp中設置緩存目錄、請求攔截器,響應攔截器,代碼如下:

public class OkHttpUtil {

    private static OkHttpClient mOkHttpClient;

    //設置緩存目錄
    private static final File cacheDirectory = new File(MyApplication.getMyApplication().getCacheDir().getAbsolutePath(), "httpCache");

    private static Cache cache = new Cache(cacheDirectory, 10 * 1024 * 1024);

    //請求攔截
    private static RequestInterceptor requestInterceptor = new RequestInterceptor();

    //響應攔截
    private static ResponseInterceptor responseInterceptor = new ResponseInterceptor();


    public static OkHttpClient getOkHttpClient() {
        if (null == mOkHttpClient) {
            mOkHttpClient = new OkHttpClient.Builder()
                    .cookieJar(CookieJar.NO_COOKIES)
                    .connectTimeout(10, TimeUnit.SECONDS)
                    .readTimeout(30, TimeUnit.SECONDS)
                    .writeTimeout(30, TimeUnit.SECONDS)
                    .addInterceptor(responseInterceptor)
                    .addInterceptor(new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
                        @Override
                        public void log(String message) {
                            Log.i("http", message);
                        }
                    }).setLevel(HttpLoggingInterceptor.Level.BODY))
                    .cache(cache)
                    .build();
        }
        return mOkHttpClient;
    }
}

在代碼中有請求攔截器RequestInterceptor和響應攔截器ResponseInterceptor,一般的請求都需要帶上請求頭和一些公共的請求參數以及Cookie的管理,這些配置都是在請求攔截器中配置,而響應攔截器一般對返回的參數進行一定格式化,便於處理數據,具體見源碼。這裡還用okhttp中的日志攔截,這個非常厲害HttpLoggingInterceptor,可以打印出所有http請求的信息,再也不用跟你的後臺小哥爭論瞭,有詳細日志有真相。

2、Retrofit工具類封裝,Retrofit之所以牛逼是他可以跟RxJava完美的結合,無縫!!!具體代碼如下

public abstract class RetrofitUtil {
    //服務路徑
    private static final String Url = "https://hzqb.sftsdg.com/YMF_Webs/";
    private static Retrofit mRetrofit;
    private static OkHttpClient mOkHttpClient;

    //獲取Retrofit對象

    protected static Retrofit getRetrofit(){
        if (null == mRetrofit){
            if (null == mOkHttpClient){
                mOkHttpClient = OkHttpUtil.getOkHttpClient();
            }
            mRetrofit = new Retrofit.Builder()
                    .baseUrl(Url)
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                    .client(mOkHttpClient)
                    .build();
        }
        return mRetrofit;
    }
}

這裡是將Retrofit與OkHttp完美結合,因為Retrofit需要傳入一個Request Client,此時用OkHttp再合適不過瞭,同時,還可以用GsonConverterFactory來自動解析數據。需要註意的是,此處的url必須要以“/”結尾,不然會拋異常

3、獲取將OkHttpClient和Retrofit結合好Retrofit對象,此處,Retrofit.create()返回一個泛型,完美結合RxJava,代碼如下:

public class RequestEngine {

    private static Retrofit mRetrofit;
    //private static RequestEngine instance;

    public RequestEngine() {
        mRetrofit = RetrofitUtil.getRetrofit();
    }

    /*public static RequestEngine getInstance() {
        if (instance == null) {
            synchronized (RequestEngine.class) {
                if (null == instance) {
                    instance = new RequestEngine();
                }
            }
        }
        return instance;
    }*/

    //返回一個泛型
    public  T getServer(Class server) {
        return mRetrofit.create(server);
    }

}

代碼片中註釋的是用一般的方法引入的封裝好的Retrofit,此處把代碼註釋掉是因為用到瞭註解Dagger2,

4、用Retrofit寫請求方法,返回的是Observable泛型,代碼如下:

public interface RequestApi {

    @FormUrlEncoded
    @POST("login/u.php")
    Observable<basebean> login(@Field("username") String username, @Field("password") String password, @Field("app") String type);

}</basebean

<basebean<basebean

至於Retrofit一般用法還是挺簡單的,簡單介紹下,在retrofit中通過一個Java接口作為http請求的api接口
get請求:在方法上使用@Get註解來標志該方法為get請求,在方法中的參數需要用@Query來註釋,如:

@GET("search/repositories")
Call queryRetrofitByGetCall(@Query("q")String owner,
                                      @Query("since")String time,
                                      @Query("page")int page,
                                      @Query("per_page")int per_Page);

Post請求:使用@FormUrlEncoded和@POST註解來發送表單數據。使用 @Field註解和參數來指定每個表單項的Key,value為參數的值。需要註意的是必須要使用@FormUrlEncoded來註解,因為post是以表單方式來請求的,如:

@FormUrlEncoded
@POST("user/edit")
Call updateUser(@Field("first_name") String first, @Field("last_name") String last);

5、封裝請求方法,代碼如下:

public class RequestMethod {

    private RequestApi RequestApi;
    @Inject
    RequestEngine requestEngine;

    public RequestMethod() {
        //此處也可以用註解來做,
       // this.RequestApi = RequestEngine.getInstance().getServer(RequestApi.class);
        //用註解的方式
        DaggerLoginComponent.builder().loginModel(new LoginModule()).build().inject(this);
        RequestApi = requestEngine.getServer(RequestApi.class);
    }

    //該方法不能獲取去到baseBean中的result和msg的值
    public void loginRequest(String userName, String password, String type, HttpSubscriber subscriber){
        RequestApi.login(userName,password,type)
                .compose(RxHelper.handleResult())
                .subscribe(subscriber);
    }

    //取到所有的解析後的json數據
    public void loginRequestWithBaseBean(String userName, String password, String type, HttpSubscriber<basebean> subscriber){
        RequestApi.login(userName,password,type)
                .compose(RxHelper.<basebean>schedulersThread())
                .subscribe(subscriber);
    }

}</basebean</basebean

<basebean<basebean<basebean<basebean

在該類中獲取RequestEngine使用的註解的方式來做的,代碼裡面有註釋,在請求方法中,封裝瞭HttpSubscriber,這個是繼承Subscriber類,用來處理RxJava接受數據,以及網絡請求加載框,網絡請求異常處理類,代碼如下:

public abstract class HttpSubscriber extends Subscriber {

    private Context context;
    private boolean isShowDialog;
    private ProgressDialog progressDialog;

    public HttpSubscriber(Context context, boolean isShowDialog) {
        this.context = context;
        this.isShowDialog = isShowDialog;
    }

    @Override
    public void onStart() {
        super.onStart();
        if (!isNetWorking(context)) {
            onError("網絡不可用");
            onFinish();
            if (!isUnsubscribed()) {
                unsubscribe();
            }
        } else {
            if (progressDialog == null && isShowDialog) {
                progressDialog = new ProgressDialog(context);
                progressDialog.setMessage("正在加載...");
                progressDialog.show();
            }
        }
    }

    @Override
    public void onCompleted() {
        onFinish();
        if (!isUnsubscribed()) {
            unsubscribe();
        }
        if (progressDialog != null && isShowDialog) {
            progressDialog.dismiss();
            progressDialog = null;
        }
    }

    /**
     * onCompleted和onError是互斥的,隊列中調用瞭其中一個,就不應該再調用另一個。也是事件序列中的最後一個
     *
     */

    @Override
    public void onError(Throwable e) {
        onFinish();
        if (!isNetWorking(context)) {
            onError("網絡不可用");
        } else if (e instanceof SocketTimeoutException) {
            onError("服務器響應超時");
        } else if (e instanceof ConnectException) {
            onError("服務器請求超時");
        } else if (e instanceof HttpException) {
            onError("服務器異常");
        } else {
            onError("未知異常:"+e.getMessage());
        }
        if (progressDialog != null && isShowDialog) {
            progressDialog.dismiss();
            progressDialog = null;
        }
    }

    @Override
    public void onNext(T t) {
        onSuccess(t);
    }

    public abstract void onSuccess(T t);

    public abstract void onError(String msg);

    public abstract void onFinish();

    /**
     * 網絡監測
     *
     * @param context
     * @return
     */
    public static boolean isNetWorking(Context context) {
        boolean flag = checkNet(context);
        if (!flag) {

            Toast.makeText(context, "當前設備網絡異常,請檢查後再重試!", Toast.LENGTH_SHORT).show();
        }
        return flag;
    }

    private static boolean checkNet(Context context) {

        if (context != null) {
            ConnectivityManager mConnectivityManager = (ConnectivityManager) context
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo mNetworkInfo = mConnectivityManager
                    .getActiveNetworkInfo();
            if (mNetworkInfo != null) {
                return mNetworkInfo.isAvailable();
            }
        }
        return false;
    }
}

在這個demo中使用的Rxjava1.0的,所以重寫瞭onStart() 方法,在該方法中處理一些請求前工作,需要註意的是,改方法是在子線程運行的,但是在這裡彈出加載框肯定是有問題的,別慌,RxJava可以指定線程來處理訂閱結果的,這裡我們指定在AndroidSchedulers.mainThread()這個線程中。可以看到,在請求方法中(5)我們用到瞭compose操作符,這個操作符強大是因為,他能解決一般Rxjava普通寫法打斷鏈式結構,這個操作符需要傳入一個Transformers,Transformer實際上就是一個Func1<observable, Observable>,換言之就是:可以通過它將一種類型的Observable轉換成另一種類型的Observable,在代碼中,封裝瞭一個RxHelper類,代碼如下:</observable

public class RxHelper {

    /**
     * 處理http請求返回的結果,result_code,當返回成功的時候將data剝離出來,返回給subscriber
     *
     * @param 
     * @return
     */
    public static  Observable.Transformer<basebean, T> handleResult() {
        return new Observable.Transformer<basebean, T>() {
            @Override
            public Observable call(Observable<basebean> baseBeanObservable) {
                return baseBeanObservable.flatMap(new Func1<basebean, Observable>() {
                    @Override
                    public Observable call(BaseBean tBaseBean) {
                        if ("1".equals(tBaseBean.getResult())) {
                            //返回成功
                            return addData(tBaseBean.getData());
                        } else {
                            //返回失敗
                            return Observable.error(new Exception(tBaseBean.getMsg()));
                        }
                    }
                }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
            }
        };
    }

    /**
     * 將服務端返回的數據加入subscriber
     *
     * @param data
     * @param 
     * @return
     */

    private static  Observable addData(final T data) {
        return Observable.create(new Observable.OnSubscribe() {
            @Override
            public void call(Subscriber subscriber) {
                try {
                    subscriber.onNext(data);
                    subscriber.onCompleted();
                } catch (Exception e) {
                    subscriber.onError(e);
                }
            }
        });
    }

    /**
     * rxJava線程轉換,在io線程中發起請求,回調給主線程
     *
     * @param 
     * @return
     */

    public static  Observable.Transformer schedulersThread() {
        return new Observable.Transformer() {
            @Override
            public Observable call(Observable tObservable) {
                return tObservable
                        .subscribeOn(Schedulers.io())
                        .unsubscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread());
            }
        };
    }
}
</basebean</basebean</basebean</basebean

<basebean<basebean<basebean<basebean<basebean<basebean<basebean<basebean

與RxJava結合使用我們一般會封裝一個BaseBean來處理返回數據,這裡我們使用登錄方法類做例子:

public class BaseBean implements Serializable {
    private String result;
    private String msg;
    private T data;

    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    @Override
    public String toString() {
        return "BaseBean{" +
                "result='" + result + '\'' +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }

result,msg分別是你後臺返回的請求狀態碼,我們是result=1是代表請求成功,然後msg就會返回成功的Message,而泛型T就是返回的你需要的具體數據,一般是一個Object,或者裡面還嵌套瞭數組。
登錄返回的數據的bean如下:

public class LoginInfo extends BaseBean{
   private String token;
    private String type;
    private String is_allow_create;

    public String getIs_allow_create() {
        return is_allow_create;
    }

    public void setIs_allow_create(String is_allow_create) {
        this.is_allow_create = is_allow_create;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    @Override
    public String toString() {
        return "LoginInfo{" +
                "token='" + token + '\'' +
                ", type='" + type + '\'' +
                ", is_allow_create='" + is_allow_create + '\'' +
                '}';
    }

到此為止,網絡層封裝完畢,但是此處有一個帶解決的問題就是RxJava的生命周期問題,如果不與Activity和Fragment生命中期綁定來判定是否要取消訂閱,會出現兩個問題,第一,內存泄漏;第二,有可能會導致View拋異常而崩潰App,但是這個情況出現的幾率較小,現在網絡上的這些框架很少人加入瞭生命周期管理,在這裡,我也不知道怎麼加入到這個工程裡面。如果有大神,可以告訴我怎麼加入生命周期管理,當然是要嵌入到這個工程裡面後的代碼,不是要告訴方法,哈哈哈,彩筆一般都這樣!附上網絡層的結構圖:
網絡層工程結構圖

接下來,以登錄的例子來寫MVP模式以及Dagger2,界面如圖:
登錄界面
1、分析,這個界面有兩個功能,一個是登錄,一個是清除,所以我們寫一個接口,裡面兩個方法,分別是commit()和clear(),代碼如下:

/**
 * @author: wangbo
 * @description:    提交,清除邏輯接口
 * @date: 2017-08-08   11:02
 */
public interface IMainActivityPresenter {
    void commit(Context context,boolean isShowProgress,List editTexts, TextView msg, RequestMethod requestMethod);
    void clear(List editTexts);
}

然後在寫這個接口的實現類,重載這兩個方法,在方法裡面處理具體的邏輯,代碼如下:

/**
 * @author: wangbo
 * @description: 處理界面邏輯
 * @date: 2017-08-08   11:04
 */
public class MainActivityImpl implements IMainActivityPresenter {
    @Override
    public void commit(Context context, boolean isShowProgress, List editTexts, final TextView msg, RequestMethod requestMethod) {
        String tel = editTexts.get(0).getText().toString();
        String psw = editTexts.get(1).getText().toString();
        String type = editTexts.get(2).getText().toString();
        requestMethod.loginRequestWithBaseBean(tel, psw, type, new HttpSubscriber<basebean>(context, isShowProgress) {
            @Override
            public void onSuccess(BaseBean loginInfoBaseBean) {
                msg.setText(loginInfoBaseBean.toString());
            }

            @Override
            public void onError(String msg) {

            }

            @Override
            public void onFinish() {

            }
        });
    }

    @Override
    public void clear(List editTexts) {
        for (EditText editText : editTexts) {
            editText.setText("");
        }
    }
}</basebean

<basebean<basebean

2、寫個在MainActiviy中處理View的邏輯接口,有,初始化,View的初始化,清除,提交,這些行為,代碼如下:

/**
 * @author: wangbo
 * @description:    view動作接口
 * @date: 2017-08-08   11:13
 */
public interface IMainActivityView {
    void init();

    void intView();

    void commit();

    void clear();

}

3、在MainActivity中實現IMainActivityView 這個接口,看到重載方法:

public class MainActivity extends AppCompatActivity implements IMainActivityView, View.OnClickListener {
    /**
     * 通過@Inject來聲明依賴對象,註意,被註解的字段不能用private和protected修飾
     */
    @Inject
    IMainActivityPresenter mainActivityPresenter;
    @Inject
    RequestMethod requestMethod;
    private List editTexts;
    private TextView textView;
    private Button commit, clear;

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

    @Override
    public void init() {
        /**
         * 編寫完Component和Module和Dagger2並不會自動創建對應的類,此時需要我們手動點擊開發工具的Rebuild後,
         * 自動生成DaggerLoginComponent,通過生成的DaggerLoginComponent類來創建LoginModule實例
         * Component所需要的Module類是通過系統自動生成的Module類類名首字母小寫對應的方法來實例化的
         */
        DaggerLoginComponent.builder().loginModel(new LoginModule()).build().inject(this);
        editTexts = new ArrayList<>();
    }

    @Override
    public void intView() {
        editTexts.add((EditText) findViewById(R.id.tel));
        editTexts.add((EditText) findViewById(R.id.psw));
        editTexts.add((EditText) findViewById(R.id.type));
        textView = (TextView) findViewById(R.id.tv_msg);
        commit = (Button) findViewById(R.id.commit);
        commit.setOnClickListener(this);
        clear = (Button) findViewById(R.id.clear);
        clear.setOnClickListener(this);

    }

    @Override
    public void commit() {
        mainActivityPresenter.commit(MainActivity.this, true, editTexts, textView, requestMethod);
    }

    @Override
    public void clear() {
        mainActivityPresenter.clear(editTexts);
    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.commit:
                commit();
                break;
            case R.id.clear:
                clear();
                break;
        }
    }
}

項目結構圖如下:
MVP模式工程結構圖

在這裡總結下我的MVP的寫法:首先分析界面中有哪些功能,把這些功能寫成接口,在接口的方法中傳入所需要的參數,然後寫一個該接口的實現類,實現這個接口,在重寫的方法中寫這個功能的具體邏輯,然後再為瞭簡化Activity中的代碼,美化結構,在一個處理Activity中所有總邏輯的接口,比如,初始化類,初始化view,Activity中涉及的view的動作的接口,讓Activity去實現這個接口,最後,在Activity中傳入邏輯處理的接口,即IMainActivityPresenter,實例化該接口是通過new它的實現類來完成的。說到實例化類,這裡就引入瞭Dagger2,依賴註入的方式來傳遞對象,避免在使用對象的過程中去new對象,具體如下:

1、寫一個在該工程中索要使用的Module類,這個類中是提供你工程中要用的對象,具體代碼如下,用法說明見代碼註釋:

/**
 * @author: wangbo
 * @description: 聲明Module
 * @date: 2017-08-08   14:05
 */

/**
 * @ Module申明該類是Module類
 * @ Provides聲明Module類中哪些方法是用來提供依賴對象,當Component類需要依賴對象時,他就會根據返回值的類型來在有@Provides註解的方法中選擇調用哪個方法
 * @ Singleton的作用就是聲明單例模式,^-^以後再也不用寫單例模式瞭
 * @ Singleton的單利模式還以通過自定義註解來實現,這樣做的目的就是方法查看該類的作用域
 * 如: @ Scope
 *      @ Retention(RetentionPolicy.RUNTIME)
 *      public @interface PerActivity {}
 *   此處的@Singleton可以用 @PerActivity來代替,可以清楚的看到這是作用字Activity,則能與Fragment區別
 */

@Module
public class LoginModule {

    @Singleton
    @Provides
    IMainActivityPresenter mainActivity(){
        return new MainActivityImpl();
    }

    @Singleton
    @Provides
    RequestMethod requestMethod(){
        return new RequestMethod();
    }

    @Singleton
    @Provides
    RequestEngine requestEngine(){
        return new RequestEngine();
    }
}

2、寫一個Component,這個類是一個中間橋接類,她的作用是將你要使用的對象,通過Component來註入到你的對象需求方,具體代碼如下,詳細說明見代碼註釋:

/**
 * @author: wangbo
 * @description: 聲明Component
 * @date: 2017-08-08   14:08
 */

/**
 * 當@module中聲明瞭單例模式Singleton的時候在Component中也需要聲明
 * @ Component註解有兩個屬性,modules和dependencies這兩個屬性的類型都是Class數組,modules的作用就是聲明該Component含有哪幾個Module,當Component需要某個依賴對象時,就會通過這些Module類中對應的方法獲取依賴對象
 * inject方法就是將module中對應方法取出的對象通過Component來把依賴需求方(MainActivity、RequestMethod)所需要的對象註入到依賴需求方
 */
@Singleton
@Component(modules = LoginModule.class)
public interface LoginComponent {
    void inject(MainActivity mainActivity);
    void inject(RequestMethod requestMethod);
}

Component接口中有兩個inject方法,這個方法就是將LoginModule.class註入到MainActivity 和RequestMethod 中。

3、在你所需要用到這兩個對象的地方用@Inject註解來標識該對象,註意,使用註解的字段不能用private和protected修飾,然後rebuild工程,生成DaggerLoginComponent對象,調用

 DaggerLoginComponent.builder().loginModel(new LoginModule()).build().inject(this);

方法實例化Module。然後註解的類不需要new就可以直接用瞭。ok,到此結束,工程結構圖如下:
Dagger2註解工程結構

4、Dagger2的引入方法:

a、在工程跟目錄下的build.gradle下面的dependencies下添加:
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'

b、在app目錄下的build.gradle下添加插件:
apply plugin: 'com.neenbedankt.android-apt'

c、引入依賴:

  //引入dagger2
    compile 'com.google.dagger:dagger:2.6'
    apt 'com.google.dagger:dagger-compiler:2.6'
    //java註解
    provided 'org.glassfish:javax.annotation:10.0-b28'

最後,這個工程所用的其他項目依賴:

    compile 'io.reactivex:rxandroid:1.1.0'
    compile 'io.reactivex:rxjava:1.1.0'
    compile 'com.squareup.retrofit2:retrofit:2.3.0'
    compile 'com.squareup.retrofit2:converter-gson:2.3.0'
    compile 'com.squareup.retrofit2:adapter-rxjava:2.3.0'
    compile 'com.squareup.okhttp3:okhttp:3.8.1'
    compile 'com.squareup.okhttp3:logging-interceptor:3.8.1'

</basebean</basebean</basebean</basebean</basebean</basebean</basebean</basebean</basebean</basebean</basebean</basebean</basebean</basebean</basebean</basebean

You May Also Like