android生产环境下状态模式应用及分析

这是我在掘金的第一篇文章,本着学习技术就是为了能够落地实践的目的,我从原先的博客搬运到这里,但愿一篇小文章能给你们带来一点帮助。html

状态模式

做为java设计模式中常见的行为型设计模式,一问到你们就说java

知道嘛,就是上下文里面切换状态嘛,不一样状态干不一样事情嘛程序员

那具体呢,怎样个落地呢,又是这样的说法数据库

这个无法用在咱们项目里,咱们项目太大了,一改很麻烦。不少问题的,不适合设计模式

就巴拉巴拉一堆不知道或者不想落地到生产环境里。平时学是学了,可是你们都知道技术这种东西,特别是程序员的事情,没得投入生产环境进行有效产出,都是假技术。bash

好,咱们上来百度搜一下“java 设计模式”,上网一搜,嘿嘿,一堆结果,你们都能找到,咱们上一张图先,哇,一看亲妈爆炸还没穿复活甲,这什么玩意。 服务器

状态模式UML图
来,我给你们分析一下。演示类或者 上下文类持有一个状态的引用state,可是这个 引用的类型是接口类型,该引用的 具体赋值交给set方法进行。而这个接口到底定义了什么,doAction行为,那其实就是交给不一样的扩展类进行扩展, 不一样的扩展类对于行为方法有不一样的实现。 有的人一听这不就是多态么,又想到里氏替换。不,这是多态的一种体现,但不是里氏替换,这个叫依赖倒转,由于这里不强调一个子类扩展后对父类原有功能的扩展。好扯远了,说回如今这个类图。 总的来看,这是一种典型的行为型模式。什么意思?即类的行为是基于它的状态改变的,但平时在生产环境中的应用又真的没见到太多,今天在这里我给你们深刻浅出地分享一哈,便于你们改善一下本身的代码质量

实际应用场景

谈完了前面的理论让咱们如今进入生产环境,接下来我将模拟你们项目中常见状况,作我们状态模式的实际应用介绍.包括双重状态和多状态模式的管理.掌声有请,pia pia pia,为何不是papapa?由于水多,水生财。讲了这些铺垫,总算能够上代码了网络

双重状态下绑定和解绑

在开发过程当中,不知道你们是怎么去防止一个服务或者广播被重复绑定注册的。或者说别的场景,应用在用户未登陆的状况下,要使用某些功能须要跳转到登陆界面,而不是具体的功能页。这里你们脑海里已经有个大概的思路了,但我想在座的各位确定见过这种代码app

private boolean mReceiverTag = false;
private void initBroadcastReceiver(Context context) {
        if (context == null || mReceiverTag) {
            return;
        }
        mNetworkConnectChangedReceiver = new NetworkConnectChangedReceiver();
        IntentFilter filter = new IntentFilter();
        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        context.registerReceiver(mNetworkConnectChangedReceiver, filter);
        mReceiverTag = true;
    }
复制代码

是否是以为似曾相识?用一个变量来做为标记位。那么一样道理,怎么防止重复解绑呢?键盘一顿操做ide

public void unRegister() {
        if (mReceiverTag) {
            getActivity().unregisterReceiver(mNetworkConnectChangedReceiver);
			mReceiverTag = false;
        }
    }
复制代码

爽不爽,是挺爽的,代码简单易懂

场景分析

如今咱们来看下这种方案的一个优势,绑定与否只向一个标记位变量进行get获取,不须要关注太多。可是随之的问题就来,我注册绑定时候改变,注销解绑的时候也须要作对应改变。若是中间有其余操做的话,我也须要改变,那变来变去,就出现个问题,万一我哪天发现出问题了,我就一个一个状况去断点,看何时会致使这个变量的标记变了,致使重复绑定解绑。是能解决问题,可是,太慢了。那本来今天的活怎么办,没办法加班,一天又那么过去本身也累。那么怎么解决这种问题,状态模式!!!

解决方案

定义一个状态基类或者接口

