Android狀態機StateMachine

2021-03-02 CODING技術小館

工作中有一同事說到Android狀態機StateMachine。作為一名Android資深工程師,我居然沒有聽說過StateMachine,因此抓緊時間學習一下。
StateMachine不是Android SDK中的相關API,其存在於frameworks層源碼中的一個Java類。可能因為如此,許多應用層的開發人員並未使用過。
因此這裡我們先說一下StateMachine的使用方式,然後再對源碼進行相關介紹。

StateMachine使用舉例

StateMachine原理學習

一、StateMachine使用舉例

StateMachine 處於Android frameworks層源碼frameworks/base/core/java/com/android/internal/util路徑下。應用層若要使用StateMachine需將對應路徑下的三個類拷貝到自己的工程目錄下。
這三個類分別為:StateMachine.java、State、IState

下邊是使用的代碼舉例,這個例子我也是網絡上找的(讀懂StateMachine源碼後,我對這個例子進行了一些簡單更改,以下為更改後的案例):

主要分以下幾個部分來說明:

1.1、PersonStateMachine.java

創建PersonStateMachine繼承StateMachine類。
創建四種狀態,四種狀態均繼承自State:

默認狀態 BoringState

工作狀態 WorkState

吃飯狀態 EatState

睡覺狀態 SleepState

定義了狀態轉換的四種消息類型:

喚醒消息 MSG_WAKEUP

睏乏消息 MSG_TIRED

餓了消息 MSG_HUNGRY

狀態機停止消息 MSG_HALTING

下面來看完整的案例代碼:

public class PersonStateMachine extends StateMachine {

    private static final String TAG = "MachineTest";

    //設置狀態改變標誌常量
    public static final int MSG_WAKEUP = 1; // 消息:醒
    public static final int MSG_TIRED = 2; // 消息:困
    public static final int MSG_HUNGRY = 3; // 消息:餓
    private static final int MSG_HALTING = 4; // 狀態機暫停消息

    //創建狀態
    private State mBoringState = new BoringState();// 默認狀態
    private State mWorkState = new WorkState(); // 工作
    private State mEatState = new EatState(); // 吃
    private State mSleepState = new SleepState(); // 睡

    /**
     * 構造方法
     *
     * @param name
     */
    PersonStateMachine(String name) {
        super(name);
        //加入狀態,初始化狀態
        addState(mBoringState, null);
        addState(mSleepState, mBoringState);
        addState(mWorkState, mBoringState);
        addState(mEatState, mBoringState);

        // sleep狀態為初始狀態
        setInitialState(mSleepState);
    }

    /**
     * @return 創建啟動person 狀態機
     */
    public static PersonStateMachine makePerson() {
        PersonStateMachine person = new PersonStateMachine("Person");
        person.start();
        return person;
    }


    @Override
    protected void onHalting() {
        synchronized (this) {
            this.notifyAll();
        }
    }


    /**
     * 定義狀態:無聊
     */
    class BoringState extends State {
        @Override
        public void enter() {
            Log.e(TAG, "############ enter Boring ############");
        }

        @Override
        public void exit() {
            Log.e(TAG, "############ exit Boring ############");
        }

        @Override
        public boolean processMessage(Message msg) {
            Log.e(TAG, "BoringState  processMessage");
            return true;
        }
    }

    /**
     * 定義狀態:睡覺
     */
    class SleepState extends State {
        @Override
        public void enter() {
            Log.e(TAG, "############ enter Sleep ############");
        }

        @Override
        public void exit() {
            Log.e(TAG, "############ exit Sleep ############");
        }

        @Override
        public boolean processMessage(Message msg) {
            Log.e(TAG, "SleepState  processMessage");
            switch (msg.what) {
                // 收到清醒信號
                case MSG_WAKEUP:
                    Log.e(TAG, "SleepState  MSG_WAKEUP");
                    // 進入工作狀態
                    transitionTo(mWorkState);
                    //...
                    //...
                    //發送餓了信號...
                    sendMessage(obtainMessage(MSG_HUNGRY));
                    break;
                case MSG_HALTING:
                    Log.e(TAG, "SleepState  MSG_HALTING");

                    // 轉化到暫停狀態
                    transitionToHaltingState();
                    break;
                default:
                    return false;
            }
            return true;
        }
    }


