Android Annotations+Retrofit+Rxjava2+okhttp3+MVP框架搭建

前言

  以前一直在qq空間和公衆號發佈文章,這幾天才轉移到博客平臺。對博客平臺的各類規則還不是特別熟悉,屬於博客小白。可是我會堅持更新,將實用的,易懂的文章推送給你們。感謝你們的點贊和關注。
  上一篇文章 《Android Gradle統一管理打包》收到了不少點贊和評論。Gradle統一管理在項目體量較小的狀況下是沒必要要的,可是若是體量稍大,或者使用了插件化框架。那麼它的好處就會顯示出來,在上一篇的基礎上,繼續進行探索,梳理流行框架Annotations+Retrofit+Rxjava2+okhttp3+MVP框架的搭建。
  而Annotations+Retrofit+Rxjava2+okhttp3+MVP框架又和gradle有什麼關係呢?
  原本想經過一篇 Small插件化+Annotations+Retrofit+Rxjava2+okhttp3+MVP 來描述在使用插件化的狀況下moudle過多的狀況,可是Small插件化是一個很重要的東西,若是所有混在一塊兒寫會形成篇幅過長或者內容過於雜亂。因此本篇拋開Small插件化只描述Annotations+Retrofit+Rxjava2+okhttp3+MVP的搭建。下一篇博文再對Small插件化和此框架進行融合。javascript

AndroidAnnotations與註解框架對比

註解(Annotation)也叫元數據。是一種代碼級別的說明。它與類、接口、枚舉在同一個層次。能夠聲明在包、類、字段、方法、局部變量、方法參數等的前面,用來對這些元素進行說明,註釋。說白了就是一種標記,能夠用本身的註解解釋器在編譯時進行處理(編譯時註解),或者在運行時經過反射對其進行處理(運行時註解)。註解框架有不少,經常使用的有ButterKnife、AndroidAnnotations、XUtils等。java

一、ButterKnife(編譯時註解)

ButterKnfie出自Jakewharton大神,是使用很是多的一個註解框架。
ButterKnife經過反射在編譯時生成一個ViewBinder對象,相似於:react

MainActivity$ViewBinder<T extends MainActivity> implements ButterKnife.ViewBinder<T>
複製代碼

在這個類中就有使用註解聲明的一些組件和監聽。
由於ButterKnife在編譯時經過反射一次性生成了這些須要使用到的類,因此對運行時的速度是沒有什麼影響的。android

二、XUtils(運行時註解)

XUtils和ButterKnife相似,都使用了反射來對註解聲明的字段或者對象進行賦值。 區別在於XUtils是在運行時進行反射,衆所周知反射的效率相對於原生代碼是較慢的。 雖然設備性能在提高,可是在組件、事件過多的狀況下仍是形成初始化速度下降。git

三、AndroidAnnotations(編譯時註解)

AndroidAnnotations和ButterKnife相似,都是編譯時註解。因此在性能方面不相上下,可是二者的實現思路卻不一樣:
ButterKnfie使用反射來對註解標記的字段賦值。而AndroidAnnotations會生成一個繼承於當前類的子類來對標記的字段進行賦值。
AndroidAnnotations生成的類後面都會帶有一個符號「_」,運行的時候也運行的是這個生成的子類,而不是咱們的當前類。
看一下AndroidAnnotations生成的代碼就可以很清楚的瞭解它的思路,首先咱們看一下使用了註解的StartActivity中的代碼。github

StartActivity.java api

//使用EActivity註解加載佈局
@EActivity(R.layout.activity_start)
public class StartActivity extends BaseActivity implements StartContract.View {
    private StartContract.Presenter mPresenter;
    
    //使用ViewById註解查找組件
    @ViewById(R.id.cdpv_start)
    CountDownProgressView mCountDownView;            //倒計時組件
    private static final long mDelayTimeLong = 2000l;//倒計時毫秒

    //加載佈局以前調用方法的註解
    @AfterInject
    void afterInject() {
        new StartPresenter(this);
        StatusBarUtil.immersive(this);
    }
    
