android源碼中使用的設計模式(行為型--狀態(tài)模式,責任鏈模式)

1.狀態(tài)模式(state)

1.1定義

當一個對象的內在狀態(tài)改變時容許改變其行為。這個對象看起來像是改變了其類

狀態(tài)模式

1.2說明

  • Context:控制類,定義客戶感興趣的接口犁苏,維護一個State子類的實例
  • State:抽象狀態(tài)類或者狀態(tài)接口场绿,定義一個或者以組接口,表示該狀態(tài)下的行為建车。
  • ConcreteStateA,ConcreteStateB:具體狀態(tài)類扩借,每一個具體的類實現抽象State中定義的接口。

PS:和策略模式的區(qū)別

狀態(tài)模式和策略模式的結構幾乎一模一樣,但他們的目的, 本質卻完全不一樣. 狀態(tài)模式的行為是平行的不可替換的. 策略模式的行為是彼此獨立, 可相互替換的. 總結一句話表述: 狀態(tài)模式是把對象的行為包裝在不同的狀態(tài)對象里, 每一個狀態(tài)對象都有一個共同的抽象狀態(tài)基類, 狀態(tài)模式的意圖是讓一個對象在其內部狀態(tài)改變的時候, 其行為也隨之改變

1.3使用場景

  • 一個代碼的行為取決于它的裝填, 并且必須在運行時根據其狀態(tài)改變它的行為
  • 代碼中包含大量與對象狀態(tài)有關的條件語句, 同樣可以去除分支語句的效果

1.4代碼實現

如果登陸成功則可以調用其他的頁面缤至,沒有登陸則需要跳轉到登陸界面的例子

1.狀態(tài)接口

public interface UserState {
    /**
     * 轉發(fā)
     */
    public void forward(Context context);
    
    /**
     * 評論
     */
    public void comment(Context context);
}

2.具體的實現類

登陸過的

public class LoginState implements UserState {
    @Override
    public void forward(Context context) {
      Toast.makeText(context, "跳轉", Toast.LENGTH_LONG).show();
    }

    @Override
    public void comment(Context context) {
        Toast.makeText(context, "評論", Toast.LENGTH_LONG).show();
    }
}

未登陸的

public class LogoutState implements UserState {
    @Override
    public void forward(Context context) {
        gotoLoginActivity(context);
    }

    @Override
    public void comment(Context context) {
        gotoLoginActivity(context);
    }

    private void gotoLoginActivity(Context context) {
        Intent intent = new Intent(context, LoginActivity.class);
        context.startActivity(intent);
    }
}

3.控制類

public class LoginContext {
    
    //用戶狀態(tài)
    UserState userState = new LogoutState();
    
    private LoginContext(){
        
    }
    //單例
    private static class SingleTonHolder 
    {   
        private static LoginContext sLoginContext = new LoginContext();
    }
    
    public static LoginContext getLoginContext() {
        return SingleTonHolder.sLoginContext; 
    }
    
    public void setUserState(UserState userState) {
        this.userState = userState;
     }

    public void forward(Context context) {
        userState.forward(context);
    }

    public void comment(Context context) {
        userState.forward(context);
    }
     
}

4.調用方法

登陸跳轉

1.4優(yōu)缺點

優(yōu)點:狀態(tài)模式將所有與一個狀態(tài)相關的行為放到一個狀態(tài)對象中潮罪,它提供了一個更好的方法來組織與特定狀態(tài)相關的代碼,將繁瑣的判斷換成結構清晰的狀態(tài)類族领斥,在避免代碼膨脹的同時保證了可擴展和可維護性嫉到。

缺點:狀態(tài)模式使用必然增加系統(tǒng)類和對象的個數

1.5 Android源碼對相應實現

WIFI管理就是使用了狀態(tài)模式

在WiFi復雜的調用中, 存在一個State的狀態(tài)類, 它代表了WiFi的某個狀態(tài), 定義如下:

com\android\internal\State.java

public class State implements IState {
    // 進入當前狀態(tài)之后調用該函數
    @Override
    public void enter() {
    }
    
    // 退出該狀態(tài)后改用該函數
    @Override
    public void exit() {
    }  
    
    // 處理消息
    @Override
    public boolean processMessage(Message msg) {
        return false;
    }      
}

狀態(tài)之間并不是可以隨意切換的, 他們有一種層級關系, 這些層級關系StateMachine的構造函數中被定義的, 代碼如下:

com\android\server\WifiStaaMachine.java

   public WifiStateMachine(Context context, String wlanInterface,
                            WifiTrafficPoller trafficPoller) {
                            addState(mDefaultState);
            addState(mInitialState, mDefaultState);
            addState(mSupplicantStartingState, mDefaultState);
            addState(mSupplicantStartedState, mDefaultState);
                addState(mDriverStartingState, mSupplicantStartedState);
                addState(mDriverStartedState, mSupplicantStartedState);
                    addState(mScanModeState, mDriverStartedState);
                    addState(mConnectModeState, mDriverStartedState);
                        addState(mL2ConnectedState, mConnectModeState);
                            addState(mObtainingIpState, mL2ConnectedState);
                            addState(mVerifyingLinkState, mL2ConnectedState);
                            addState(mConnectedState, mL2ConnectedState);
                            addState(mRoamingState, mL2ConnectedState);
                        addState(mDisconnectingState, mConnectModeState);
                        addState(mDisconnectedState, mConnectModeState);
                        addState(mWpsRunningState, mConnectModeState);
                addState(mWaitForP2pDisableState, mSupplicantStartedState);
                addState(mDriverStoppingState, mSupplicantStartedState);
                addState(mDriverStoppedState, mSupplicantStartedState);
            addState(mSupplicantStoppingState, mDefaultState);
            addState(mSoftApStartingState, mDefaultState);
            addState(mSoftApStartedState, mDefaultState);
                addState(mTetheringState, mSoftApStartedState);
                addState(mTetheredState, mSoftApStartedState);
                addState(mUntetheringState, mSoftApStartedState);
        // 初始化模式為mInitialState
        setInitialState(mInitialState);
    }

com\android\internal\StateMachine.java

  protected final void addState(State state, State parent) {
        mSmHandler.addState(state, parent);
    }

在構造函數中調用了addState()函數, 這些函數最終會調用mSmHandler#addState()函數. 這個函數就是在狀態(tài)之間建立一個層級關系, 這是一個樹形的層級關系. 狀態(tài)之間并不是跨越式的轉換, 當前狀態(tài)只能轉換到上一個狀態(tài)或者下一個狀態(tài).

State的類有enter,exit,processMessage三個函數, 進入狀態(tài)之后會調用enter(), 退出時調用exit(), 處理具體消息時調用processMessage(). 而狀態(tài)模式的核心就是當一個對象的內在狀態(tài)改變時允許改變其行為, 所以我們關注processMessage()不同的狀態(tài)下就是依賴這個函數實現不同行為的.

例如: 在請求掃描Wifi時, 如果在初始化狀態(tài)(InitialState)下, 說明Wifi驅動還沒有進行加載和啟動, 掃描的請求會被會被忽略. 而在驅動加載狀態(tài)下, 請求會被添加到延遲處理的消息隊列中, 等待驅動加載完畢進行掃描請求.

2.責任鏈模式(Chain of Responsibility)

2.1 定義

使多個對象都有機會處理請求, 從而避免了請求的發(fā)送者和接收者之間的耦合關系. 將這些對象連成一條鏈, 并沿著這條鏈傳遞該請求, 直到對象處理它為止

責任鏈模式

2.2使用場景

  • 多個對象可以處理一個請求,但是具體由那個處理則在運行中動態(tài)決定
  • 在請求處理者不明確的情況下向多個對象的一個進行提交請求月洛。

2.3 代碼實現

1.處理抽象類

public abstract class Handler {
    //下一個節(jié)點處理者
    protected Handler successor;
    /**
     * 請求處理
     * @param condition 請求條件
     */
    public abstract void handleRequest(String condition);
}

2.具體的實現

public class ConcreteHandler1 extends Handler {

    @Override
    public void handleRequest(String condition) {
        if(condition.equals("ConcreteHandler1")){
            System.out.println("ConcreteHandler1 handler");
        }else{
            successor.handleRequest(condition);
        }
    }
}
public class ConcreteHandler2 extends Handler {

    @Override
    public void handleRequest(String condition) {
        if(condition.equals("ConcreteHandler2")){
            System.out.println("ConcreteHandler2 handler");
        }else{
            successor.handleRequest(condition);
        }
    }
}

3.具體調用

public class Client {
    public static void main(String[] args) {
        ConcreteHandler1 concreteHandler1 = new ConcreteHandler1();
        ConcreteHandler2 concreteHandler2 = new ConcreteHandler2();
        
        concreteHandler1.successor = concreteHandler2;
        concreteHandler2.successor = concreteHandler1;
        
        concreteHandler2.handleRequest("ConcreteHandler1");
    }
}

2.4 Android源碼對應實現

View的事件分發(fā)

事件分發(fā)
2.5.1 Activity分發(fā)

