1.元件化開發
元件化開發這個名詞並不陌生,但真正什麼才是元件化開發,大家在網上搜可以檢視很多相應的文章,我概念中,模組化的開發,就是把很多模組獨立出來,基礎模組,業務模組等。什麼是基礎模組,基礎模組就是公司常用的一些sdk,一些封裝好的基類,業務模組就是基於基礎模組進行開發的。在以往的開發中,我並未真正的去使用元件化開發,直到加入新的團隊可以說是開啟新世界的大門,給我的感覺,元件化開發,賊爽,為什麼爽?
我總結了好幾點:
1.各自負責業務模組獨立開發,以application進行開發,後期再以library引入專案 2.因為每個模組獨立出來,以最小模組的進行開發,編譯速度快 3.有利於單元測試,對業務模組進行單元測試 4.降低耦合,提高模組的複用
以下為基礎模組包:
整個專案結構:
Android studio:
在gradle.properties中,我們可以設定一個變數,控制是否使用模組化來開發
#是否使用模組化開發
isModule=false
複製程式碼
然後在settings.gradle中設定專案引入包
//預設都開啟基礎模組
include ':sdk', ':model', ':widget', ':module-basic'
//根據自己負責的模組分別進行相應的引入
include ':module-user'
include ':module-business'
//根據是否模組開發,是否引入app 模組
if (!isModule.toBoolean()) {
include ':app'
}
複製程式碼
業務模組gradle進行模組判斷
//通過之前設定的變數進行設定是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…
2.Retrofit+RxJava+MVP模式
關於Retrofit跟RxJava,具體詳細的用法就不在這裡介紹,網上有很多現有的文章,為什麼使用Retrofit跟RxJava,Retrofit是基於Okhttp封裝一層的客戶端,配合RxJava執行緒排程,很好的控制網路請求,使用RxJava可以提高程式碼的可讀性,這裡我分享一下retrofit+Rxjava封裝。
1.基於Retrofit的Api工廠
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);
}
.....
}
複製程式碼
2.MVP
使用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複用以及單元測試會更方便。
3.結合Retrofit+RxJava+Mvp
結合之前談到的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) {
//登入成功等操作
}
});
}
}
複製程式碼