推荐用接口方便解耦。从这里看就有一个注册和绑定的行为。咱们将这个接口对象交给上下文去持有,也就是上下文中会持有一个IRegisteredState类型的变量

public interface IRegisteredState {
    void registerReceiver(Context context, BroadcastReceiver receiver);

    void unregisterReceiver(Context context, BroadcastReceiver receiver);
}
复制代码

到这里咱们能够开始相关的行为,在该执行注册的时候调用对应registerReceiver方法,注销同理。可是这里会问,不是没赋值么,NPE了。别急,这里咱们就来讲说赋值,前面提到咱们依靠set方法对这个引用进行赋值,那赋的值就应该是IRegisteredState接口的的扩展对象。再定义一个RegistedState跟UnRegistedState类,对对应的行为方法进行实现。

状态接口扩展
public class UnRegistedState implements IRegisteredState {
    @Override
    public void registerReceiver(Context context, BroadcastReceiver receiver) {
        IntentFilter filter = new IntentFilter();
        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
        filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        context.registerReceiver(receiver, filter);
    }

    @Override
    public void unregisterReceiver(Context context, BroadcastReceiver receiver) {
        LogUtil.log("unregisterReceiver","未注册状态下对解注操做忽略");
    }
}

public class RegistedState implements IRegisteredState {
    @Override
    public void registerReceiver(Context context, BroadcastReceiver receiver) {
        LogUtil.log("unregisterReceiver","注册状态下对绑定操做忽略");
    }

    @Override
    public void unregisterReceiver(Context context,BroadcastReceiver receiver) {
        context.unregisterReceiver(receiver);
    }
}
复制代码

那么到这里我想你猜的七七八八了,就是在未绑定的状况下对IRegisteredState引用赋值UnRegistedState对象,若是已经绑定了,就赋值RegistedState对象。由不一样的状态对象去作该状态的对应行为。对于上下文对象自己而言,就只知道IRegisteredState引用进行了替换,对外的方法不管是registerReceiver仍是unregisterReceiver,都是交给IRegisteredState引用去执行。具体行为交给具体类。

对应时机替换状态
public void registerReceiver(Context context) {
        if (context != null) {
            mReceiverState.registerReceiver(context, mNetworkConnectChangedReceiver);
            setReceiverState(new RegistedState());
        }
    }

    public void unregisterReceiver(Context context) {
        if (context !=null){
            mReceiverState.unregisterReceiver(context, mNetworkConnectChangedReceiver);
            setReceiverState(new UnRegistedState());
        }
    }
复制代码

那么从上面的逻辑看,当咱们完成一次注册之后,屡次执行绑定,因为处理对象已经发生改变,因此会获得的结果就是“注册状态下对绑定操做忽略”。屡次注销同理获得"未注册状态下对解注操做忽略"。这样依赖咱们只须要关注State引用的值是不是对应当前状态,该值内部是否作的是对应当前状态的行为便可。不须要再使用标记变量去引导当前的逻辑判断。毕竟状态模式的特色就是帮助消除多余的if...else 等条件选择语句

多重状态下界面更新

同样回归到开发过程当中,当咱们再也不只是上面只管理开关状态,而是须要大量的状态并同时根据状态的改变来更新UI时候。状态模式也是个好的方案。好比说,当咱们从文章或者我的资料的阅读模式,变成编辑模式,有的人会选择启动一个新的activity或者fragment,或者稍加考虑会选择打开一个大点的dialog来解决。又好比说,项目里面经常有加载中,加载完成,空状态和错误状态的作法。怎么作的呢,来我晒一下大概的代码,具体你们脑部。