    /**
     * 定義狀態:工作
     */
    class WorkState extends State {
        @Override
        public void enter() {
            Log.e(TAG, "############ enter Work ############");
        }

        @Override
        public void exit() {
            Log.e(TAG, "############ exit Work ############");
        }

        @Override
        public boolean processMessage(Message msg) {
            Log.e(TAG, "WorkState  processMessage");
            switch (msg.what) {
                // 收到 餓了 信號
                case MSG_HUNGRY:
                    Log.e(TAG, "WorkState  MSG_HUNGRY");
                    // 吃飯狀態
                    transitionTo(mEatState);
                    //...
                    //...
                    // 發送累了信號...
                    sendMessage(obtainMessage(MSG_TIRED));
                    break;
                default:
                    return false;
            }
            return true;
        }
    }

    /**
     * 定義狀態:吃
     */
    class EatState extends State {
        @Override
        public void enter() {
            Log.e(TAG, "############ enter Eat ############");
        }

        @Override
        public void exit() {
            Log.e(TAG, "############ exit Eat ############");
        }

        @Override
        public boolean processMessage(Message msg) {
            Log.e(TAG, "EatState  processMessage");
            switch (msg.what) {
                // 收到 困了 信號
                case MSG_TIRED:
                    Log.e(TAG, "EatState  MSG_TIRED");
                    // 睡覺
                    transitionTo(mSleepState);
                    //...
                    //...
                    // 發出結束信號...
                    sendMessage(obtainMessage(MSG_HALTING));
                    break;
                default:
                    return false;
            }
            return true;
        }

    }
}

1.2、PersonStateMachine 使用

// 獲取 狀態機引用
PersonStateMachine personStateMachine = PersonStateMachine.makePerson();
// 初始狀態為SleepState,發送消息MSG_WAKEUP
personStateMachine.sendMessage(PersonStateMachine.MSG_WAKEUP);

1.3、案例的簡單說明

幾種狀態的依賴關係如下:

在這裡插入圖片描述

構造方法中,添加所有狀態,並設置初始狀態:

PersonStateMachine(String name) {
    super(name);
    //加入狀態,初始化狀態
    addState(mBoringState, null);
    addState(mSleepState, mBoringState);
    addState(mWorkState, mBoringState);
    addState(mEatState, mBoringState);

    // sleep狀態為初始狀態
    setInitialState(mSleepState);
}

通過以下方法,創建並啟動狀態機:

public static PersonStateMachine makePerson() {
    PersonStateMachine person = new PersonStateMachine("Person");
    person.start();
    return person;
}

1.4、案例源碼下載

Android_StateMachine案例地址

https://github.com/AndroidHighQualityCodeStudy/Android_-StateMachine

二、實現原理學習

在 StateMachine中,開啟了一個線程HandlerThread,其對應的Handler為SmHandler。因此上文案例中對應狀態的 processMessage(Message msg)方法,均在HandlerThread線程中執行。

2.1、首先從`StateMachine`的構造方法說起,對應的代碼如下:

protected StateMachine(String name) {
    // 創建 HandlerThread
    mSmThread = new HandlerThread(name);
    mSmThread.start();
    // 獲取HandlerThread對應的Looper
    Looper looper = mSmThread.getLooper();
    // 初始化 StateMachine
    initStateMachine(name, looper);
}

private void initStateMachine(String name, Looper looper) {
    mName = name;
    mSmHandler = new SmHandler(looper, this);
}

private SmHandler(Looper looper, StateMachine sm) {
    super(looper);
    mSm = sm;

    // 添加狀態:暫停 和 退出
    // 這兩個狀態 無父狀態
    addState(mHaltingState, null);
    addState(mQuittingState, null);
}

mHaltingState狀態,顧名思義讓狀態機暫停,其對應的processMessage(Message msg)方法,返回值為true,將消息消費掉,但不處理消息。從而使狀態機狀態停頓到mHaltingState狀態

mQuittingState狀態,若進入該狀態, 狀態機將退出。HandlerThread線程對應的Looper將退出,HandlerThread線程會被銷毀,所有加入到狀態機的狀態被清空。