A.activity事件分發(fā)是先調用dispatchTouchEvent

  public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

從代碼可知道:
1.是否執(zhí)行onTouchEvent(ev)取決于getWindow().superDispatchTouchEvent(ev).
2.如果getWindow().superDispatchTouchEvent(ev)返回的是true則onTouchEvent不會執(zhí)行何恶。
3.getWindow()實際上是phonwindow,并在其中調用了mDecor.superDispatchTouchEvent(). DecorView繼承于viewgroup嚼黔,所以實際調用了父類的dispatchTouchEvent().于是進入了ViewGroup的事件分發(fā)细层。

B.ViewGroup事件分發(fā)
ViewGroup的dispatchTouchEvent()方法
1.先設置mFirstTouchTarget = null;

如果ViewGroup的有子元素成功處理惜辑,mFirstTouchTarget就會指向該元素

  // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
            
     /**
     * Clears all touch targets.
     */
    private void clearTouchTargets() {
        TouchTarget target = mFirstTouchTarget;
        if (target != null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while (target != null);
            mFirstTouchTarget = null;
        }
    }

2.默認情況下所有的ViewGroup都是默認不攔截的

  // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
            
public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }

3.然后會查找子控件是否有事件消耗。如果找到了就會設置FirstTouchTarget不為空且設置 alreadyDispatchedToNewTouchTarget = true

  for (int i = childrenCount - 1; i >= 0; i--) {
    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
        // Child wants to receive touch within its bounds.
        mLastTouchDownTime = ev.getDownTime();
         if (preorderedList != null) {
            // childIndex points into presorted list, find original index
                for (int j = 0; j < childrenCount; j++) {
                    if (children[childIndex] == mChildren[j]) {
                        mLastTouchDownIndex = j;
                        break;
                        }
                       }
                   } else {
                        mLastTouchDownIndex = childIndex;
                    }
                     mLastTouchDownX = ev.getX();
                     mLastTouchDownY = ev.getY();
                     newTouchTarget = addTouchTarget(child, idBitsToAssign);
                     alreadyDispatchedToNewTouchTarget = true;
                     break;
                    }
                }

4.dispatchTransformedTouchEvent區(qū)分是本view還是child

 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
    }
  • 當是本view調用父類的dispatchTouchEvent也就是View的dispatchTouchEvent疫赎。
  • 如果是子view這根據情況調用viewgroup或者view的dispatchTouchEvent盛撑。

C.View事件分發(fā)
dispatchTouchEvent

  ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
  • 如果onTouchListener的onTouch方法返回了true,那么view里面的onTouchEvent就不會被調用了捧搞。
  • 如果view為disenable,則:onTouchListener里面不會執(zhí)行抵卫,但是會執(zhí)行onTouchEvent(event)方法
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市胎撇,隨后出現的幾起案子介粘,更是在濱河造成了極大的恐慌,老刑警劉巖晚树,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件姻采,死亡現場離奇詭異,居然都是意外死亡题涨,警方通過查閱死者的電腦和手機偎谁,發(fā)現死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纲堵,“玉大人巡雨,你說我怎么就攤上這事∠” “怎么了铐望?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長茂附。 經常有香客問我正蛙,道長,這世上最難降的妖魔是什么营曼? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任乒验,我火速辦了婚禮,結果婚禮上蒂阱,老公的妹妹穿的比我還像新娘锻全。我一直安慰自己,他們只是感情好录煤,可當我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布鳄厌。 她就那樣靜靜地躺著,像睡著了一般妈踊。 火紅的嫁衣襯著肌膚如雪了嚎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機與錄音歪泳,去河邊找鬼萝勤。 笑死,一個胖子當著我的面吹牛夹囚,可吹牛的內容都是我干的纵刘。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼荸哟,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了瞬捕?” 一聲冷哼從身側響起鞍历,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肪虎,沒想到半個月后劣砍,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡扇救,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年刑枝,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片迅腔。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡装畅,死狀恐怖,靈堂內的尸體忽然破棺而出沧烈,到底是詐尸還是另有隱情掠兄,我是刑警寧澤,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布锌雀,位于F島的核電站蚂夕,受9級特大地震影響,放射性物質發(fā)生泄漏腋逆。R本人自食惡果不足惜婿牍,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望惩歉。 院中可真熱鬧等脂,春花似錦、人聲如沸柬泽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锨并。三九已至露该,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間第煮,已是汗流浹背解幼。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工抑党, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人撵摆。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓底靠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親特铝。 傳聞我的和親對象是個殘疾皇子暑中,可洞房花燭夜當晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內容