override fun showLoading() = activity?.runOnUiThread {
        LogUtils.i("${this::class.java.simpleName} showLoading")
        (viewModel as BaseViewModel<*>).loadingState.postValue(true)
        (viewModel as BaseViewModel<*>).loadedState.postValue(false)
        (viewModel as BaseViewModel<*>).emptyState.postValue(false)
        (viewModel as BaseViewModel<*>).errorState.postValue(false)
    }

    override fun showContentView() = activity?.runOnUiThread {
        LogUtils.i("${this::class.java.simpleName} showContentView")
        (viewModel as BaseViewModel<*>).loadingState.postValue(false)
        (viewModel as BaseViewModel<*>).loadedState.postValue(true)
        (viewModel as BaseViewModel<*>).emptyState.postValue(false)
        (viewModel as BaseViewModel<*>).errorState.postValue(false)
    }

    override fun showError() = activity?.runOnUiThread {
        LogUtils.i("${this::class.java.simpleName} showError")
        (viewModel as BaseViewModel<*>).loadingState.postValue(false)
        (viewModel as BaseViewModel<*>).loadedState.postValue(false)
        (viewModel as BaseViewModel<*>).emptyState.postValue(false)
        (viewModel as BaseViewModel<*>).errorState.postValue(true)
    }

    override fun showEmpty() = activity?.runOnUiThread {
        LogUtils.i("${this::class.java.simpleName} showEmpty")
        (viewModel as BaseViewModel<*>).loadingState.postValue(false)
        (viewModel as BaseViewModel<*>).loadedState.postValue(false)
        (viewModel as BaseViewModel<*>).emptyState.postValue(true)
        (viewModel as BaseViewModel<*>).errorState.postValue(false)
    }
复制代码

场景分析

方案自己没有问题,终归仍是有更好的解决方案。乍看是否是也以为很正常,当展现时候切换对应状态的控件展现或者隐藏,leader一问起来还振振有词

没办法就是状态太多了咱们得想办法优化

可是,若是我状态多了起来,我有10个,那是否是我得有10个方法,而这10个方法里要对10个状态下的view作处理,复杂度10*10?或者说我不当心写漏了show或者hide,我就要去一个个看,是写漏了仍是写错了。

解决方案

要知道,咱们是面向对象的思想,一切皆对象。怎么解决,这里我用前阵子重构公司项目的一个方案做为例子给你们分析一下。多重状态的管理,核心是依赖十六进制进行状态集管理,切换模式使用状态集,子状态判断是否属于该状态来进行子状态的对应行为。拿UI管理来讲,就是根据当前状态集是否包含当前状态来作对应的UI切换。前言铺垫完,再次上代码

为何是十六进制

这里借鉴掘金上的一篇文章对十六进制应用的实践讲解就算不去火星种土豆,也请务必掌握的 Android 状态管理最佳实践。由于当时就是由于看了这篇才有了对项目实践的冲动,十分感谢大佬的分享。

十六进制能够作到

  • 经过状态集的注入,一行代码便可完成模式的切换。
  • 不管再多的状态,都只须要一个字段来存储。状态被存放在 int 类型的状态集中,能够直接向数据库写入或读取。

例如 0x0001,0x0002,而十六进制的计算,咱们能够借助二进制的 “按位计算” 方式来理解,即 与、或、异或、取反等

a & b,a | b,a ^ b,~a

十六进制数 0x0004 | 0x0008,能够理解为

0100 | 1000 = 1100

十六进制 (0x0004 | 0x0008) & 0x0004 能够获得

1100 & 0100 = 0100

因此状态集中包含某状态时,再与上该状态,就会获得非 0 的结果,从而利用这个特性来完成状态管理

定义状态并加入状态集

首先咱们须要定义各个状态,用刚学一阵子的kotlin做为示范,确定会写的不太优雅,可是若是写得很差我将在后面的博客中将这段代码做为重构的一个例子,但愿你们能够看到一块儿交流,最后实现代码质量的优化。好回归正题。 例如说这里我有4个基本状态,对应的控件有展现或者隐藏,那么这里有8个子状态。

