前言
- 在一次日常逛知乎的時候發(fā)現(xiàn)了Android Flux,相比與之前的開發(fā),自己還從未接觸到這樣的開發(fā)模式,于是經(jīng)過查閱資料后,自己動手嘗試體驗了一番,并加以總結(jié)記錄。
簡介
- Flux源于Facebook在14年提出的一種Web前端架構(gòu),主要是用來處理復(fù)雜的UI邏輯的一致性問題。經(jīng)過實踐后發(fā)現(xiàn),這種架構(gòu)可以很好的應(yīng)用于Android平臺,相對于其他的MVC/MVP/MVVM等模式,擁有良好的文檔和更具體的設(shè)計,比較適合于快速開發(fā)實現(xiàn)。
核心思想
單向數(shù)據(jù)流
Android Flux
- 如圖所示,F(xiàn)lux的核心思想是單向的數(shù)據(jù)流,所謂單向數(shù)據(jù)流,就是當用戶進行操作的時候,會從View層發(fā)出一個Action,這個Action通過Dispatcher流向Store里面,觸發(fā)Store對狀態(tài)進行改動,然后再由Store觸發(fā)新的狀態(tài)通知到View進行重新渲染。
Action
- 用戶操作界面時會觸發(fā)一個Action,這個Action即是數(shù)據(jù)的封裝,Dispatcher會將這個Action分發(fā)到Store
- Action的創(chuàng)建一般被封裝到一個有語義的Helper類,(ActionCreator),所謂有語義,就是根據(jù)不同的業(yè)務(wù)創(chuàng)建不同的Action,然后把這個Action傳遞到Dispatcher,進行Action的分發(fā)
Dispatcher
- 一個應(yīng)用中只有一個Dispatcher,Dispatcher管理所有的數(shù)據(jù)流,(即內(nèi)部管理的是所有的Store),它是Action與Store聯(lián)系的中心樞紐
- 實際上,它管理的是Store注冊的一系列回調(diào)接口,本身沒有其他的邏輯,僅僅是把Action分發(fā)到Store
- 由此可知,所有的數(shù)據(jù)流必須從這里經(jīng)過,依次分發(fā)到已注冊的Store中
Store
- Store包含應(yīng)用的狀態(tài)和邏輯,通常在此類中實現(xiàn)對Action的邏輯處理,并把處理的結(jié)果通知到View中
- Store會把自己注冊在Dispatcher上并提供一個回調(diào)的接口,用于處理Action,當Dispatcher分發(fā)Action時,內(nèi)部管理的Store就會回調(diào)這個用于處理Action的接口
- 通過Dispatcher發(fā)送Action來更新Store的內(nèi)部狀態(tài),當Store更新后,它會發(fā)送一個事件聲明自己的狀態(tài)已經(jīng)發(fā)送了改變,(通常是View接收),然后View會讀取這些變化并更新自己
View
- 即是Activity和Fragment,負責監(jiān)聽Store發(fā)送的事件并更新界面
- 通常一個Activity對應(yīng)一個Store,但是如果Activity包含許多Fragment,也可以讓每個Fragment對應(yīng)自己的Store
代碼實踐
- 本項目模擬了一個登陸的過程,Activity由兩個EditText和一個Button組成,當username和password均為123時,模擬登陸成功;否則模擬登陸失敗。
Flux Test
Action
/**
* 簡單的POJO類型,只提供兩個字段:type 和 data, 分別記錄Action的類型和數(shù)據(jù)
* Created by ckerv on 16/12/4.
*/
public class Action<T> {
private String type;
private T data;
public Action(String type, T data) {
this.type = type;
this.data = data;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
/**
* Created by ckerv on 16/12/5.
*/
public class LoginAction extends Action<LoginBean> {
public static final String LOGIN_ACTION = "login.action";
public LoginAction(String type, LoginBean data) {
super(type, data);
}
}
- Action是簡單的POJO類型,采用泛型對數(shù)據(jù)進行封裝,只提供了兩個字段
type
和data
,分別記錄Action的類型和數(shù)據(jù) - LoginAction是Action的具體業(yè)務(wù)實現(xiàn),實現(xiàn)非常簡單,只添加了一個Action類型字段
LOGIN_ACTION
Dispatcher
/**
* 一個app通常只有一個Dispatcher類,內(nèi)部進行對Store的管理
* 此類的作用是將action分發(fā)到store,是連接action和store的中心樞紐
* Created by ckerv on 16/12/4.
*/
public class Dispatcher {
private static Dispatcher INSTANCE;
private List<Store> mStores;
private Dispatcher() {
mStores = new ArrayList<>();
}
public static Dispatcher getInstance() {
if(INSTANCE == null) {
synchronized (Dispatcher.class) {
if(INSTANCE == null) {
INSTANCE = new Dispatcher();
}
}
}
return INSTANCE;
}
/**
* 注冊store,即把store添加到list中
* @param store
*/
public void register(Store store) {
this.mStores.add(store);
}
/**
* 注銷store,從list中移除
* @param store
*/
public void unRegister(Store store) {
this.mStores.remove(store);
}
/**
* 分發(fā)action
* @param action
*/
public void dispatch(Action action) {
post(action);
}
/**
* 向注冊的store list依次分發(fā)acion
* @param action
*/
private void post(Action action) {
for (Store store : mStores) {
store.onAction(action);
}
}
}
- Store會在這里注冊自己的回調(diào)接口,Dispatcher會把Action分發(fā)到注冊的Store,所以它會提供一些公有方法來注冊監(jiān)聽和分發(fā)消息
- Dispatcher對外僅暴露3個公有方法:
-
register(final Store store)
用來注冊每個Store的回調(diào)接口 -
unregister(final Store store)
用來接觸Store的回調(diào)接口 -
dispatch(Action action)
用來觸發(fā)Store注冊的回調(diào)接口
-
- 用一個ArrayList來管理Stores,對于一個更復(fù)雜的app可能需要精心設(shè)計store所管理的數(shù)據(jù)結(jié)構(gòu)
Store
/**
* 處理action的基類,通常可根據(jù)不同的業(yè)務(wù)邏輯實現(xiàn){@link #onAction(Action)}
* 通過EventBus傳遞數(shù)據(jù)
* Created by ckerv on 16/12/4.
*/
public abstract class Store {
private static final EventBus mBus = EventBus.getDefault();
protected Store() {
}
public void register(Object view) {
this.mBus.register(view);
}
public void unRegister(Object view) {
this.mBus.unregister(view);
}
/**
* post事件到view層,進行UI的后續(xù)處理
*/
protected void emitStoreChange() {
this.mBus.post(changeEvent());
}
/**
* 處理Action的邏輯,子類必須實現(xiàn)此方法
* @param action
*/
public abstract void onAction(Action action);
/**
* post事件
* @return
*/
protected abstract StoreChangeEvent changeEvent();
public class StoreChangeEvent{}
}
- 這里Store是個抽象類,處理具體業(yè)務(wù)邏輯的子類必須實現(xiàn)
onAction(Action action)
和changeEvent()
方法 - 采用EventBus來向view發(fā)送事件,因此必須提供
register(Object view)
和unRegister(Object view)
方法給外部注冊和注銷EventBus
LoginStore
/**
* Created by ckerv on 16/12/5.
*/
public class LoginStore extends Store {
public LoginStore() {
super();
loginResponseBean = new LoginResponseBean();
}
private LoginResponseBean loginResponseBean;
public LoginResponseBean getLoginResponseBean() {
return loginResponseBean;
}
@Override
public void onAction(Action action) {
switch (action.getType()) {
case LoginAction.LOGIN_ACTION:
LoginBean loginBean = (LoginBean) action.getData();
if(loginBean.getUserName().equals("123") && loginBean.getPassWord().equals("123")) {
loginResponseBean.setSuccess(true);
} else {
loginResponseBean.setSuccess(false);
}
break;
default:break;
}
emitStoreChange();
}
@Override
protected StoreChangeEvent changeEvent() {
return new StoreChangeEvent();
}
}
- 處理登錄邏輯的LoginStore類,繼承自Store,可以看到,在
onAction(Action)
進行登錄邏輯的驗證 - 在登錄邏輯的處理過程中,通過改變
LoginReponseBean
的狀態(tài)來區(qū)分登錄成功與否,處理完邏輯之后,調(diào)用父類的emitStoreChange()
方法發(fā)送事件通知view更新 - 請注意,這里只提供LoginReponseBean的
get
方法,Store內(nèi)部狀態(tài)的更新只能是通過Dispatcher改變
MainActivity
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private EditText mEtUsername;
private EditText mEtPassword;
private Button mBtnLogin;
private ActionCreator mActionCreator;
private Dispatcher mDispatcher;
private LoginStore mStore;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initVariables();
initViews();
}
private void initVariables() {
mStore = new LoginStore();
mDispatcher = Dispatcher.getInstance();
mActionCreator = ActionCreator.getInstance(mDispatcher);
mDispatcher.register(mStore);
}
@Override
protected void onResume() {
super.onResume();
mStore.register(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
mStore.unRegister(this);
}
private void initViews() {
mEtUsername = (EditText) findViewById(R.id.main_et_username);
mBtnLogin = (Button) findViewById(R.id.main_btn_login);
mEtPassword = (EditText) findViewById(R.id.main_et_password);
mBtnLogin.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.main_btn_login :
mActionCreator.login(mEtUsername.getText().toString(), mEtPassword.getText().toString());
break;
default : break;
}
}
@Subscribe
public void onChangeEvent(Store.StoreChangeEvent changeEvent) {
if(mStore.getLoginResponseBean().isSuccess()) {
Toast.makeText(MainActivity.this, "登錄成功", Toast.LENGTH_LONG).show();
} else {
Toast.makeText(MainActivity.this, "登錄失敗", Toast.LENGTH_LONG).show();
}
}
}
- 一般說來,一個Activity有多少個業(yè)務(wù)就有多少個Store
- 在
initVariables()
進行Store、Dispatcher和ActionCreator的初始化和注冊 - 在
onResume()
和onDestroy()
方法里進行EventBus的注冊和注銷 - 當用戶點擊登錄按鈕時,由ActionCreator調(diào)用具體的業(yè)務(wù)方法
login(String username, String password)
使Dispatcher分發(fā)LoginAction到LoginStore,當LoginStore處理完LoginAction發(fā)送事件到MainActivity,MainActivity層接收到事件,獲取LoginStore的狀態(tài),更新界面。
流程總結(jié)
流程總結(jié)
小結(jié)
- 以上是我對Android Flux開發(fā)模式的第一次實踐,可以看到,采用Android Flux的開發(fā)模式有如下好處:
- View層只負責渲染界面和觸發(fā)Action,具體業(yè)務(wù)邏輯由Store實現(xiàn),高度解耦
- 要理解一個Store可能發(fā)生的狀態(tài)變化,只看
onAction(Action action)
中的邏輯處理即可 - 由于數(shù)據(jù)(Action)是單向流動,因此Debug的時候變得輕松很多,可以快速定位Bug的發(fā)生地點
- 以上是個人對于Anroid Flux的理解 ,有不足之處望指出一起討論,一起學(xué)習(xí)~O(∩_∩)O哈哈