2.2、狀態機的start() 方法

狀態機的初始化說完,下邊來說狀態機的啟動方法start()

public void start() {
    // mSmHandler can be null if the state machine has quit.
    SmHandler smh = mSmHandler;
    // StateMachine 未進行初始化,為什麼不拋出一個異常
    if (smh == null) {
        return;
    }
    // 完成狀態機建設
   smh.completeConstruction();
}

private final void completeConstruction() {
    int maxDepth = 0;
    // 循環判斷所有狀態,看看哪一個鏈最長,得出深度
    for (StateInfo si : mStateInfoHashMap.values()) {
        int depth = 0;
        for (StateInfo i = si; i != null; depth++) {
            i = i.parentStateInfo;
        }
        if (maxDepth < depth) {
            maxDepth = depth;
        }
    }
    // 狀態堆棧
    mStateStack = new StateInfo[maxDepth];
    // 臨時狀態堆棧
    mTempStateStack = new StateInfo[maxDepth];
    // 初始化堆棧
    setupInitialStateStack();

    // 發送初始化完成的消息(消息放入到隊列的最前邊)
    sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
}

下邊來具體說setupInitialStateStack();方法中,如何完成棧的初始化。

private final void setupInitialStateStack() {
    // 獲取初始狀態信息
    StateInfo curStateInfo = mStateInfoHashMap.get(mInitialState);
    //
    for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {
        // 初始狀態 放入臨時堆棧
        mTempStateStack[mTempStateStackCount] = curStateInfo;
        // 當前狀態的 所有父狀態 一級級放入堆棧
        curStateInfo = curStateInfo.parentStateInfo;
    }

    // 清空 狀態堆棧
    // Empty the StateStack
    mStateStackTopIndex = -1;
    // 臨時堆棧 換到 狀態堆棧
    moveTempStateStackToStateStack();
}

enter description hereenter description here

到這裡,初始化基本完成,但我們還落下一部分代碼沒有說:

// 發送初始化完成的消息(消息放入到隊列的最前邊)
sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));

下邊來看一下SmHandler的handleMessage(Message msg)方法:

public final void handleMessage(Message msg) {

    // 處理消息
    if (!mHasQuit) {
        // 保存傳入的消息
        mMsg = msg;
        State msgProcessedState = null;
        // 已完成初始化
        if (mIsConstructionCompleted) {
        // ..
        }
        // 接收到 初始化完成的消息
        else if (!mIsConstructionCompleted
                && (mMsg.what == SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) {
            /** Initial one time path. */
            // 初始化完成
            mIsConstructionCompleted = true;
            // 調用堆棧中狀態的enter方法,並將堆棧中的狀態設置為活躍狀態
            invokeEnterMethods(0);
        } else {
        // ..
        }
        // 執行Transition
        performTransitions(msgProcessedState, msg);
    }
}

接收到初始化完成的消息後mIsConstructionCompleted = true;對應的標誌位變過來

執行 invokeEnterMethods方法將mStateStack堆棧中的所有狀態設置為活躍狀態,並由父—&gt;子的順序,執行堆棧中狀態的enter()方法

performTransitions(msgProcessedState, msg);在start()時,其中的內容全部不執行,因此先不介紹。

invokeEnterMethods方法的方法體如下:

private final void invokeEnterMethods(int stateStackEnteringIndex) {
    for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {
        if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName());
        mStateStack[i].state.enter();
        mStateStack[i].active = true;
    }
}

到此start()完成,最終mStateStack堆棧狀態,也如上圖所示。

2.3、狀態轉化

還是拿案例中的代碼舉例:

// 獲取 狀態機引用
PersonStateMachine personStateMachine = PersonStateMachine.makePerson();
// 初始狀態為SleepState,發送消息MSG_WAKEUP
personStateMachine.sendMessage(PersonStateMachine.MSG_WAKEUP);

下邊,再次看一下SmHandler的handleMessage(Message msg)方法:

public final void handleMessage(Message msg) {
    // 處理消息
    if (!mHasQuit) {
        // 保存傳入的消息
        mMsg = msg;
        State msgProcessedState = null;
        // 已完成初始化
        if (mIsConstructionCompleted) {
            // 處理消息的狀態
            msgProcessedState = processMsg(msg);
        }
        // 接收到 初始化完成的消息
        else if (!mIsConstructionCompleted
                && (mMsg.what == SM_INIT_CMD) && (mMsg.obj == mSmHandlerObj)) {
            // 初始化完成
            mIsConstructionCompleted = true;
            // 調用堆棧中狀態的enter方法,並將堆棧中的狀態設置為活躍狀態
            invokeEnterMethods(0);
        } else {
            throw new RuntimeException("StateMachine.handleMessage: "
                    + "The start method not called, received msg: " + msg);
        }
        // 執行Transition
        performTransitions(msgProcessedState, msg);
    }
}

我們來看processMsg(msg);方法:

private final State processMsg(Message msg) {
    // 堆棧中找到當前狀態
    StateInfo curStateInfo = mStateStack[mStateStackTopIndex];
    // 是否為退出消息
    if (isQuit(msg)) {
        // 轉化為退出狀態
        transitionTo(mQuittingState);
    } else {
        // 狀態返回true 則是可處理此狀態
        // 狀態返回false 則不可以處理
        while (!curStateInfo.state.processMessage(msg)) {
            // 當前狀態的父狀態
            curStateInfo = curStateInfo.parentStateInfo;
            // 父狀態未null
            if (curStateInfo == null) {
                // 回調到未處理消息方法中
                mSm.unhandledMessage(msg);
                break;
            }
        }
    }
    // 消息處理後,返回當前狀態信息
    // 如果消息不處理,則返回其父狀態處理,返回處理消息的父狀態
    return (curStateInfo != null) ? curStateInfo.state : null;
}

代碼會直接走到while (!curStateInfo.state.processMessage(msg))
執行mStateStack堆棧中,最上層狀態的 processMessage(msg)方法。案例中這個狀態為SleepState

這裡
如果mStateStack堆棧中狀態的processMessage(msg)方法返回true,則表示其消費掉了這個消息;
如果其返回false,則表示不消費此消息,那麼該消息將繼續向其父狀態進行傳遞;

最終將返回,消費掉該消息的狀態。

這裡,堆棧對上層的狀態為SleepState。所以我們看一下其對應的processMessage(msg)方法。

public boolean processMessage(Message msg) {
    switch (msg.what) {
        // 收到清醒信號
        case MSG_WAKEUP:
            // 進入工作狀態
            transitionTo(mWorkState);
            //...
            //...
            //發送餓了信號...
            sendMessage(obtainMessage(MSG_HUNGRY));
            break;
        case MSG_HALTING:
        // ...
            break;
        default:
            return false;
    }
    return true;
}

我們看一下transitionTo(mWorkState);方法:

private final void transitionTo(IState destState) {
    mDestState = (State) destState;
}

下邊我們回到SmHandler的handleMessage(Message msg)方法:

代碼會執行到SmHandler.handleMessage(Message msg)的performTransitions(msgProcessedState, msg);方法之中。

而這裡我們傳入的參數msgProcessedState為mSleepState。

private void performTransitions(State msgProcessedState, Message msg) {
    // 當前狀態
    State orgState = mStateStack[mStateStackTopIndex].state;
    // ...
    // 目標狀態
    State destState = mDestState;
    if (destState != null) {
        while (true) {
            // 目標狀態 放入temp 堆棧
            // 目標狀態的 父狀態 作為參數 傳入下一級
            StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);
            // commonStateInfo 狀態的子狀態全部退棧
            invokeExitMethods(commonStateInfo);
            // 目標狀態入棧
            int stateStackEnteringIndex = moveTempStateStackToStateStack();
            // 入棧狀態 活躍
            invokeEnterMethods(stateStackEnteringIndex);
            //...
            moveDeferredMessageAtFrontOfQueue();

            if (destState != mDestState) {
                // A new mDestState so continue looping
                destState = mDestState;
            } else {
                // No change in mDestState so we're done
                break;
            }
        }
        mDestState = null;
    }
    // ...
}

