組件化開發這個名詞並不陌生,但真正什麼纔是組件化開發,你們在網上搜能夠查看不少相應的文章,我概念中,模塊化的開發,就是把不少模塊獨立出來,基礎模塊,業務模塊等。什麼是基礎模塊,基礎模塊就是公司經常使用的一些sdk,一些封裝好的基類,業務模塊就是基於基礎模塊進行開發的。在以往的開發中,我並未真正的去使用組件化開發,直到加入新的團隊能夠說是開啓新世界的大門,給個人感受,組件化開發,賊爽,爲何爽?java
我總結了好幾點:android
1.各自負責業務模塊獨立開發,以application進行開發,後期再以library引入項目 2.由於每一個模塊獨立出來,以最小模塊的進行開發,編譯速度快 3.有利於單元測試,對業務模塊進行單元測試 4.下降耦合,提升模塊的複用git
如下爲基礎模塊包:github
整個項目結構:api
Android studio:bash
在gradle.properties中,咱們能夠設置一個變量,控制是否使用模塊化來開發網絡
#是否使用模塊化開發
isModule=false
複製代碼
而後在settings.gradle中設置項目引入包mvc
//默認都打開基礎模塊
include ':sdk', ':model', ':widget', ':module-basic'
//根據本身負責的模塊分別進行相應的引入
include ':module-user'
include ':module-business'
//根據是否模塊開發,是否引入app 模塊
if (!isModule.toBoolean()) {
include ':app'
}
複製代碼
業務模塊gradle進行模塊判斷app
//經過以前設定的變量進行設置是application仍是library
if (isModule.toBoolean()) {
apply plugin: 'com.android.application'
} else {
apply plugin: 'com.android.library'
}
複製代碼
根據結構圖,咱們基礎模塊的依賴,默認引入sdk、model、widget、module-baisc 而後根據本身負責的業務模塊,分別引入不一樣的業務,若是我是負責用戶模塊,我在開發就只須要引入用戶模塊便可,這樣開發每一個模塊的時候能夠提升每一個模塊的編譯效率。框架
最後模塊合併的時候,在gradle.properties中關閉模塊開發,在settings.gradle引入項目相應的模塊包,並設置app的build-gradle:
build-gradle:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:26.+'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
compile 'com.android.support:design:26.+'
testCompile 'junit:junit:4.12'
//若是不使用模塊化開發,就引入全部的業務模塊
if (!isModule.toBoolean()) {
compile project(':module-business')
compile project(':module-user')
}
}
複製代碼
如今的問題,不一樣模塊的activity怎麼跳轉,之前個人作法都會在每一個activity中寫一個靜態方法,把入參設定好.
/**
* 跳轉
*
* @param context 上下文
* @param param 參數
*/
public static void toAcitivty(Context context, String param) {
Intent intent = new Intent(context, MainActivity.class);
intent.putExtra("param", param);
context.startActivity(intent);
}
複製代碼
由於使用模塊化開發的話,不一樣業務模塊是不能調用其activity,所以咱們使用阿里的Arouter, 在每一個activity頭部使用註解進行跳轉,就像Spring mvc 的controller同樣,使用路由進行設置跳轉,在模塊化的開發中,這個很關鍵,一方面使用arouter能夠下降activity之間的耦合,另外一方面能夠對模塊進行單元測試。
Arouter具體的使用方法: github.com/alibaba/ARo…
關於Retrofit跟RxJava,具體詳細的用法就不在這裏介紹,網上有不少現有的文章,爲何使用Retrofit跟RxJava,Retrofit是基於Okhttp封裝一層的客戶端,配合RxJava線程調度,很好的控制網絡請求,使用RxJava能夠提升代碼的可讀性,這裏我分享一下retrofit+Rxjava封裝。
ApiFactory以下圖:
圖中的ApiFactory的職責是提供全部業務Api接口,具體提供的Api是經過接口ApiProvider提供每一個業務接口,若是用戶接口,交易接口,令牌接口等,ApiFactory經過單例,獲取api提供者,ApiProvider具體的實現類ApiProvideImpl繼承於網絡引擎RetrofitApi,RetrofitApi用於初始化一些網絡引擎。ApiProvideImpl中使用retrofit來初始化各類Api接口。
ApiProviderImpl.java:
class ApiProviderImpl extends RetrofitApi implements ApiProvider {
private OkHttpClient httpClient;
ApiProviderImpl(Context applicationContext) {
super(applicationContext);
}
private <T> T create(Class<T> cls) {
return mRetrofit.create(cls);
}
@Override
public ITokenApi getTokenApi() {
return create(ITokenApi.class);
}
@Override
public IUserApi getUserApi() {
return create(IUserApi.class);
}
@Override
public IProductApi getProductApi() {
return create(IProductApi.class);
}
@Override
public IPushApi getPushApi() {
return create(IPushApi.class);
}
@Override
public IQuotationApi getQuotationApi() {
return create(IQuotationApi.class);
}
@Override
public ITradeApi getTradeApi() {
return create(ITradeApi.class);
}
.....
}
複製代碼
使用mvp能夠解耦,結構清晰,對於業務複雜的場景來講,能夠提升代碼可讀性,結構清晰,下降後期維護成本。以下圖登陸模塊所示:
View跟presenter都抽象成接口,這樣相互不依賴於細節,有易於作單元測試,下降耦合。這裏有兩個基礎接口,LoginView跟LoginPresenter分別繼承於IView跟IPresenter,LoginViewImpl以及LoginPresenterImpl分別實現LoginView跟LoginPresenter,其依賴於抽象不依賴於實現的細節。
/**
* 登陸契約類
*/
public interface LoginContract {
/**
* 表現層接口
*/
interface Presenter extends IPresenter {
/**
* 登陸操做
*/
void login();
}
/**
* 視圖層接口
*/
interface View extends IPresenterView {
/**
* 獲取密碼
*
* @return return
*/
String getPassword();
/**
* 獲取用戶信息
*
* @return return
*/
String getUsername();
/**
* 登陸成功
*/
void loginSuccess();
/**
* 登陸失敗
*
* @param msg msg
*/
void loginFailed(String msg);
}
}
複製代碼
咱們經過定義一個Contract契約類,來制定接口,在定Presenter跟view接口的同時,咱們能夠很清晰的知道,表現層須要什麼東西,view層須要提供什麼東西,包括網絡請求後相應的響應,這樣在咱們作一個業務邏輯的時候思路能夠更清晰,同事在進行presenter複用以及單元測試會更方便。
結合以前談到的Api跟mvp,在這個基礎上進行封裝Presenter的實現基礎類。
/**
* presenter基礎實現類的封裝
* 1.跟視圖view進行綁定與解綁
* 2.對rx事件進行加工處理與釋放資源
*/
public class BasicPresenterImpl<T extends IPresenterView> implements IPresenter {
/**
* 視圖
*/
protected T mView;
/**
* 上下文
*/
protected Context mContext;
/**
* 記錄標識,用於此presenter全部的任務進行標識
*/
private String mTag = this.getClass().getName();
public BasicPresenterImpl(Context context, T view) {
this.mView = view;
this.mContext = context;
}
public void start() {
}
/**
* 銷燬資源,通常用於與view解綁操做
* 如activity做爲view中,activity 銷燬的時候調用
* 避免錯誤引用,避免內存泄露
*/
public void destroy() {
this.mView = null;
this.mContext = null;
this.cancelRequest();
}
/**
* 根據tag清掉任務,如清掉未完成的網路請求
*/
protected void cancelRequest() {
RxObservable.dispose(this.mTag);
RxObservable.dispose("PageDataObservable");
}
/**
* rxJava 多數用於建立網絡請求
* 如createObservable(mUser.login())
* retorfit結合rxJava
*
* @param observable observable
* @param <T> t
* @return return
*/
protected <T> Observable<T> createObservable(Observable<T> observable) {
//建立任務
return RxObservable.create(observable, this.mTag);
}
}
複製代碼
基礎Presenter封裝了綁定與解綁的操做,presenter跟view解綁時調用destory釋放資源,並把此presenter中使用rxJava處理得事件所有清掉,釋放資源,例如一些網絡請求,當view跟presenter解綁後網絡請求將來得及返回處理,容易出現view空指針的操做。
接着介紹一下RxObservable的封裝:
/**
* 用於封裝rx事件,經過鍵值對的方式保存任務
* 對任務進行初始化,釋放等操做所
*/
public final class RxObservable {
/**
* 全局變量,使用tag標識保存Disposable集合
* Disposable?Observer(觀察者)與Observable(被觀察者)切斷連接
*/
private static final Map<String, List<Disposable>> sObservableDisposableList = new WeakHashMap();
public RxObservable() {
}
/**
* 建立被觀察者,如retrofit集合rxJava返回的網絡請求,
* 此方法用於事件在初始化時進行處理,把此事件保存到sObservableDisposableList集合中,
* 以tag爲key,覺得List<Disposable>爲值,訂閱被觀察者時能夠獲其Disposable
*/
public static <T> Observable<T> create(Observable<T> observable, final String tag) {
return observable.doOnSubscribe(new Consumer() {
public void accept(@NonNull Disposable disposable) throws Exception {
//在集合中判斷是否存在集合
//沒有則建立,並以key-tag保存到sObservableDisposableList中
List disposables = (List) RxObservable.sObservableDisposableList.get(tag);
if (disposables == null) {
ArrayList disposables1 = new ArrayList();
RxObservable.sObservableDisposableList.put(tag, disposables1);
}
//把此事件的Disposable添加到對應的tag的集合中
((List) RxObservable.sObservableDisposableList.get(tag)).add(disposable);
}
//訂閱過程在Io線程處理,發送在主線程處理
}).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
}
/**
* 釋放全部資源
*/
public static void dispose() {
try {
Iterator e = sObservableDisposableList.values().iterator();
while (e.hasNext()) {
List disposables = (List) e.next();
Iterator var2 = disposables.iterator();
while (var2.hasNext()) {
Disposable disposable = (Disposable) var2.next();
if (disposable != null && !disposable.isDisposed()) {
disposable.dispose();
}
}
disposables.clear();
}
} catch (Exception var7) {
Log.e("rae", "釋放HTTP請求失敗!", var7);
} finally {
sObservableDisposableList.clear();
}
}
/**
* 根據tag標識進行釋放資源
*
* @param tag tag
*/
public static void dispose(String tag) {
try {
if (!TextUtils.isEmpty(tag) && sObservableDisposableList.containsKey(tag)) {
List e = (List) sObservableDisposableList.get(tag);
Iterator var2 = e.iterator();
while (var2.hasNext()) {
Disposable disposable = (Disposable) var2.next();
if (disposable != null && !disposable.isDisposed()) {
disposable.dispose();
}
}
e.clear();
sObservableDisposableList.remove(tag);
return;
}
} catch (Exception var7) {
Log.e("rae", "釋放HTTP請求失敗!", var7);
return;
} finally {
sObservableDisposableList.remove(tag);
}
}
}
複製代碼
在RxObservable中,建立一個sObservableDisposableList用於保存每一個presenter中處理的事件,經過tag做爲標識建立,每一個presenter中會經過tag找到對應的Disposable集合,Disposable集合中保存了此presenter中的全部任務,如網絡請求、io操做等,經過此方法能夠統一管理tag的任務,在presenter解綁的時候能夠及時的銷燬資源,避免內存泄露。
登陸的一個小例子:
public class LoginPresenterImpl extends BasicPresenterImpl<LoginContract.View> implements LoginContract.Presenter {
IUserApi mUserApi;
public LoginPresenterImpl(Context context, LoginContract.View view) {
super(context, view);
//初始化變量....
}
@Override
public void login() {
//在view層獲取手機號跟密碼
final String mobile = mView.getMobile();
final String password = mView.getPassword();
if (TextUtils.isEmpty(mobile)) {
mView.onLoginFailed("請輸入手機號碼");
return;
}
if (TextUtils.isEmpty(password)) {
mView.onLoginFailed("請輸入密碼");
return;
}
if (!mPhoneValidator.isMobile(mobile)) {
mView.onLoginFailed("請輸入正確的手機號碼");
return;
}
createObservable(mUserApi.login(mobile, password)).subscribe(new ApiDefaultObserver<UserInfo>() {
@Override
protected void onError(String msg) {
//登陸失敗
mView.onLoginFailed(msg);
}
@Override
protected void accept(UserInfo userInfo) {
//登陸成功等操做
}
});
}
}
複製代碼