    //加載佈局以後調用方法的註解
    @AfterViews
    void afterViews() {
        mCountDownView.setTimeMillis(mDelayTimeLong);
        mCountDownView.start();
        countDown();
    }
    
    //運行在子線程的註解
    @Background(delay = mDelayTimeLong, id = "delay_to_main")
    void countDown() {
        startMain();
    }

    //運行在主線程的註解
    @UiThread
    void startMain() {
        if (UserStateUtil.getInstance().isLoggedOn()) {
            MainActivity_.intent(getContext()).start();
        } else {
            LoginActivity_.intent(getContext()).start();
        }
        finish();
    }
    
    //點擊事件監聽註解
    @Click(R.id.cdpv_start)
    void onCountDownClick() {
        BackgroundExecutor.cancelAll("delay_to_main", true);
        startMain();
    }

    @Override
    public void setPresenter(StartContract.Presenter presenter) {
        mPresenter = Null.checkNotNull(presenter);
    }

    @Override
    public Activity getContext() {
        return this;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        BackgroundExecutor.cancelAll("delay_to_main", true);
    }
}
複製代碼

上面的代碼是啓動頁本類的實現,能夠看到使用了AndroidAnnotations的一些經常使用註解。
而這些註解都會經過框架生成對應的代碼,下面是經過本類生成的StartActivity_.java子類的部分代碼:bash

StartActivity_ . java 部分代碼 微信

public final class StartActivity_ extends StartActivity implements HasViews, OnViewChangedListener {
    private final OnViewChangedNotifier onViewChangedNotifier_ = new OnViewChangedNotifier();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        OnViewChangedNotifier previousNotifier = OnViewChangedNotifier.replaceNotifier(onViewChangedNotifier_);
        init_(savedInstanceState);//此處調用了AfterInject
        super.onCreate(savedInstanceState);
        OnViewChangedNotifier.replaceNotifier(previousNotifier);
        setContentView(R.layout.activity_start);//此處經過EActivity加載
    }
    
    private void init_(Bundle savedInstanceState) {
        OnViewChangedNotifier.registerOnViewChangedListener(this);
        afterInject();
    }

    @Override
    public <T extends View> T internalFindViewById(int id) {
        return ((T) this.findViewById(id));
    }

    @Override
    public void setContentView(int layoutResID) {
        super.setContentView(layoutResID);
        onViewChangedNotifier_.notifyViewChanged(this);
    }
    
    //查找組件設置監聽
    @Override
    public void onViewChanged(HasViews hasViews) {
        this.mCountDownView = hasViews.internalFindViewById(R.id.cdpv_start);
        if (this.mCountDownView != null) {
            this.mCountDownView.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View view) {
                           StartActivity_.this.onCountDownClick();
                        }
                   }
            );
        }
        afterViews();
    }
    
    //建立UI線程
    @Override
    void startMain() {
        UiThreadExecutor.runTask("", new Runnable() {

                    @Override
                    public void run() {
                        StartActivity_.super.startMain();
                    }
                }
                , 0L);
    }
    
    //建立子線程
    @Override
    void countDown() {
        BackgroundExecutor.execute(new BackgroundExecutor.Task("delay_to_main", 2000L, "") {
                   @Override
                   public void execute() {
                       try {
                           StartActivity_.super.countDown();
                       } catch (final Throwable e) {
                           Thread.getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
                       }
                   }
               }
        );
    }
}
複製代碼

從上面的代碼能夠看到,生成的StartActivity_子類中有本類定義的方法,還有對註解事件的實現。
原理大概瞭解了以後,就是AndroidAnnotations的使用了,使用AndroidAnnotations須要導入:cookie

//AndroidAnnotations
implementation 'org.androidannotations:androidannotations:4.3.1'
複製代碼

在AndroidManifest中註冊使用@EActivity標記的Activity須要使用框架生成的Activity,如:

<!--登陸-->
<activity android:name=".moudle.login.LoginActivity_" android:screenOrientation="portrait" />**
複製代碼