companion object {
        const val SHOW_LOADING = 0x00000001
        const val HIDE_LOADING: Int = 0x00000001.shl(1)
        const val SHOW_ERROR: Int = 0x00000001.shl(2)
        const val HIDE_ERROR: Int = 0x00000001.shl(3)
        const val SHOW_EMPTY: Int = 0x00000001.shl(4)
        const val HIDE_EMPTY: Int = 0x00000001.shl(5)
        const val SHOW_CONTENT: Int = 0x00000001.shl(6)
        const val HIDE_CONTENT: Int = 0x00000001.shl(7)

        const val LOADING: Int = (SHOW_LOADING or HIDE_ERROR or HIDE_EMPTY or HIDE_CONTENT)
        const val LOADED: Int = (HIDE_LOADING or HIDE_ERROR or HIDE_EMPTY or SHOW_CONTENT)
        const val EMPTY: Int = (HIDE_LOADING or HIDE_ERROR or SHOW_EMPTY or HIDE_CONTENT)
        const val ERROR: Int = (HIDE_LOADING or SHOW_ERROR or HIDE_EMPTY or HIDE_CONTENT)
    }
复制代码

这里对于状态集的操做上,添加子集使用或,此处kotlin的or对应java的|,移除使用取反,即kotlin的inv对应java的&~。

定义各个子状态下所须要的行为

若是是控件操做,那么就是根据各个子状态对应来作UI的隐藏显示。大概是这样的,这里用了jetpack里的livedata来实现UI更新,可是换成mvp模式里的也是对应的V层回调,细节忽略,我们注意一下思路就能够了

internal val loadingState: MutableLiveData<Boolean> = MutableLiveData()
    internal val errorState: MutableLiveData<Boolean> = MutableLiveData()
    internal val emptyState: MutableLiveData<Boolean> = MutableLiveData()
    internal val loadedState: MutableLiveData<Boolean> = MutableLiveData()
	(viewModel as BaseViewModel<*>).apply {
            loadingState.observe(this@AbsMvvmFragment, Observer { shouldShow ->
                shouldShow?.run {
                    activity?.runOnUiThread {
                        loadingView?.apply {
                            visibility = if (shouldShow) View.VISIBLE else View.GONE
                        }
                    }
                }
            })

            loadedState.observe(this@AbsMvvmFragment, Observer { shouldShow ->
                shouldShow?.run {
                    activity?.runOnUiThread {
                        dataBinding.root.apply {
                            visibility = if (shouldShow) View.VISIBLE else View.GONE
                        }
                    }
                }
            })

            emptyState.observe(this@AbsMvvmFragment, Observer { shouldShow ->
                shouldShow?.run {
                    activity?.runOnUiThread {
                        emptyView?.apply {
                            visibility = if (shouldShow) View.VISIBLE else View.GONE
                        }
                    }
                }
            })

            errorState.observe(this@AbsMvvmFragment, Observer { shouldShow ->
                shouldShow?.run {
                    activity?.runOnUiThread {
                        errorView?.apply {
                            visibility = if (shouldShow) View.VISIBLE else View.GONE
                        }
                    }
                }
            })
        }
复制代码

在完成了各子状态对应的控件操做以后。开始留下问题,这里仍是跟大的状态集没什么联系呀。来,这里开始进行对状态集的管理

管理状态集

一样,咱们管理这个状态。什么意思,当状态改变的时候,咱们判断子状态是否属于这个状态集,来肯定该状态集对应的子状态操做

internal val pageState: MutableLiveData<Int> = MutableLiveData()

(viewModel as BaseViewModel<*>).apply {
            pageState.observe(this@AbsMvvmFragment, observerStatus())
        }

fun observerStatus(): Observer<Int> = Observer { status ->
        status?.run {
            loadingState.postValue(statusEnabled(status, SHOW_LOADING))
            loadedState.postValue(statusEnabled(status, SHOW_CONTENT))
            emptyState.postValue(statusEnabled(status, SHOW_EMPTY))
            errorState.postValue(statusEnabled(status, SHOW_ERROR))
        }
    }

private fun statusEnabled(statuses: Int, status: Int): Boolean = (statuses and status) != 0
复制代码

上面代码意思就是状态集自己发生改变的时候,以下面操做showLoading,showContentView方法对状态集进行修改

