這是我在掘金的第一篇文章,本着學習技術就是爲了能夠落地實踐的目的,我從原先的博客搬運到這裏,但願一篇小文章能給你們帶來一點幫助。html
做爲java設計模式中常見的行爲型設計模式,一問到你們就說java
知道嘛,就是上下文裏面切換狀態嘛,不一樣狀態幹不一樣事情嘛程序員
那具體呢,怎樣個落地呢,又是這樣的說法數據庫
這個無法用在咱們項目裏,咱們項目太大了,一改很麻煩。不少問題的,不適合設計模式
就巴拉巴拉一堆不知道或者不想落地到生產環境裏。平時學是學了,可是你們都知道技術這種東西,特別是程序員的事情,沒得投入生產環境進行有效產出,都是假技術。bash
好,咱們上來百度搜一下「java 設計模式」,上網一搜,嘿嘿,一堆結果,你們都能找到,咱們上一張圖先,哇,一看親媽爆炸還沒穿復活甲,這什麼玩意。 服務器
談完了前面的理論讓咱們如今進入生產環境,接下來我將模擬你們項目中常見狀況,作我們狀態模式的實際應用介紹.包括雙重狀態和多狀態模式的管理.掌聲有請,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操做便可。
通過前面的一番演示,可能對你們實現如何在生產環境中應用狀態模式有了一個更清楚的瞭解,知道如何落地,畢竟學了要有產出這個是我學習一切基礎的目的,也是鞏固所學的一個好方式。那如今咱們來複盤一下狀態模式有什麼好處呢。 根據在菜鳥教程上所搜索到的下面這幾個點,你們均可以在這個講解中找到對照。
好,但願上面的介紹能對你們有些許用戶,若是有哪些不夠清楚的地方也歡迎跟我討論,讓我作的更好,感謝。
本篇參考內容有,若是有侵權冒犯的地方請與我聯繫