一樣,使用@EApplication標記的Application也須要使用框架生成的子類,如:

android:name=".application.XXXXApplication_"
複製代碼

Service也一樣相似,都須要使用框架生成的子類。
AndroidAnnotations所有的註解文檔在其GitHub上能夠查閱:
github.com/androidanno…

四、註解框架對比

這張圖片引用自CSDN,Annotations使用的複雜度稍高於另外兩個框架,並非指接入特別困難。

Retrofit+OkHttp

Retrofit是一個請求框架,和Okhttp出自同一個團隊,OkHttp負責進行底層的網絡請求,Retrofit負責對請求事件進行裝配,對請求結果進行解析。Retrofit對請求進行封裝,很大程度上下降了代碼的耦合程度。
引入:

implementation 'com.squareup.okhttp3:okhttp:3.8.1'              //okhttp
    implementation 'com.squareup.okhttp3:logging-interceptor:3.8.1' //okhttp攔截器
    implementation 'com.squareup.retrofit2:retrofit:2.0.2'          //retrofit
    implementation 'com.squareup.retrofit2:converter-gson:2.0.2'    //retrofit的Gson解析器
    implementation 'com.squareup.retrofit2:adapter-rxjava:2.0.2'    //retrofit的RxJava適配器支持
    implementation 'com.google.code.gson:gson:2.7'                  //Gson
複製代碼

RxJava

RxJava是一個很火的線程調度框架,這樣描述或許不太準確,想要深刻了解的能夠打開傳送門:
《給 Android 開發者的 RxJava 詳解》
《RxJava 與 Retrofit 結合的最佳實踐》
引入:

implementation 'io.reactivex:rxandroid:1.0.1'                   //RxAndroid
    implementation 'io.reactivex.rxjava2:rxjava:2.1.1'              //RxJava
複製代碼

MVP

使用AndroidAnnotations和Retrofit+Rxjava2+okhttp3都簡化了代碼的邏輯,下降了代碼的耦合度,再引入MVP的架構,則會讓項目更加清晰明瞭。MVP的概念已經很早就誕生了,通過了很長時間的發展,依然被不少人採用,存在便是合理,接下來就整個框架進行展現。

一、Retrofit+Okhttp封裝

首先,須要對Retrofit和Okhttp進行初始化:

/** * @author WuYang * @version v1.0 設置網絡請求 */
public abstract class RetrofitSingleton {
    protected static ApiInterface apiService = null;
    public static Retrofit retrofit = null;
    public static OkHttpClient okHttpClient = null;
    
    //此處的ApiInterface就是經過註解定義請求接口的類
    public static void init() {
        initOkHttp();
        initRetrofit();
        if (retrofit != null)
            apiService = retrofit.create(ApiInterface.class);
    }
    
    //初始化OkHttp
    private static void initOkHttp() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        //若是不是發佈版本,則初始化logging攔截器
        if (!TzcmApplication_.isRelease) {
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            builder.addInterceptor(loggingInterceptor);
        } else {
            Log.i("舒適提示", "反編譯及攻擊本軟件將承擔法律風險!請勿以身試法!");
        }
        //是否強制取消代理來防止抓包
        if (TzcmApplication_.isCancelProxy) {
            builder.proxy(Proxy.NO_PROXY);
        }
        //設置超時
        builder.connectTimeout(90, TimeUnit.SECONDS);
        builder.readTimeout(90, TimeUnit.SECONDS);
        builder.writeTimeout(90, TimeUnit.SECONDS);
        //錯誤重連、斷線重連
        builder.retryOnConnectionFailure(false);
        //添加自定義請求頭捕獲器,如token等
        builder.addInterceptor(new AddCookiesInterceptor());
        okHttpClient = builder.build();
    }
    
    //初始化Retrofit
    private static void initRetrofit() {
        retrofit = new Retrofit.Builder()
                .baseUrl(XXXXApplication_.isRelease ? ApiInterface.API_HOST_ONLINE : ApiInterface.API_HOST_OFFLINE)
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();
    }
    
    //攔截器,添加一些固定的參數。如:token、deviceId、cookie等
    public static class AddCookiesInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request.Builder builder = chain.request().newBuilder();
            //添加token
            if (UserStateUtil.getInstance().isLoggedOn()) {
                builder.addHeader(CommonKey.ApiParams.TOKEN, UserStateUtil.getInstance().getToken());
            }
            return chain.proceed(builder.build());
        }
    }
}
複製代碼

