背景
参考前端学习系列2:从移动端的角度学习与分析Redux一文,在React中Redux框架被用来将展示与数据分离以及管理状态的变化,扩展性非常好,而且便于测试,那我们不禁想到,能不能把这样一套框架移植到Android原生开发中来。
Redux核心框架
上图展示了Redux的数据流向:
当用户操作View时可能引发数据的变化,这时候会经由ActionCreator产生特定的Action传递给Store;
Store在接收到Action后,将其分发给Reducer进行处理,而Reducer内部会根据Action更新对应的数据,产生新的State返回给Store;
Store最终将更新的状态State通知给View,改变UI刷新页面
TODO应用
基础功能实现过程
我们先看下基于Redux框架如何完成添加TODO item以及修改item状态这些基本功能:
定义状态State
在TODO应用中我们首先需要保存一个TODO item的信息列表,因此state应该对应的是List这样一种数据类型
定义交互的Actions
·每一个Action应该包含两部分内容,一部分是Action的类型标识,另一个部分传递的是附加的数据。
public class Action { public final String type; public final Object value; public Action(String type, Object value) {...}
}
具体到本应用,我们需要创建添加和修改item这两种操作对应的Action,为了保证代码的复用和整洁,通常Action的创建要依赖ActionCreator。
public class TodoActions{ public static final String ADD_ITEM = "ADD_ITEM"; public static final String CHANGE_STATE = "CHANGE_STATE"; public static Action addItem(TodoItem item){ return new Action(ADD_ITEM, item);
} public static Action changeState(long id, boolean isChecked){ return new Action(CHANGE_STATE, Pair.create(id, isChecked));
}
}
创建Reducer
在定义好Actions后,我们需要知道如何去处理它们,也就是实现Reducer接口
public interface Reducer{ State reduce(State state, Action action);
}
reduce方法接收两个参数,一个是当前状态,另一个是需要处理的Action。在处理完成后它返回的是更新的状态。
下面的TodoReducer展示了本应用中具体的action处理过程:
public class TodoReducer implements Reducer>{ List addItem(List items, TodoItem item){ return TreePVector.from(items)
.plus(item);
} List changeState(List items, long id, boolean isChecked){ for (int i = 0; i < items.size(); i++) {
TodoItem todoItem = items.get(i); if (todoItem.id == id) {
TodoItem changed = new TodoItem(id, todoItem.text, isChecked); return TreePVector.from(items)
.with(i, changed);
}
} return items;
} @Override public List reduce(List items, Action action){ switch (action.type) { case TodoActions.ADD_ITEM:
TodoItem todoItem = (TodoItem) action.value; return addItem(items, todoItem); case TodoActions.CHANGE_STATE:
Pair pair = (Pair) action.value; long id = pair.first; boolean isChecked = pair.second; return changeState(items, id, isChecked); default: return items;
}
}
}
创建Store
Store是State的容器,一个Store通常得实现以下几个方法
public class Store{ public State getState(); public void dispatch(Object action); public Cancelable subscribe(StateChangeListener listener); public interface StateChangeListener{ void onStateChanged(S state);
}
}
getState(): 获取当前状态
dispatch(): 将action分发给对应的reducer
subcribe(): 注册状态的监听器,如果产生新状态则将其回调出去
这里给出Store的一种实现:
public class Store implements Dispatcher, Cursor{ public static final String INIT_ACTION = "@@reductor/INIT"; private final Reducer reducer; private final Dispatcher dispatcher; private final List> listeners = new CopyOnWriteArrayList<>(); private volatile State state; private Store(Reducer reducer, State initialState, Middleware[] middlewares){ this.reducer = reducer; this.state = initialState;
Dispatcher dispatcher = this::dispatchAction; for (int i = middlewares.length - 1; i >= 0; i--) {
Middleware middleware = middlewares[i];
dispatcher = middleware.create(Store.this, dispatcher);
} this.dispatcher = dispatcher;
dispatchAction(Action.create(INIT_ACTION));
} private void dispatchAction(final Object actionObject){ if (actionObject instanceof Action) { final Action action = (Action) actionObject; synchronized (this) {
state = reducer.reduce(state, action);
} for (StateChangeListener listener : listeners) {
listener.onStateChanged(state);
}
} else { throw new IllegalArgumentException(String.format("action %s of %s is not instance of %s, use custom Middleware to dispatch another types of actions", actionObject, actionObject.getClass(), Action.class));
}
} public static Store create(Reducer reducer, S initialState, Middleware... middlewares){ return new Store<>(reducer, initialState, middlewares);
} public void dispatch(final Object action){
dispatcher.dispatch(action);
} public State getState(){ return state;
} public Cancelable subscribe(final StateChangeListener listener){
listeners.add(listener); return () -> listeners.remove(listener);
}
}
编写界面Activity
有了以上这些组件以后,我们可以很轻松地完成UI界面的编写
public class MainActivity extends AppCompatActivity{
Store> todoStore;
TodoAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); //boilerplate code ... //Creating Store object with empty list as initial state todoStore = Store.create(new TodoReducer(), Collections.emptyList());
todoStore.subscribe(state -> adapter.notifyDataSetChanged());
} void addNote(String text){
TodoItem todoItem = new TodoItem(generateId(), text, false);
todoStore.dispatch(TodoActions.addItem(todoItem));
} void changeState(long id, boolean isChecked){
todoStore.dispatch(TodoActions.changeState(id, isChecked));
}
}
高级功能
Reducer组合
随着业务的累加,将所有action的处理都放到一个Reducer中显然是不合理的,我们可以在Store内部维护多个Reducer,每个Reducer只处理特定的数据。
例如:当要增加根据当前状态筛选所有TODO item的功能时,我们可以先定义新的组合状态
public enum TodoFilter {
ALL,
CHECKED,
UNCHECKED
} public class AppState{ public final List todoItems; public final TodoFilter filter; public AppState(List todoItems, TodoFilter filter){ this.todoItems = todoItems; this.filter = filter;
}
}
而在处理action时再将其分发对应的子reducer处理
public class AppStateReducer implements Reducer{ private Reducer> itemsReducer = TodoReducer.create(); private Reducer filterReducer = FilterReducer.create(); @Override public AppState reduce(AppState state, Action action){
List items = itemsReducer.reduce(state.todoItems, action);
TodoFilter filter = filterReducer.reduce(state.filter, action); return new AppState(items, filter);
}
}
操作回滚
Redux的架构让State的管理保存变得极为方便,也让状态回滚成为可能,在TODO应用中可以这样实现:
public class UndoableReducer implements Reducer{ private final Reducer sourceReducer; public static Action pop(){ return Action.create("POP");
} private LinkedList stack = new LinkedList<>(); public UndoableReducer(Reducer sourceReducer){ this.sourceReducer = sourceReducer;
} @Override public State reduce(State state, Action action){ if (action.type.equals("POP")) { return stack.isEmpty()
? state
: stack.pop();
} else if (!action.type.equals(Store.INIT_ACTION)){
stack.push(state);
} return sourceReducer.reduce(state, action);
}
}
中间件
中间件提供的是位于 action 被发起之后,到达 reducer 之前的扩展点,你可以利用它来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。
以打log为例
public class LogMiddleware implements Middleware{ @Override public Dispatcher create(Store store, Dispatcher nextDispatcher){ return action -> {
Log.d("LogMiddleware", "dispatch action:" + action + ",current state:" + store.getState());
nextDispatcher.dispatch(action);
Log.d("LogMiddleware", "next state:" + store.getState());
};
}
}
Redux的优缺点
优势
View和Model解耦,View变得非常薄,只包含渲染逻辑和触发action两个职责
观察理解数据状态的变化,只用看它注册的所有action就行,排查问题以及做单测非常方便,最牛叉的是可以做到历史回滚
中间件的自由组合
特别适合组件之间有数据交互的情况
按照Redux这种模式写出来的代码风格容易统一
缺点
State通常是一个统一的状态树,如果页面被分拆成了许多相对独立的Fragment或者组件,用统一的状态树去管理容易将问题复杂化
和前端有一堆轮子相比,Android原生开发使用Redux编写过程异常繁琐,需要创建很多类,并且在处理复杂业务逻辑时不如MVP等模式来的直接易懂