override fun showLoading() = activity?.runOnUiThread {
        LogUtils.i("${this::class.java.simpleName} showLoading")
        (viewModel as BaseViewModel<*>).pageState.postValue(BaseViewModel.LOADING)
    }

    override fun showContentView() = activity?.runOnUiThread {
        LogUtils.i("${this::class.java.simpleName} showContentView")
        (viewModel as BaseViewModel<*>).pageState.postValue(BaseViewModel.LOADED)
    }

    override fun showError() = activity?.runOnUiThread {
        LogUtils.i("${this::class.java.simpleName} showError")
        (viewModel as BaseViewModel<*>).pageState.postValue(BaseViewModel.ERROR)
    }

    override fun showEmpty() = activity?.runOnUiThread {
        LogUtils.i("${this::class.java.simpleName} showEmpty")
        (viewModel as BaseViewModel<*>).pageState.postValue(BaseViewModel.EMPTY)
    }
复制代码

对各个负责子状态UI的对象进行回调,回调传回的内容是什么呢,就是子状态的行为是否在这个状态集内。如加载状态的展现与否(下面附上状态集内容)取决于展现加载这个子状态是否在当前发生变化的状态集里面。

const val LOADING: Int = (SHOW_LOADING or HIDE_ERROR or HIDE_EMPTY or HIDE_CONTENT)
复制代码

若是是这个状态集里的子状态,传true,触发前面的回调,loadingView显示。其他状态完成判断后也将判断结果传给对应的回调方法,以此完成各个视图控件的更新。

loadingState.postValue(true)
👇
(viewModel as BaseViewModel<*>).apply {
            loadingState.observe(this@AbsMvvmFragment, Observer { shouldShow ->
                shouldShow?.run {
                    activity?.runOnUiThread {
                        loadingView?.apply {
                            visibility = if (shouldShow) View.VISIBLE else View.GONE
                        }
                    }
                }
            })
复制代码

这里所体现出来的状态模式思想,经过一个状态集,来控制多个子状态。相较以往的设置true跟false有什么区别呢,从代码上,确定是逃不开set(true)或者set(false)的原理。不一样的是,咱们将各个view的设置归于各个状态,展现与否跟其余view的展现与否互不关联。之前会出如今某个状态下set(true)和set(false)混了的状况,如今咱们只关注这个状态下view该怎么作便可。就算删除某个子状态的操做,也只须要完成子状态的移除,不须要去每一个操做view的地方进行移除

这里使用简单的页面状态来进行演示,若是是有更多的状态,如网络失败,服务器失败要求另外的展现,同理,将这个对应状态添加到状态集中或者建立新的状态集,紧接着绑定该子状态对应的UI操做便可。

优点及缺点

通过前面的一番演示,可能对你们实现如何在生产环境中应用状态模式有了一个更清楚的了解,知道如何落地,毕竟学了要有产出这个是我学习一切基础的目的,也是巩固所学的一个好方式。那如今咱们来复盘一下状态模式有什么好处呢。 根据在菜鸟教程上所搜索到的下面这几个点,你们均可以在这个讲解中找到对照。

优点

  • 封装了转换规则
  • 将全部与某个状态有关的行为放到一个类中,而且能够方便地增长新的状态,只须要改变对象状态便可改变对象的行为(两个例子都有体现)
  • 容许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块(根据前面第一个例子)

缺点

  • 状态模式的使用必然会增长系统类和对象的个数(状态增长)
  • 对"开闭原则"的支持并不太好,对于能够切换状态的状态模式,增长新的状态类须要修改那些负责状态转换的源代码,不然没法切换到新增状态,并且修改某个状态类的行为也需修改对应类的源代码(状态集改动)

好,但愿上面的介绍能对你们有些许用户,若是有哪些不够清楚的地方也欢迎跟我讨论,让我作的更好,感谢。

本篇参考内容有,若是有侵权冒犯的地方请与我联系

www.runoob.com/design-patt… juejin.im/post/5d1a14…

相关文章
相关标签/搜索