接下來就建立ApiInterface:

/** * @author WuYang * @version v1.0 API接口 */
public interface ApiInterface {
    String API_HOST_OFFLINE = "http://www.xxxxxxxx.com/";//線下的api 地址
    String API_HOST_ONLINE = "http://www.xxxxxxxx.com/";//線上地址

    String API_LOGIN = "ChujiMallServer/app/appDelivery/api/deliveryLogin";

    //用戶登陸
    @FormUrlEncoded
    @POST(API_LOGIN)
    Observable<UserInfoResponse> mLoginAPI(@FieldMap Map<String, Object> params);
複製代碼

定義好接口以後須要對接口聲明的方法進行實現:

public class RetrofitApi extends RetrofitSingleton {
    private volatile static RetrofitApi instance;

    /** * Returns singleton class instance * 使用單例模式中的雙重加鎖 */
    public static RetrofitApi getInstance() {
        if (instance == null) {
            synchronized (RetrofitApi.class) {
                if (instance == null) {
                    instance = new RetrofitApi();
                }
            }
        }
        return instance;
    }

    /** * 登陸 * @param account 帳號,必填 * @param password 密碼,必填 */
    public Observable login(String account, String password) {
        HashMap<String, Object> params = new HashMap<>();
        params.put(CommonKey.ApiParams.LOGIN_ID, account);
        params.put(CommonKey.ApiParams.PASSWORD, password);
        return apiService.mLoginAPI(params);
    }
}
複製代碼

而後再Application中進行init:

@EApplication
public class XXXXApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        initRetrofit();
    }

    private void initRetrofit() {
        RetrofitSingleton.init();
    }
}
複製代碼

二、BaseView、BasePresenter

定義接口BaseView和BasePresenter。

public interface BaseView<T extends BasePresenter> {
    //設置Presenter
    void setPresenter(T presenter);
    //獲取上下文
    Activity getContext();
    //能夠添加其他在View層的方法
    ... ...
}

public interface BasePresenter {
    //提供一個公用的開始方法
    void start();
    //能夠添加一些其他公用的方法,如showLoading(),dismissLoading()等。
    ... ...
}
複製代碼

三、使用RxJava配合Retrofit進行請求:

/** * @author YangWu * @description 請求接口基類 */
public abstract class OnRequestCallback<T extends BaseResponse> extends Subscriber<T> {

    public abstract void onFailed(int code, String respMsg, String respCode, T response);

    public abstract void onException(Throwable e);

    public abstract void onResponse(T response);

    public abstract void onFinish();

    public void onStart() {
        if (!RxNetTool.isAvailable(TzcmApplication_.getInstance())) {
            onFailed(-1, "網絡鏈接異常,請檢查網絡鏈接", "E99999", null);
            onFinish();
            unsubscribe();
            return;
        }
    }

    public final void onCompleted() {
        onFinish();
    }

    public final void onError(Throwable e) {
        try {
            onException(e);
            onFinish();
        } catch (Exception ex) {
            e.printStackTrace();
        }
    }

    public final void onNext(T response) {
        if (!Null.isNull(response) && response.isSuccess()) {
            onResponse(response);
        } else {
            onFailed(response.code, response.respMsg, response.respCode, response);
        }
    }
}

/** * @author YangWu * @description 請求接口 */
public abstract class OnSimpleRequestCallback<T extends BaseResponse> extends OnRequestCallback<T> {

    private Context mContext;

    public OnSimpleRequestCallback(Context context) {
        this.mContext = context;
    }