此時此刻performTransitions(State msgProcessedState, Message msg)方法中內容的執行示意圖如下:

A、目標狀態放入到mTempStateStack隊列中

// 目標狀態 放入temp 堆棧
// 目標狀態的 父狀態 作為參數 傳入下一級
StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);

1、將WorkState狀態放入到mTempStateStack堆棧中

2、將WorkState狀態的非活躍父狀態一一入mTempStateStack堆棧

3、因為WorkState狀態的父狀態為BoringState,是活躍狀態,因此只將WorkState放入到mTempStateStack堆棧中

4、返回活躍的父狀態BoringState

以上代碼的執行示意圖如下:

enter description hereB、`commonStateInfo`狀態在`mStateStack`堆棧中的子狀態退堆棧

commonStateInfo為setupTempStateStackWithStatesToEnter(destState);方法的返回參數。這裡是BoringState

// commonStateInfo 狀態的子狀態全部退棧
invokeExitMethods(commonStateInfo);

以上代碼的執行示意圖如下:

在這裡插入圖片描述C、`mTempStateStack`全部狀態出堆棧,`mStateStack`入堆棧

// 目標狀態入棧
int stateStackEnteringIndex = moveTempStateStackToStateStack();
// 入棧狀態 活躍
invokeEnterMethods(stateStackEnteringIndex);

最終的堆棧狀態為:

在這裡插入圖片描述

到此StateMachine的源碼講解完成。
感興趣的同學,還是自己讀一遍源碼吧,希望我的這篇文章可以為你的源碼閱讀提供一些幫助。

========== THE END ==========wx_gzh.jpg

