Android工程架構設計:Base Library(基層MVP框架)基於EventBus
Base Library部分把App中Application,UI(activity,fragment)公用方法重新封裝成模板方法,並開放對子類的擴充套件。同時融入mvp設計思想,封裝成基於mvp的基層架構體系。
目錄
2.1 規定Application中行為的執行規則(模板方法模式)
2.2 實現IApplication中的部分行為(通用部分)
2.4 定義與公共行為相關的抽象方法(具體行為),供子類擴充套件
3.1.1.PV之間通訊採用EventBus訊息機制替換之前的介面回撥機制。
1,IApplication(介面):
1,把框架內應用(Application)的通用行為抽象定義(框架內app都會用到的初始化
2,繼承ActivityLifecycleCallbacks介面,整合Activity生命週期回撥方法。
之後框架內所有app的自定義Application應該都實現此介面,具體行為放在各個app的Application中實現。
public interface IApplication extends Application.ActivityLifecycleCallbacks { void initNetworkConfig(); void initSharedPreference(String sharedName); void initOtherConfig(); }
也就是說,每個app的Application做了兩部分事情,一部分是實現了公共行為(IApplication)(共性),另外一部分實現了私有行為(異性)。
2,BaseApplication(抽象基類)
public abstract class BaseApplication extends MultiDexApplication implements IApplication
BaseApplication需要做四件事情:
2.1 規定Application中行為的執行規則(模板方法模式)
@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(this);
initNetworkConfig();
initSharedPreference(defineSharedPreferenceName());
initOtherConfig();
}
2.2 實現IApplication中的部分行為(通用部分)
@Override
public void initNetworkConfig() {
// init common config
}
@Override
public void initSharedPreference(String sharedName) {
SharedUtils.initInstance(getApplicationContext(), sharedName);
}
2.3 Application關鍵方法
@Override
public void onTerminate() {
super.onTerminate();
unregisterActivityLifecycleCallbacks(this);
}
@Override
public void onLowMemory() {
super.onLowMemory();
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
本類庫中,利用Activity生命週期回撥方法,由BaseApplication對Activity對應P層的註冊,解注行為進行統一管理。支援Activity,AppCompatActivity,FragmentActivity。
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
if (activity instanceof BaseActivity) {
((BaseActivity) activity).registerPresenter();
} else if (activity instanceof BaseAppCompatActivity) {
((BaseAppCompatActivity) activity).registerPresenter();
} else if (activity instanceof BaseFragmentActivity) {
((BaseFragmentActivity) activity).registerPresenter();
}
}
@Override
public void onActivityDestroyed(Activity activity) {
if (activity instanceof BaseActivity) {
((BaseActivity) activity).unregisterPresenter();
} else if (activity instanceof BaseAppCompatActivity) {
((BaseAppCompatActivity) activity).unregisterPresenter();
} else if (activity instanceof BaseFragmentActivity) {
((BaseFragmentActivity) activity).unregisterPresenter();
}
}
2.4 定義與公共行為相關的抽象方法(具體行為),供子類擴充套件
protected abstract String defineSharedPreferenceName();
// other methods ...
3,mvp(基層mvp架構)
mvp最近很火,也很煩,各個公司,各位大神,對mvp的理解和應用各異,論壇上各種對mvp的詳解,demo,設計分析以及基於mvp的各種開源專案 ……
作者淺見:mvp就是一種架構設計思想,目的在於把UI展現層,業務邏輯處理層,資料管理層在程式碼中解耦,達到某種程度的程式碼層次清晰明瞭(我的理解:某種程度!!!)。至於實際應用中以何種方式設計實現,達到何種程度? …… 因人因專案因公司而已吧,設計成本,團隊理解力,學習成本,程式碼可讀性等等……哪種才是最好的?達到目的,適合自己就夠了,別出嚴重bug就好。另外,作者後續會對mvp的個人理解整理成文,敬請關注!
作者在接觸並應用mvp設計思想的過程中,總結到一些問題(針對標準mvp):
1,頻繁的介面呼叫和回撥:V讓P做事情,需要呼叫P的介面,P從M請求資料需要呼叫M的介面;M把資料返回給P做業務處理要呼叫P的回撥介面,P處理完業務邏輯或者業務資料返回給V做互動反饋又要呼叫V的回撥介面。
你會發現,一套mvp程式碼寫完,光介面就定義了一火車,煩且繁!一個簡單UI互動,動輒需要2~4個甚至更多介面定義,層級解耦的開發代價是否過大呢?
2,耦合,主要是PV的耦合,很難完全避免。V依賴P,P又要持有V的介面型別,即便網上某些設計採用了各種方式極力避免(弱引用等等) ……
那是否mvp就一定要達到一種完全解耦的效果呢?也不一定,作者從業這幾年,也拜讀過一些元件原始碼,發現一個問題,coding在封裝程式碼的時候,往往會過度追求完美(技術流往往很執著),有時候一段很簡單的程式碼,被設計的很複雜,層層封裝,層層回撥 …… 其實有時候還是適當為好吧,也要考慮開發成本,效能,程式碼可讀性,如果是作為公共元件,還要考慮學習成本,外觀介面是否便於外部呼叫等等因素(僅個人愚見,不喜勿噴,也許是因為作者程式碼能力的確有限,達不到而下意識給自己找了個藉口吧)。
以最直接的方式,最簡單的策略,儘可能高效滴實現需求。這是我對coding的理解。
上述淺見,作者在自己的mvp框架內,做了如下調整:
3.1. mvp策略微調
3.1.1.PV之間通訊採用EventBus訊息機制替換之前的介面回撥機制。
V單項依賴於P,避免P層持有V物件引用,P回饋給V通過EventBus封裝訊息實現。 流程就是V向P發處理命令(V呼叫P的方法),P做業務處理後,向Module層索要資料,M層獲取資料之後介面反饋給P,P針對不同業務做資料二次加工(這個部分也可以放在M,來分擔P的壓力)。P將執行結果(資料,命令)封裝成訊息(Event),通過EventBus傳送給V進行處理。(考慮到程式碼可讀性,可追溯性,訊息機制僅限於P—>V的部分,並沒有用EventBus實現PV雙向通訊)
綜述就是,在PV之間加了EventBus訊息機制,由雙向依賴變成了單項依賴,保留了業務請求介面(V—>P)
(1) P層(BaseEvent)
Base Library在P層定義了Event基類,通過成員變數eventTag區分訊息目的UI執行緒(訊息應該傳送給哪個View,eventTag值一般為View的包名 + 類名 字串構成),通過成員變數actionId區分同一UI執行緒不同型別的邏輯操作(登入,忘記密碼,找回密碼,重新整理列表等操作型別)。
public class BaseEvent {
//......
/**
* default action id for Initialization UI thread
*/
public static final int INIT_PROCESS_ACTION_ID = 20181001;
/**
* for different UI thread. default value: name of current class
*/
public String eventTag;
/**
* same UI thread, different action
*/
public int actionId = INIT_PROCESS_ACTION_ID;
//......
}
(2)V層
Base Library在V層提供了統一的Event接收入口,統一接收Event訊息,並通過eventTag過濾,使用者可以在UI執行緒通過接收到的Event的actionId進行行為分發,區別響應。
public abstract class BaseActivity<P extends BasePresenter> extends Activity implements Contract.IView {
// ......
@Subscribe
@Override
public <E extends BaseEvent> void onEventMessageReceive(E event) {
if (null == event || !event.eventTag.equals(getSimpleTag())) {
return;
}
switch (event.actionId) {
case BaseEvent.INIT_PROCESS_ACTION_ID:
// do sth init view
initDataView(event);
break;
default:
onHandleMessage(event);
break;
}
}
// ......
}
其中BaseEvent.INIT_PROCESS_ACTION_ID定義為初始化重新整理UI資料行為,單獨提供訊息處理介面initDataView,其餘型別訊息由onHandleMessage接收處理。
/**
* Init data and refresh view
* @param event BaseEvent
* @param <E> BaseEvent
*/
protected abstract <E extends BaseEvent> void initDataView(E event);
/**
* receive and handle EventBus message
* @param event BaseEvent
* @param <E> BaseEvent
*/
protected abstract <E extends BaseEvent> void onHandleMessage(E event);
優勢:
系統內建這套BaseEvent以及接收入口,通過eventTag區分UI執行緒(view),eventId區分執行緒內實際操作。可大大減少EventMessage(訊息類)的定義數量,改善原來EventBus對每一種訊息,都需要定義一種訊息類的情況。結合對eventId的統一定義和管理,簡化程式碼的同時,也可提高程式碼可讀性。
爭議:
如果使用系統內建這套BaseEvent以及接收入口,會導致每個註冊的觀察者類都會收到傳送的訊息:
public <E extends BaseEvent> void onEventMessageReceive(E event)
是否會影響效能呢?
作者認為微乎其微,可以忽略。策略本身實際是把原來EventBus對註冊特定Message型別的觀察者傳送訊息的點對點機制,變成了廣播。但考慮本身app實際同時有效註冊EventBus的類和方法數量有限,量級可控,效能影響可忽略。
使用者也可以拋棄Base Library內建的BaseEvent,在App層自定義一套EventMessage,並按照常規寫法,在每個註冊EventBus的類中,實現訊息接收程式碼。同時在每個App中對Base Library的BaseActivity定義中間父類,隱藏相關方法,實現對子類的無感知。
public abstract class BaseActivity<P extends BasePresenter> extends library.base.mvp.v.activity.BaseActivity<P> {
// ......
@Override
protected <E extends BaseEvent> void initDataView(E event) {
}
@Override
protected <E extends BaseEvent> void onHandleMessage(E event) {
}
// ......
}
3.1.2.增加對P的快取管理
增加對P的快取管理,制定快取策略(失效策略),有效解決由於手機橫豎屏切換等原因(V重啟生命週期),導致P重新建立,重新請求資料等資源消耗。(稍後詳述)
/**
* cache:packageName + className(V) : Presenter
*/
private LinkedHashMap<String, BasePresenter> presenterCache = new LinkedHashMap<>();
目前對P的快取是對應互動業務邏輯,是按照UI的互動邏輯劃分的,也就是認為PV一 一對應(key:V的package + name; value:P),也就是實際上一個P中只註冊(快取)了一個V(eventTag)
優化目標(此處只提供建議,各位可自行優化擴充套件):
(1)P保持按照UI的互動邏輯劃分,PV實現一對多關係,實現對P的複用(key:P的package + name; value:P),P中將註冊(快取)多個V(eventTag),P與V互動的時候,會對所有快取的eventTag,都發送一遍EventMessage。
(2)P按照專案業務需求劃分,VP實現多對多關係,實現對P的真正複用(key:P的package + name; value:P)。
3.2. 契約類 Contract
契約類 Contract 來統一管理 mvp相關介面,使得某一功能模組的介面能更加直觀的呈現出來,這樣做是有利於後期維護的。
public class Contract {
/**
* @author
* @version 1.0
*/
public interface IView {
@Subscribe
<E extends BaseEvent> void onEventMessageReceive(E event);
}
/**
* @author
* @version 1.0
*/
public interface IPresenter {
void doInitProcess();
}
/**
* @author
* @version 1.0
*/
public interface IModule {
}
/**
* @author
* @version 1.0
*/
public interface IPresenterManager {
BasePresenter getPresenter(String localClassPath);
void addPresenter(String localClassPath, BasePresenter presenter);
}
}
3.3. V層
Base Library對Activity和Fragment分別作了封裝,BaseActivity,BaseAppCompatActivity,BaseFragmentActivity分別對應Activity,AppCompatActivity,FragmentActivity;BaseFragment,BaseFragmentV4分別對應 android.app.Fragment,android.support.v4.app.Fragment。
UI層的封裝沒啥可說的,大家的做法大同小異,僅供參考。
1. Activity
Base類分別做了以下幾件事情:
(1)把基礎行為封裝為模板
// 跟蹤列印當前activity名
LogUtils.i(getSimpleTag(), "sky_dreaming on onCreate activity = "
+ getSimpleTag());
// 載入佈局檔案
if (returnLayoutId() > 0) {
this.setContentView(returnLayoutId());
ButterKnife.bind(this);
}
// 初始化控制元件
initView();
// 特殊控制元件設定監聽器
setListener();
// 如果頁面不需要在onResume生命週期中重新整理資料,在onCreate中初始化載入資料;否則該行為放在onResume中執行
if (!refreshOnResume()) {
/**
* 初始化載入資料
*/
initProcess();
}
(2)提供abstract方法供子類擴充套件
/**
* Init views
*/
protected abstract void initView();
/**
* Init data and refresh view
* @param event BaseEvent
* @param <E> BaseEvent
*/
protected abstract <E extends BaseEvent> void initDataView(E event);
/**
* receive and handle EventBus message
* @param event BaseEvent
* @param <E> BaseEvent
*/
protected abstract <E extends BaseEvent> void onHandleMessage(E event);
/**
* set listener to view
*/
protected abstract void setListener();
/**
* get layout id
* @return layout resource id
*/
protected abstract int returnLayoutId();
/**
* create Presenter
* @return P
*/
public abstract P createPresenter();
/**
* if this view need EventBus
* @return boolean
*/
protected abstract boolean needEventBus();
(3)開放Presenter註冊以及解注方法
由Application在Activity生命週期回撥方法中,呼叫registerPresenter()完成對P的建立和繫結以及解綁
@SuppressWarnings("unchecked")
public void registerPresenter() {
if (needEventBus()) {
registerEventBus();
}
mPresenter = (P) PresenterManager.getInstance().getPresenter(getSimpleTag());
if (null == mPresenter) {
mPresenter = createPresenter();
PresenterManager.getInstance().addPresenter(getSimpleTag(), mPresenter);
}
mPresenter.registerViewEvent(getSimpleTag());
}
1)根據使用者需求,選擇是否註冊EventBus元件
2)從快取(PresenterManager)嘗試獲取presenter物件,如果獲取不到,建立一個,同時將新建立的物件納入快取。
3)把當前UI執行緒的標識TAG註冊給P(PV通過EventBus通訊,TAG用於區分目的UI執行緒)。
同時將建立P的方法createPresenter();開放給子類使用者實現。
解綁:unRegisterPresenter();
public void unregisterPresenter() {
if (null != mPresenter) {
mPresenter.unRegisterViewEvent(getSimpleTag());
}
unregisterEventBus();
}
(4)開放通用行為
統一接收Event訊息,並通過eventTag過濾,通過eventId分發
@Subscribe
@Override
public <E extends BaseEvent> void onEventMessageReceive(E event) {
if (null == event || !event.eventTag.equals(getSimpleTag())) {
return;
}
switch (event.actionId) {
case BaseEvent.INIT_PROCESS_ACTION_ID:
// do sth init view
initDataView(event);
break;
default:
onHandleMessage(event);
break;
}
}
獲取當前activity標識(包名 + 類名)
public String getSimpleTag() {
return getPackageName() + this.getClass().getSimpleName();
}
跳轉到指定頁面
public void startNextActivity (Intent intent, boolean needForResult,
int requestCode, int enterAnim, int exitAnim, boolean needFinishCurrent) {
if (needForResult) {
startActivityForResult(intent, requestCode);
} else {
startActivity(intent);
}
if (enterAnim > 0 && exitAnim > 0) {
overridePendingTransition(enterAnim, exitAnim);
}
if (needFinishCurrent) {
finish();
}
}
3.3.2. Fragment
Fragment在上述Activity封裝策略的基礎上,加了懶載入機制:
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
/**
* (for lazy loading data)
*/
isViewInitiated = true;
prepareRequestData();
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
/**
* (for lazy loading data)
*/
prepareRequestData();
}
/**
* prepare to get data (for lazy loading data)
*
* @return
*/
public boolean prepareRequestData() {
// set param true: force update data when fragment visible
return prepareRequestData(false);
}
/**
* prepare to get data (for lazy loading data)
*
* @param forceUpdate true: force update data when fragment visible
* @return
*/
public boolean prepareRequestData(boolean forceUpdate) {
if (getUserVisibleHint() && isViewInitiated && (!isDataLoaded || forceUpdate)) {
initProcess();
isDataLoaded = true;
return true;
}
return false;
}
3.4. P層
3.4.1. Presenter
(1)BasePresenter
Presenter對應互動業務邏輯,是按照UI的互動邏輯劃分的(當前PV一對一設計)。
P持有V的唯一標識(包名 + 類名)的快取列表(當前每個P只快取一個V的標識),通過新增,移除V標識,實現在P對V的註冊解注。P通過EventBus傳送帶有V唯一標識的Event訊息給V,V通過識別唯一標識來判斷接收到的訊息歸屬。
定義了V唯一標識的快取列表
/**
* cache event tag
*/
private List<String> eventTagCache = new ArrayList<>();
提供了註冊,解注的開放介面
/**
* create relationship with view
* @param eventTag event tag
*/
public void registerViewEvent(String eventTag) {
if (!isViewRegistered(eventTag)) {
validTime = System.currentTimeMillis();
eventTagCache.add(eventTag);
}
}
/**
* detach the relationship
*/
public void unRegisterViewEvent(String eventTag) {
if (isViewRegistered(eventTag)) {
eventTagCache.remove(eventTag);
}
if (eventTagCache.size() == 0) {
invalidTime = System.currentTimeMillis();
}
}
並內建本身失效策略,為外部對P的快取提供策略依據。
/**
* time for registering new event tag of view
*/
private long validTime = 0;
/**
* time for clear new event tag of view
*/
private long invalidTime = 0;
public boolean isEffective() {
return (invalidTime - validTime) / 1000 < PRESENTER_CACHE_TIME;
}
提供傳送EventMessage訊息介面
public <E extends BaseEvent> void postEvent(E event) {
EventBus.getDefault().post(event);
}
public <E extends BaseEvent> void postStickyEvent(E event) {
EventBus.getDefault().postSticky(event);
}
(2)PresenterManager
P快取管理類,內建快取池,開放get,add介面,同時在每次get呼叫之前,輪詢檢查快取,剔除無效快取。
之所以設計Presenter快取策略,主要考慮兩點因素,一是對多V複用,二是節省資源,避免由於V生命週期異常等原因,導致P重構,浪費資源。之前有考慮把P的生成,P對V的註冊,解注也放到管理類中實現,後來取消了,有心者可以嘗試變更。
public class PresenterManager implements Contract.IPresenterManager {
/**
* cache
*/
private LinkedHashMap<String, BasePresenter> presenterCache = new LinkedHashMap<>();
// ......
/**
* @param localClassPath packageName + className
* @return BasePresenter
*/
public BasePresenter getPresenter(String localClassPath) {
/*
* clear Invalid presenter
*/
for (Map.Entry<String, BasePresenter> entry : presenterCache.entrySet()) {
if (null == entry.getKey() || null == entry.getValue()) {
presenterCache.remove(entry.getKey());
continue;
}
if (!entry.getValue().isEffective() && !entry.getKey().equals(localClassPath)) {
presenterCache.remove(entry.getKey());
}
}
if (presenterCache.containsKey(localClassPath)) {
return presenterCache.get(localClassPath);
} else {
return null;
}
}
/**
* @param localClassPath packageName + className
* @param presenter BasePresenter
*/
public void addPresenter(String localClassPath, BasePresenter presenter) {
if (null == localClassPath || null == presenter) {
return;
}
presenterCache.put(localClassPath, presenter);
}
// ......
}
3.4.2. Event
Event包提供了EventMessage的基類BaseEvent,定義了eventTag,eventId關鍵欄位,用於區分UI執行緒以及業務操作。BaseDataEvent增加了對Data資料的承載,用於需要傳輸資料的場景。
按照計劃,App所有與mvp相關的訊息體,都會繼承BaseEvent或者BaseDataEvent,為UI執行緒統一接收訊息提供支援,可以大大減少EventMessage類的定義數量,簡化程式碼。
public class BaseEvent {
/**
* default action id for Initialization UI thread
*/
public static final int INIT_PROCESS_ACTION_ID = 20181001;
/**
* for different UI thread. default value: name of current class
*/
public String eventTag;
/**
* same UI thread, different action
*/
public int actionId = INIT_PROCESS_ACTION_ID;
// ......
}
public class BaseDataEvent<T> extends BaseEvent {
public T data;
public BaseDataEvent() {
super();
}
public BaseDataEvent(String eventTag) {
super(eventTag);
}
}
3.5. M層
Module對應業務資料管理,是按照資料業務劃分的(一個Module對應並管理多個Model(資料Bean,可以理解為介面,網路介面,資料庫介面等))。
無論對於功能型mvp架構(mvp作為頂層節點,每個M,V,P中進行業務模組劃分)而言,還是對於業務型mvp架構(業務模組作為頂層節點,每個業務節點中做mvp層次劃分)而言,Module就是P的資料管理員(按業務模組劃分),P管理業務,Module管理資料,至於Module把業務中涉及到的資料(Model)存到remote還是local,P並不關心。Module管理當前業務模組中各個Model(Bean)的儲存;Repository負責對資料在物件與儲存介質之間進行校驗,封裝,轉換(可有可無)。
結構說明:P ——> Module ->(Repository ->)(LoginModel –> net,RegisterModel -> local)
Module將被設計為資料儲存層(Model)向上層業務邏輯層(Presenter)提供服務的入口(外觀)。並負責協調Data Repository,管理並加工業務資料。
Base Library對Module的封裝比較簡單,按照MP以介面呼叫及回撥的設計思想,BaseModule做了如下封裝:
(1)快取資料回撥Observers
/**
* model callback observer
*/
private Map<Class, IModelObserver> modelChangeObserver;
說明一下:資料回撥的Observer是針對Model(資料)的,不是針對P的,所以一個P中可能會向Module註冊多個Observer用於接收不同的型別的Model。
(2)對P回撥介面的註冊解注:
/**
* add
*
* @param modelClass model class
* @param modelObserver observer
*/
public <T> BaseModule addModelObserver(Class<T> modelClass, IModelObserver modelObserver) {
if (null == modelChangeObserver) {
modelChangeObserver = new HashMap<>();
}
if (!modelChangeObserver.containsKey(modelClass)) {
modelChangeObserver.put(modelClass, modelObserver);
}
return this;
}
/**
* remove
*
* @param modelClass model class
*/
public <T> void removeModelObserver(Class<T> modelClass) {
if (modelChangeObserver.containsKey(modelClass)) {
modelChangeObserver.remove(modelClass);
}
}
P中初始化Module時,根據P負責處理的業務,把對應不同Model(資料)的回撥介面註冊給Module。
(3)按照Model型別做資料回撥
/**
* call the notify method with model data
*
* @param modelClass model class
* @param models data
*/
@SuppressWarnings("unchecked")
public <T> void notifyModelObserver(Class<T> modelClass, T... models) {
if (modelChangeObserver.containsKey(modelClass)) {
modelChangeObserver.get(modelClass).onNotify(models);
}
}
Module子類中,針對業務需求擴充套件獲取Model(資料)的方法,獲取Model(資料)後,呼叫notifyModelObserver方法把資料回傳給P。
(4)定義資料回撥介面
/**
* @author Created by qiang.hou on 2018/5/9.
* @version 1.0
*/
public interface IModelObserver<T> {
void onNotify(T... data);
}
4,GlobalField
靜態全域性引數,預設配置引數等,不做贅述
public class GlobalField {
public static final String SHARED_PREFERENCE_NAME = "default_shared_preference";
public static final long PRESENTER_CACHE_TIME = 20;
}
總結
Base Library是一個android庫,也是一個完整的android工程,其他主工程app1,app2,app3需要新增Base Library工程依賴。
mvp是一種設計思想,致力於層級解耦,但它也只是一種架構思想,具體實現不應被mvp固定模式所束縛。
Base Library可優化的方向(僅供參考)
1,按照業務邏輯而非UI邏輯劃分P,實現PV多對多,實現對P的充分複用。
2,Module與Model之間,增加Repository,用於對原始資料(remote,localDB)的業務性加工處理。
3,放大EventBus訊息機制的使用範圍,目前只用於P—>V業務回撥部分。
最後,好搶手是子彈喂出來的,好coding是程式碼練出來的,Lib中涉及到的關鍵程式碼(基本上是所有)都已貼出,真正有興趣的不妨整理優化一下,歡迎交流心得體會。