    @Override
    public void onStart() {
        super.onStart();
    }

    @Override
    public void onFinish() {

    }

    @Override
    public void onFailed(int code, String respMsg, String respCode, T response) {
        ToastUtils.show(respMsg);
        XPopupManager.dismissLoading();
    }

    @Override
    public void onException(Throwable e) {
        Log.e(mContext.getPackageName(), e.getMessage());
        XPopupManager.dismissLoading();
    }
}
複製代碼

定義一個BaseResponse來統一接口請求的數據格式:

/** * @author YangWu * @description 請求體基類 */
public class BaseResponse implements Serializable {

    public String respMsg;
    public String respCode;
    public int code;

    public boolean isSuccess() {
        return code == 200;
    }
}
複製代碼

四、使用RxJava管理請求

到這裏,框架須要的基本東西已經搭建完成了。接下來使用RxJava對請求的隊列進行管理:

/** * @author YangWu * @description RxJavaActionManager 請求隊列操做接口 */
public interface RxActionManager<T> {

    void add(T tag, Subscription subscription); //添加請求

    void remove(T tag);                         //移除請求

    void cancel(T tag);                         //取消請求

    void cancelAll();                           //取消全部請求
}


/** * @author YangWu * @description RxApi管理器 */
public class RxApiManager implements RxActionManager<Object> {

    private static RxApiManager sInstance = null;
    private ArrayMap<Object, Subscription> maps;