相關焦點

  • Spring Statemachine 1.0.0 正式發布
    此版本更新內容如下:Easy to use flat one level state machine for simple use cases.Hierarchical state machine structure to ease complex state configuration.
  • 獨家秘訣:用YAKINDU StatechartTools進行Java開發的圖形狀態機
    Initialize the state machineIPlayerStatemachine statemachine = new PlayerStatemachine();statemachine.init();// 2.
  • JavaScript狀態模式及狀態機模型
    文章大體就兩部分:狀態模式的介紹狀態機模型的函數庫javascript-state-machine的用法和源碼解析場景及問題背景:我們平時開發時本質上就是對應用程式的各種狀態進行切換並作出相應處理。最直接的解決方案是將這些所有可能發生的情況全都考慮到,然後使用if... ellse語句來做狀態判斷來進行不同情況的處理。
  • state machine replication vs primary backup system
    然後zab, vr 經常提到他們的一個primary backup system, 與replicate state machine 還是有不同的. 雖然他們都有state machine, consensus  module, log.
  • SNUG論文巡禮系列二:State Machine Coding styles for Synthesis
    State Machine Classification 根據狀態機輸出生成的方式有兩種類型狀態機, Moore State Machine,輸出僅是當前狀態(present state Two-Always Block State Machine Two-Always狀態機包含生成下一狀態的組合邏輯(next state combinational logic)、生成輸出的組合邏輯(output
  • 認識一下Qt狀態機QStateMachine
    關於Qt狀態機的介紹就懶得說了,網絡上一搜一大堆,反正也看不懂。我關心的就是怎麼使用,畢竟我只是一個編寫應用程式的程式設計師。簡單粗暴地理解一下狀態機就是一個管理很多狀態的機器。組成一個最簡單的狀態機應該包括狀態機(QStateMachine)、狀態(QState)和過渡(QAbstractTransition子類)。狀態機就相當於一個容器,過渡就是將某一個狀態切換到另一個狀態(當然也可以不切換)。
  • 淺談狀態機
    而在建模仿真的領域中,狀態機又是逃不開的一個話題。狀態機(State Machine)是有限狀態自動機的簡稱,是現實事物運行規則抽象而成的一個數學模型。簡單來說,狀態機主要是用來描述事物或者事物間的狀態以及轉換的,舉個例子,現實的事物燈泡,一般來說只有兩個狀態:亮起和熄滅,燈泡的亮起和熄滅取決於開關這個事件,所以這個燈泡抽象成狀態機應該是如下圖所示:這就引出了狀態機中幾個比較重要的概念:狀態、事件、動作、轉換。
  • XState: State 中的戰鬥機,歐耶
    每個 event 可能對應許多不同的 action (行為),並且其中一些 action 還會修改 state (狀態)解決方案: 有限狀態機與狀態圖許多人在學校可能有學習過狀態機的相關概念和學術定義,看學術定義或許理解成本比較高,讓我們來通過例子直觀理解下。有限狀態機包含五個重要部分舉個例子,當我們 fetch 時會返回一個 Promise,這時它進入 pending 狀態。如果它被 resolve,進入 fullfilled 狀態。
  • Unity動畫系統詳解10:子狀態機是什麼?
    子狀態機會顯示為一個細長的六邊形用來和正常的state區分。編輯子狀態機雙擊這個子狀態機可以打開它,界面會顯示這個子狀態機內的內容(初始會是一個空的狀態機)。子狀態機的Transition子狀態機只是從視覺上將一些狀態摺疊到一個子狀態機中,所以如果想轉換到sub-state machine時,需要選擇具體轉換到哪一個狀態或整個狀態機。
  • 狀態機編碼風格(二)之Two-Always狀態機
    可綜合的狀態機有多種編碼方式。最常見的是two-always狀態機和one-always狀態機。 在two-always狀態機中,輸出邏輯在組合always邏輯,或者單獨的assign語句中實現,即two-always狀態機包含present-state時序邏輯、next-state組合邏輯和輸出生成組合邏輯
  • C語言狀態機編程思想
    有限狀態機是一種概念思想,把複雜的控制邏輯分解成有限個穩定狀態,組成閉環系統,通過事件觸發,讓狀態機按設定的順序處理事務。
  • 運用狀態機提高嵌入式軟體效率
    引 言 有限狀態機是根據當前狀態以及觸發條件進行狀態轉換的一種機制,包含一組狀態集(state)、一個起始狀態(start state)、一組輸入符號集
  • 函數指針方法實現簡單狀態機(附代碼)
    之前寫過一篇狀態機的實用文章,很多朋友說有幾個地方有點難度不易理解,今天給大家換種簡單寫法,使用函數指針的方法實現狀態機。狀態機簡介有限狀態機FSM是有限個狀態及在這些狀態之間的轉移和動作等行為的數學模型,是一種邏輯單元內部的高效編程方法,可以根據不同狀態或者消息類型進行相應的處理邏輯,使得程序邏輯清晰易懂。
  • 狀態機的三種騷操作,值得你了解
    value;/*狀態遷移,不遷移則沒有此行*/    break;   case E2:    action_S0_E2(); /*S0 狀態下 E2 事件的響應*/    StateVal = new state value;    break;   .
  • android藍牙框架專題及常見問題 - CSDN
    代碼來源於Android P,本文相關代碼:client:frameworks/base/core/java/android/bluetooth/*system/bt/binder/android/bluetooth/**.aidlservie:framework/base/services/core/java/com/android/server
  • android藍牙相關框架專題及常見問題 - CSDN
    代碼來源於Android P,本文相關代碼:client:frameworks/base/core/java/android/bluetooth/*system/bt/binder/android/bluetooth/**.aidlservie:framework/base/services/core/java/com/android/server/BluetoothService.java
  • 利用74LS161實現複雜狀態機
    打開APP 利用74LS161實現複雜狀態機 發表於 2018-01-18 09:00:02 狀態機的寫法
  • 輕量級Java有限狀態機 Squirrel State Machine 0.3.0 發布
    輕量級Java有限狀態機 Squirrel State Machine 0.3.0 發布,更新內容如下: - 刪除廢棄的API及對象- 提供更加簡易的狀態機行為配置,並且支持配置在StateMachineBuilder上- 支持狀態機Debug模式運行- 增加API, StateMachine.canAccept(E event)- StateMachineData.Reader與StateMachineData.Writer實現Serializable接口https:/
  • 輕量級Java有限狀態機 Squirrel State Machine 0.2.5 發布
    主要實現功能包括: - 提供友好的API來構建狀態機- 提供基於注釋聲明狀態機- 事件驅動的狀態轉移- 狀態機自診斷- 狀態機導出SCXML,DOT文件