先上个演示图
首先问题是什么
传统MVP或者MVVM架构对于页面状态的管理处于散乱状态,用代码来表示:
// 状态在view层里面
class TestActivity:Activity(),TestView{
var name:String = ""
var phone:String = ""
override fun setName(name:String){
tv_name.text = name
}
override fun setPhone(phone:String){
tv_phone.text = phone
}
}
或者另外一种形式:
// 状态在presenter层里面
class TestPresenterImpl:BasePresenter(),TestPresenter{
var name:String = ""
var phone:String = ""
fun submit(){
val result = Http.submit(name,phone)
view?.setResult(result)
}
}
class TestActivity:Activity(),TestView{
var presenter = TestPresenterImpl()
override fun setName(name:String){
presenter.name = name
}
override fun setPhone(phone:String){
presenter.phone = phone
}
fun submitClick(view:View){
presenter.submit()
}
}
可以看见,即使将所有状态声明在一处(比如类开始位置)依然是离散混乱的,因此状态管理概念就出现了,可以百度redux(状态管理的典型代表)
安卓的状态实现的一般做法
这里就不重复描述了,推荐一些文章吧
从状态管理(State Manage)到MVI(Model-View-Intent)
[译]为什么使用MVI模式(MVI编写响应式安卓APP入门系列第一部分MODEL)
MVI的改进
看了上面这些文章后,要应用到实际项目中特别是递进式改造现有传统MVP项目的话,就有一些痛苦的地方:
- 这些实现都需要对底层架构大动,改了基类presenter或者基类viewmodel后,所有子类全部需要修改,而且往往view实现也要修改,emmm......
- 这些文章里面都讲了状态合并(
reduce操作),但是实际开发中为了尽可能保持view的函数功能单一,往往会分解尽可能职责单一的方法,请看下面两种方式作为对比:
fun render(state:TotalState){
if(state.name.isNotEmpty(){
tv_name.text = state.name
}
if(state.phone.isNotEmpty()){
tv_phone.text = state.phone
}
if(state.isError){
toast("出错了")
}
}
fun setName(name:String){
tv_name.text = name
}
fun setPhone(phone:String){
tv_phone.text = phone
}
fun showErr(){
toast("出错了")
}
可见,不论是从维护角度还是从单元测试用例编写来说,第2种都要明显优于第一种实现
改进思路
-
想要不变动基础传统架构基础上给页面加上状态管理的能力,常见的一般是加注解或者AOP,笔者这里选择APT编译期生成代码的方式
-
要想尽量细化view层函数职责,那么就需要对整个页面状态分解并单独监听
编译期生成状态管理
想法是这样:给activity或者fragment通过注解方式添加代表页面状态的类和状态处理的类,大概这样子:
@BindState(state = MainState::class, agent = MainAgent::class)
class MainActivity : AppCompatActivity(),MainView{
lateinit var presenter: MainPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ShadowState.bind(this)
presenter = MainPresenter()
}
}
data class MainState(
val name: String,
val amendInternal: Int = 0,
val childState:ChildState = ChildState(),
val listStates:List<String> = listOf()
){
data class ChildState(
val gender:String = "man"
)
}
class MainAgent : StateAgent<MainState, MainView>() {
override fun initState(bundle: Bundle?): MainState =
MainState(name = "hahah",listStates = listOf("1","2"))
override fun conf() {
//监听状态变化,调用view相关方法
}
}
然后在相应的create生命周期bind一下,即将view与对应的状态类关联起来,然后在presenter或者viewModel里面直接注入对应的状态代理,即可直接操作状态:
class MainPresenter {
init {
ShadowState.injectDispatcher(this)
}
@InjectAgent
lateinit var agent: MainAgent
fun changeName(){
agent.setState { it.copy(name = it.name+"++") }
}
}
这里笔者思路分为三步:
- 通过编译期代码生成,解析出所有
BindState注解,将其state属性和agent属性与其对应的View类(比如MainActivity)形成关系图 - 通过
bind方法,对view实例进行事件流监听,当然因为还涉及安卓里的生命周期相关问题,这里只接受类型为LifecycleOwner的view - 通过
injectDispatcher方法,注入关系图中对应的代理实例
这里就展示下编译期生成的代码,其他部分直接阅读源码即可
public final class MainStateManager implements StateManager {
Map<Class<?>, Class<?>> stateMap = new HashMap<>();
Map<Class<?>, StateAgent> stateAgentMap = new HashMap<>();
Map<Class<?>, StateBinder> stateBinderMap = new HashMap<>();
public MainStateManager() {
stateMap.put(MainActivity.class,MainState.class);
stateAgentMap.put(MainState.class,new MainAgent());
stateBinderMap.put(MainState.class,new MainStateBinder());
stateMap.put(SecondActivity.class,SecondState.class);
stateAgentMap.put(SecondState.class,new SecondAgent());
stateBinderMap.put(SecondState.class,new SecondStateBinder());
}
@Override
public void bind(LifecycleOwner lifecycleOwner) {
if (getStateClass(lifecycleOwner) == null) {
return;
};
stateBinderMap.get(getStateClass(lifecycleOwner)).observe(lifecycleOwner, getStateAgent(lifecycleOwner));
}
@Override
public void injectAgent(Object instance) {
List<Class<?>> agentClasses = AgentInjection.INSTANCE.getAgents(instance);
for (Class<?> cls : agentClasses) {
for (Map.Entry<Class<?>, StateAgent> entry :
stateAgentMap.entrySet()) {
if (entry.getValue().getClass() == cls) {
AgentInjection.INSTANCE.inject(
instance,
entry.getValue()
);
}
}
};
}
@Override
public StateAgent getStateAgent(LifecycleOwner lifecycleOwner) {
if (getStateClass(lifecycleOwner) == null) {
return null;
};
return stateAgentMap.get(getStateClass(lifecycleOwner));
}
@Override
public Class getStateClass(LifecycleOwner lifecycleOwner) {
if (stateMap.get(lifecycleOwner.getClass()) == null) {
return null;
};
return stateMap.get(lifecycleOwner.getClass());
}
}
public final class MainStateBinder implements StateBinder {
@Override
public void observe(LifecycleOwner owner, StateAgent agent) {
StateObserver observer = agent.createObserver();
if (owner instanceof FragmentActivity) {
observer.getLiveData().setValue((MainState) agent.initState(((FragmentActivity) owner).getIntent().getExtras()));
} else if (owner instanceof Fragment) {
observer.getLiveData().setValue((MainState) agent.initState(((Fragment) owner).getArguments()));
} else {
throw new IllegalArgumentException("");
};
observer.getLiveData().observe(owner, observer);
agent.init(observer);
agent.bindView((MainActivity) owner,observer);
}
}
状态分解单独监听
直接看例子:
class MainAgent : StateAgent<MainState, MainView>() {
override fun initState(bundle: Bundle?): MainState =
MainState(name = "hahah",listStates = listOf("1","2"))
override fun conf() {
listen({ it.name }, {view?.setName(it)})
listen({ it.phone },{view?.setPhone(it)})
....
}
}
通过简单的listen方法,第一个function表达你想监听整个页面状态的哪个属性,第二个function里面表达你拿着这个属性想做什么操作
这里笔者用rxjava的map操作符来实现状态类的分解,具体请参考源码
最后
开局演示图上那个风骚的悬浮窗就是检查器,为了开发过程中随时调试当前页面的状态,有点像chrome浏览器开发者工具里面的那个修改变量的工具,直接ShadowState.openWatcher()即可唤起,可以最小化也可以移动,改了某个值之后不要忘记点保存图标才会反应到页面上
PS
未来考虑加上时光旅行,虽然感觉用的地方不大?emmm......