    public static RxApiManager get() {
        if (sInstance == null) {
            synchronized (RxApiManager.class) {
                if (sInstance == null) {
                    sInstance = new RxApiManager();
                }
            }
        }
        return sInstance;
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    private RxApiManager() {
        maps = new ArrayMap<>();
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    @Override
    public void add(Object tag, Subscription subscription) {
        maps.put(tag, subscription);
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    @Override
    public void remove(Object tag) {
        if (!maps.isEmpty()) {
            maps.remove(tag);
        }
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    public void removeAll() {
        if (!maps.isEmpty()) {
            maps.clear();
        }
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    @Override
    public void cancel(Object tag) {
        if (maps.isEmpty()) {
            return;
        }
        if (maps.get(tag) == null) {
            return;
        }
        if (!maps.get(tag).isUnsubscribed()) {
            maps.get(tag).unsubscribe();
            maps.remove(tag);
        }
    }

    @TargetApi(Build.VERSION_CODES.KITKAT)
    @Override
    public void cancelAll() {
        if (maps.isEmpty()) {
            return;
        }
        Set<Object> keys = maps.keySet();
        for (Object apiKey : keys) {
            cancel(apiKey);
        }
    }
}
複製代碼

使用

仍是以Login爲例,首先實現一個LoginContract對View層和Presenter層的方法進行管理。

public class LoginContract {
    //LoginActivity中須要實現的方法LoginSuccess();
    interface View extends BaseView<Presenter> {
        void loginSuccess();
    }
    //LoginPresenter中須要實現的方法login();
    interface Presenter extends BasePresenter {
        void login(String number, String password);
    }
}
複製代碼

而後對LoginActivity進行編寫:

@EActivity(R.layout.activity_login)
public class LoginActivity extends BaseActivity implements LoginContract.View {
    private LoginContract.Presenter mPresenter;
    
    //查找組件
    @ViewById(R.id.til_login_account)
    TextInputLayout mAccountTil;
    @ViewById(R.id.et_login_account)
    EditText mAccountEt;
    @ViewById(R.id.til_login_password)
    TextInputLayout mPasswordTil;
    @ViewById(R.id.et_login_password)
    EditText mPasswordEt;
    @ViewById(R.id.tv_login)
    TextView mLoginTv;
    
    //依賴注入後設置StatusBar模式爲沉浸式,而且實例化Presenter
    @AfterInject
    void afterInject() {
        StatusBarUtil.immersive(this);
        new LoginPresenter(this);
    }

    @Click(R.id.tv_login)
    void onLoginTvClick() {
        String account = mAccountEt.getText().toString().trim();
        String password = mPasswordEt.getText().toString().trim();
        if (PublicUtils.isEmpty(account)) {
            AnimationUtils.startShakeByViewIsNeedVibrate(mAccountTil, true);
            mAccountEt.setError("請輸入帳號");
            return;
        }
        if (PublicUtils.isEmpty(password)) {
            AnimationUtils.startShakeByViewIsNeedVibrate(mPasswordTil, true);
            mPasswordEt.setError("請輸入密碼");
            return;
        }
        mPresenter.login(account, password);
    }
    
    //View層方法,登陸成功後在Presenter層會回調此方法
    @Override
    public void loginSuccess() {
        MainActivity_.intent(getContext()).start();
        finish();
    }

    @Override
    public void onBackPressed() {
        if (RxTool.isFastClick(2000)) {
            TzcmApplication_.getInstance().exitApp();
        } else {
            ToastUtils.show("再按一次退出程序");
        }
    }
    
    //將Presenter綁定到View層
    @Override
    public void setPresenter(LoginContract.Presenter presenter) {
        mPresenter = Null.checkNotNull(presenter);
    }
    
    //View層實現的獲取上下文的方法
    @Override
    public Activity getContext() {
        return this;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //在頁面銷燬時經過RxJava取消登陸請求
        RxApiManager.get().cancel(ApiInterface.API_LOGIN);
    }
}
複製代碼

能夠看到,在點擊登陸按鈕時調用了mPresenter.login(account, password);方法,這個方法在P層進行處理:

/** * @author: WuYang * @describe: 登陸Presenter */
public class LoginPresenter implements LoginContract.Presenter {
    private LoginContract.View mView;
    
    //綁定View層
    public LoginPresenter(@NonNull LoginContract.View view) {
        mView = Null.checkNotNull(view);
        mView.setPresenter(this);
    }
    
    //繼承自Presenter的方法,能夠處理一些頁面初始化的邏輯
    @Override
    public void start() {

    }
    
   /** * 在View層調用login方法 * 經過Retrofit的subscribe觀察者模式進行請求,使用上面的RxJava的請求框架OnSimpleRequestCallback進行請求 * 接收的UserInfoResponse是繼承於BaseResponse的子類,根據業務邏輯具備本身的屬性 * 請求成功後經過mView.loginSuccess();方法回調到View層進行處理 */
    @Override
    public void login(String number, String password) {
        XPopupManager.showLoading(mView.getContext(), "正在登陸", ApiInterface.API_LOGIN);
        Subscriber subscriber = new OnSimpleRequestCallback<UserInfoResponse>(mView.getContext()) {
            @Override
            public void onResponse(UserInfoResponse response) {
                UserStateUtil.getInstance().updateUser(response.data);
                XPopupManager.dismissLoading();
                mView.loginSuccess();
            }
        };
        RxApiManager.get().add(ApiInterface.API_LOGIN, subscriber);
        RetrofitApi.getInstance().login(number, password)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(subscriber);
    }
}
複製代碼

總結

  經過這樣的構建方式能夠很好的將代碼解耦,便於項目後期的維護和開發。接下來我會將Small插件化框架再融合進行,而後經過Gradle統一管理。後續我會將搭建的這個框架代碼上傳至github,應該就是這幾天,要從qq空間和公衆號轉移過來的東西有點多。


長路漫漫,菜不是原罪,墮落纔是原罪。
個人CSDN:blog.csdn.net/wuyangyang_…
個人簡書:www.jianshu.com/u/20c2f2c35…
個人掘金:juejin.im/user/58009b…
個人GitHub:github.com/wuyang2000
我的網站:www.xiyangkeji.cn
我的app(茜茜)蒲公英鏈接:www.pgyer.com/KMdT
個人微信公衆號:茜洋 (按期推送優質技術文章,歡迎關注)
Android技術交流羣:691174792

以上文章都可轉載,轉載請註明原創。

相關文章
相關標籤/搜索