以前一直在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
註解(Annotation)也叫元數據。是一種代碼級別的說明。它與類、接口、枚舉在同一個層次。能夠聲明在包、類、字段、方法、局部變量、方法參數等的前面,用來對這些元素進行說明,註釋。說白了就是一種標記,能夠用本身的註解解釋器在編譯時進行處理(編譯時註解),或者在運行時經過反射對其進行處理(運行時註解)。註解框架有不少,經常使用的有ButterKnife、AndroidAnnotations、XUtils等。java
ButterKnfie出自Jakewharton大神,是使用很是多的一個註解框架。
ButterKnife經過反射在編譯時生成一個ViewBinder對象,相似於:react
MainActivity$ViewBinder<T extends MainActivity> implements ButterKnife.ViewBinder<T>
複製代碼
在這個類中就有使用註解聲明的一些組件和監聽。
由於ButterKnife在編譯時經過反射一次性生成了這些須要使用到的類,因此對運行時的速度是沒有什麼影響的。android
XUtils和ButterKnife相似,都使用了反射來對註解聲明的字段或者對象進行賦值。 區別在於XUtils是在運行時進行反射,衆所周知反射的效率相對於原生代碼是較慢的。 雖然設備性能在提高,可是在組件、事件過多的狀況下仍是形成初始化速度下降。git
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…
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是一個很火的線程調度框架,這樣描述或許不太準確,想要深刻了解的能夠打開傳送門:
《給 Android 開發者的 RxJava 詳解》
《RxJava 與 Retrofit 結合的最佳實踐》
引入:
implementation 'io.reactivex:rxandroid:1.0.1' //RxAndroid
implementation 'io.reactivex.rxjava2:rxjava:2.1.1' //RxJava
複製代碼
使用AndroidAnnotations和Retrofit+Rxjava2+okhttp3都簡化了代碼的邏輯,下降了代碼的耦合度,再引入MVP的架構,則會讓項目更加清晰明瞭。MVP的概念已經很早就誕生了,通過了很長時間的發展,依然被不少人採用,存在便是合理,接下來就整個框架進行展現。
首先,須要對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。
public interface BaseView<T extends BasePresenter> {
//設置Presenter
void setPresenter(T presenter);
//獲取上下文
Activity getContext();
//能夠添加其他在View層的方法
... ...
}
public interface BasePresenter {
//提供一個公用的開始方法
void start();
//能夠添加一些其他公用的方法,如showLoading(),dismissLoading()等。
... ...
}
複製代碼
/** * @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對請求的隊列進行管理:
/** * @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
以上文章都可轉載,轉載請註明原創。