Android官方实现的层次状态机Hierarchical State Machine源代码

setInitialState(mS1);

log(“ctor X”);

}

class P1 extends State {

@Override public void enter() {

log(“mP1.enter”);

}

@Override public boolean processMessage(Message message) {

boolean retVal;

log(“mP1.processMessage what=” + message.what);

switch(message.what) {

case CMD_2:

// CMD_2 will arrive in mS2 before CMD_3

sendMessage(obtainMessage(CMD_3));

deferMessage(message);

transitionTo(mS2);

retVal = HANDLED;

break;

default:

// Any message we don’t understand in this state invokes unhandledMessage

retVal = NOT_HANDLED;

break;

}

return retVal;

}

@Override public void exit() {

log(“mP1.exit”);

}

}

class S1 extends State {

@Override public void enter() {

log(“mS1.enter”);

}

@Override public boolean processMessage(Message message) {

log(“S1.processMessage what=” + message.what);

if (message.what == CMD_1) {

// Transition to ourself to show that enter/exit is called

transitionTo(mS1);

return HANDLED;

} else {

// Let parent process all other messages

return NOT_HANDLED;

}

}

@Override public void exit() {

log(“mS1.exit”);

}

}

class S2 extends State {

@Override public void enter() {

log(“mS2.enter”);

}

@Override public boolean processMessage(Message message) {

boolean retVal;

log(“mS2.processMessage what=” + message.what);

switch(message.what) {

case(CMD_2):

sendMessage(obtainMessage(CMD_4));

retVal = HANDLED;

break;

case(CMD_3):

deferMessage(message);

transitionTo(mP2);

retVal = HANDLED;

break;

default:

retVal = NOT_HANDLED;

break;

}

return retVal;

}

@Override public void exit() {

log(“mS2.exit”);

}

}

class P2 extends State {

@Override public void enter() {

log(“mP2.enter”);

sendMessage(obtainMessage(CMD_5));

}

@Override public boolean processMessage(Message message) {

log(“P2.processMessage what=” + message.what);

switch(message.what) {

case(CMD_3):

break;

case(CMD_4):

break;

case(CMD_5):

transitionToHaltingState();

break;

}

return HANDLED;

}

@Override public void exit() {

log(“mP2.exit”);

}

}

@Override

void onHalting() {

log(“halting”);

synchronized (this) {

this.notifyAll();

}

}

P1 mP1 = new P1();

S1 mS1 = new S1();

S2 mS2 = new S2();

P2 mP2 = new P2();

}

  • If this is executed by sending two messages CMD_1 and CMD_2

  • (Note the synchronize is only needed because we use hsm.wait())

Hsm1 hsm = makeHsm1();

synchronize(hsm) {

hsm.sendMessage(obtainMessage(hsm.CMD_1));

hsm.sendMessage(obtainMessage(hsm.CMD_2));

try {

// wait for the messages to be handled

hsm.wait();

} catch (InterruptedException e) {

loge("exception while waiting " + e.getMessage());

}

}

  • The output is:

D/hsm1    ( 1999): makeHsm1 E

D/hsm1    ( 1999): ctor E

D/hsm1    ( 1999): ctor X

D/hsm1    ( 1999): mP1.enter

D/hsm1    ( 1999): mS1.enter

D/hsm1    ( 1999): makeHsm1 X

D/hsm1    ( 1999): mS1.processMessage what=1

D/hsm1    ( 1999): mS1.exit

D/hsm1    ( 1999): mS1.enter

D/hsm1    ( 1999): mS1.processMessage what=2

D/hsm1    ( 1999): mP1.processMessage what=2

D/hsm1    ( 1999): mS1.exit

D/hsm1    ( 1999): mS2.enter

D/hsm1    ( 1999): mS2.processMessage what=2

D/hsm1    ( 1999): mS2.processMessage what=3

D/hsm1    ( 1999): mS2.exit

D/hsm1    ( 1999): mP1.exit

D/hsm1    ( 1999): mP2.enter

D/hsm1    ( 1999): mP2.processMessage what=3

D/hsm1    ( 1999): mP2.processMessage what=4

D/hsm1    ( 1999): mP2.processMessage what=5

D/hsm1    ( 1999): mP2.exit

D/hsm1    ( 1999): halting

*/

public class StateMachine {

// Name of the state machine and used as logging tag

private String mName;

/** Message.what value when quitting */

private static final int SM_QUIT_CMD = -1;

/** Message.what value when initializing */

private static final int SM_INIT_CMD = -2;

/**

  • Convenience constant that maybe returned by processMessage

  • to indicate the the message was processed and is not to be

  • processed by parent states

*/

public static final boolean HANDLED = true;

/**

  • Convenience constant that maybe returned by processMessage

  • to indicate the the message was NOT processed and is to be

  • processed by parent states

*/

public static final boolean NOT_HANDLED = false;

/**

  • StateMachine logging record.

  • {@hide}

*/

public static class LogRec {

private StateMachine mSm;

private long mTime;

private int mWhat;

private String mInfo;

private IState mState;

private IState mOrgState;

private IState mDstState;

/**

  • Constructor

  • @param msg

  • @param state the state which handled the message

  • @param orgState is the first state the received the message but

  • did not processes the message.

  • @param transToState is the state that was transitioned to after the message was

  • processed.

*/

LogRec(StateMachine sm, Message msg, String info, IState state, IState orgState,

IState transToState) {

update(sm, msg, info, state, orgState, transToState);

}

/**

  • Update the information in the record.

  • @param state that handled the message

  • @param orgState is the first state the received the message

  • @param dstState is the state that was the transition target when logging

*/

public void update(StateMachine sm, Message msg, String info, IState state, IState orgState,

IState dstState) {

mSm = sm;

mTime = System.currentTimeMillis();

mWhat = (msg != null) ? msg.what : 0;

mInfo = info;

mState = state;

mOrgState = orgState;

mDstState = dstState;

}

/**

  • @return time stamp

*/

public long getTime() {

return mTime;

}

/**

  • @return msg.what

*/

public long getWhat() {

return mWhat;

}

/**

  • @return the command that was executing

*/

public String getInfo() {

return mInfo;

}

/**

  • @return the state that handled this message

*/

public IState getState() {

return mState;

}

/**

  • @return the state destination state if a transition is occurring or null if none.

*/

public IState getDestState() {

return mDstState;

}

/**

  • @return the original state that received the message.

*/

public IState getOriginalState() {

return mOrgState;

}

@Override

public String toString() {

StringBuilder sb = new StringBuilder();

sb.append(“time=”);

Calendar c = Calendar.getInstance();

c.setTimeInMillis(mTime);

sb.append(String.format(“%tm-%td %tH:%tM:%tS.%tL”, c, c, c, c, c, c));

sb.append(" processed=");

sb.append(mState == null ? “” : mState.getName());

sb.append(" org=");

sb.append(mOrgState == null ? “” : mOrgState.getName());

sb.append(" dest=");

sb.append(mDstState == null ? “” : mDstState.getName());

sb.append(" what=");

String what = mSm != null ? mSm.getWhatToString(mWhat) : “”;

if (TextUtils.isEmpty(what)) {

sb.append(mWhat);

sb.append(“(0x”);

sb.append(Integer.toHexString(mWhat));

sb.append(“)”);

} else {

sb.append(what);

}

if (!TextUtils.isEmpty(mInfo)) {

sb.append(" ");

sb.append(mInfo);

}

return sb.toString();

}

}

/**

  • A list of log records including messages recently processed by the state machine.

  • The class maintains a list of log records including messages

  • recently processed. The list is finite and may be set in the

  • constructor or by calling setSize. The public interface also

  • includes size which returns the number of recent records,

  • count which is the number of records processed since the

  • the last setSize, get which returns a record and

  • add which adds a record.

*/

private static class LogRecords {

private static final int DEFAULT_SIZE = 20;

private Vector mLogRecVector = new Vector();

private int mMaxSize = DEFAULT_SIZE;

private int mOldestIndex = 0;

private int mCount = 0;

private boolean mLogOnlyTransitions = false;

/**

  • private constructor use add

*/

private LogRecords() {

}

/**

  • Set size of messages to maintain and clears all current records.

  • @param maxSize number of records to maintain at anyone time.

*/

synchronized void setSize(int maxSize) {

// TODO: once b/28217358 is fixed, add unit tests to verify that these variables are

// cleared after calling this method, and that subsequent calls to get() function as

// expected.

mMaxSize = maxSize;

mOldestIndex = 0;

mCount = 0;

mLogRecVector.clear();

}

synchronized void setLogOnlyTransitions(boolean enable) {

mLogOnlyTransitions = enable;

}

synchronized boolean logOnlyTransitions() {

return mLogOnlyTransitions;

}

/**

  • @return the number of recent records.

*/

synchronized int size() {

return mLogRecVector.size();

}

/**

  • @return the total number of records processed since size was set.

*/

synchronized int count() {

return mCount;

}

/**

  • Clear the list of records.

*/

synchronized void cleanup() {

mLogRecVector.clear();

}

/**

  • @return the information on a particular record. 0 is the oldest

  • record and size()-1 is the newest record. If the index is to

  • large null is returned.

*/

synchronized LogRec get(int index) {

int nextIndex = mOldestIndex + index;

if (nextIndex >= mMaxSize) {

nextIndex -= mMaxSize;

}

if (nextIndex >= size()) {

return null;

} else {

return mLogRecVector.get(nextIndex);

}

}

/**

  • Add a processed message.

  • @param msg

  • @param messageInfo to be stored

  • @param state that handled the message

  • @param orgState is the first state the received the message but

  • did not processes the message.

  • @param transToState is the state that was transitioned to after the message was

  • processed.

*/

synchronized void add(StateMachine sm, Message msg, String messageInfo, IState state,

IState orgState, IState transToState) {

mCount += 1;

if (mLogRecVector.size() < mMaxSize) {

mLogRecVector.add(new LogRec(sm, msg, messageInfo, state, orgState, transToState));

} else {

LogRec pmi = mLogRecVector.get(mOldestIndex);

mOldestIndex += 1;

if (mOldestIndex >= mMaxSize) {

mOldestIndex = 0;

}

pmi.update(sm, msg, messageInfo, state, orgState, transToState);

}

}

}

private static class SmHandler extends Handler {

/** true if StateMachine has quit */

private boolean mHasQuit = false;

/** The debug flag */

private boolean mDbg = false;

/** The SmHandler object, identifies that message is internal */

private static final Object mSmHandlerObj = new Object();

/** The current message */

private Message mMsg;

/** A list of log records including messages this state machine has processed */

private LogRecords mLogRecords = new LogRecords();

/** true if construction of the state machine has not been completed */

private boolean mIsConstructionCompleted;

/** Stack used to manage the current hierarchy of states */

private StateInfo mStateStack[];

/** Top of mStateStack */

private int mStateStackTopIndex = -1;

/** A temporary stack used to manage the state stack */

private StateInfo mTempStateStack[];

/** The top of the mTempStateStack */

private int mTempStateStackCount;

/** State used when state machine is halted */

private HaltingState mHaltingState = new HaltingState();

/** State used when state machine is quitting */

private QuittingState mQuittingState = new QuittingState();

/** Reference to the StateMachine */

private StateMachine mSm;

/**

  • Information about a state.

  • Used to maintain the hierarchy.

*/

private class StateInfo {

/** The state */

State state;

/** The parent of this state, null if there is no parent */

StateInfo parentStateInfo;

/** True when the state has been entered and on the stack */

boolean active;

/**

  • Convert StateInfo to string

*/

@Override

public String toString() {

return “state=” + state.getName() + “,active=” + active + “,parent=”

  • ((parentStateInfo == null) ? “null” : parentStateInfo.state.getName());

}

}

/** The map of all of the states in the state machine */

private HashMap<State, StateInfo> mStateInfo = new HashMap<State, StateInfo>();

/** The initial state that will process the first message */

private State mInitialState;

/** The destination state when transitionTo has been invoked */

private State mDestState;

/**

  • Indicates if a transition is in progress

  • This will be true for all calls of State.exit and all calls of State.enter except for the

  • last enter call for the current destination state.

*/

private boolean mTransitionInProgress = false;

/** The list of deferred messages */

private ArrayList mDeferredMessages = new ArrayList();

/**

  • State entered when transitionToHaltingState is called.

*/

private class HaltingState extends State {

@Override

public boolean processMessage(Message msg) {

mSm.haltedProcessMessage(msg);

return true;

}

}

/**

  • State entered when a valid quit message is handled.

*/

private class QuittingState extends State {

@Override

public boolean processMessage(Message msg) {

return NOT_HANDLED;

}

}

/**

  • Handle messages sent to the state machine by calling

  • the current state’s processMessage. It also handles

  • the enter/exit calls and placing any deferred messages

  • back onto the queue when transitioning to a new state.

*/

@Override

public final void handleMessage(Message msg) {

if (!mHasQuit) {

if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {

mSm.onPreHandleMessage(msg);

}

if (mDbg) mSm.log(“handleMessage: E msg.what=” + msg.what);

/** Save the current message */

mMsg = msg;

/** State that processed the message */

State msgProcessedState = null;

if (mIsConstructionCompleted || (mMsg.what == SM_QUIT_CMD)) {

/** Normal path */

msgProcessedState = processMsg(msg);

} else if (!mIsConstructionCompleted && (mMsg.what == SM_INIT_CMD)

&& (mMsg.obj == mSmHandlerObj)) {

/** Initial one time path. */

mIsConstructionCompleted = true;

invokeEnterMethods(0);

} else {

throw new RuntimeException("StateMachine.handleMessage: "

  • "The start method not called, received msg: " + msg);

}

performTransitions(msgProcessedState, msg);

// We need to check if mSm == null here as we could be quitting.

if (mDbg && mSm != null) mSm.log(“handleMessage: X”);

if (mSm != null && msg.what != SM_INIT_CMD && msg.what != SM_QUIT_CMD) {

mSm.onPostHandleMessage(msg);

}

}

}

/**

  • Do any transitions

  • @param msgProcessedState is the state that processed the message

*/

private void performTransitions(State msgProcessedState, Message msg) {

/**

  • If transitionTo has been called, exit and then enter

  • the appropriate states. We loop on this to allow

  • enter and exit methods to use transitionTo.

*/

State orgState = mStateStack[mStateStackTopIndex].state;

/**

  • Record whether message needs to be logged before we transition and

  • and we won’t log special messages SM_INIT_CMD or SM_QUIT_CMD which

  • always set msg.obj to the handler.

*/

boolean recordLogMsg = mSm.recordLogRec(mMsg) && (msg.obj != mSmHandlerObj);

if (mLogRecords.logOnlyTransitions()) {

/** Record only if there is a transition */

if (mDestState != null) {

mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState,

orgState, mDestState);

}

} else if (recordLogMsg) {

/** Record message */

mLogRecords.add(mSm, mMsg, mSm.getLogRecString(mMsg), msgProcessedState, orgState,

mDestState);

}

State destState = mDestState;

if (destState != null) {

/**

  • Process the transitions including transitions in the enter/exit methods

*/

while (true) {

if (mDbg) mSm.log(“handleMessage: new destination call exit/enter”);

/**

  • Determine the states to exit and enter and return the

  • common ancestor state of the enter/exit states. Then

  • invoke the exit methods then the enter methods.

*/

StateInfo commonStateInfo = setupTempStateStackWithStatesToEnter(destState);

// flag is cleared in invokeEnterMethods before entering the target state

mTransitionInProgress = true;

invokeExitMethods(commonStateInfo);

int stateStackEnteringIndex = moveTempStateStackToStateStack();

invokeEnterMethods(stateStackEnteringIndex);

/**

  • Since we have transitioned to a new state we need to have

  • any deferred messages moved to the front of the message queue

  • so they will be processed before any other messages in the

  • message queue.

*/

moveDeferredMessageAtFrontOfQueue();

if (destState != mDestState) {

// A new mDestState so continue looping

destState = mDestState;

} else {

// No change in mDestState so we’re done

break;

}

}

mDestState = null;

}

/**

  • After processing all transitions check and

  • see if the last transition was to quit or halt.

*/

if (destState != null) {

if (destState == mQuittingState) {

/**

  • Call onQuitting to let subclasses cleanup.

*/

mSm.onQuitting();

cleanupAfterQuitting();

} else if (destState == mHaltingState) {

/**

  • Call onHalting() if we’ve transitioned to the halting

  • state. All subsequent messages will be processed in

  • in the halting state which invokes haltedProcessMessage(msg);

*/

mSm.onHalting();

}

}

}

/**

  • Cleanup all the static variables and the looper after the SM has been quit.

*/

private final void cleanupAfterQuitting() {

if (mSm.mSmThread != null) {

// If we made the thread then quit looper which stops the thread.

getLooper().quit();

mSm.mSmThread = null;

}

mSm.mSmHandler = null;

mSm = null;

mMsg = null;

mLogRecords.cleanup();

mStateStack = null;

mTempStateStack = null;

mStateInfo.clear();

mInitialState = null;

mDestState = null;

mDeferredMessages.clear();

mHasQuit = true;

}

/**

  • Complete the construction of the state machine.

*/

private final void completeConstruction() {

if (mDbg) mSm.log(“completeConstruction: E”);

/**

  • Determine the maximum depth of the state hierarchy

  • so we can allocate the state stacks.

*/

int maxDepth = 0;

for (StateInfo si : mStateInfo.values()) {

int depth = 0;

for (StateInfo i = si; i != null; depth++) {

i = i.parentStateInfo;

}

if (maxDepth < depth) {

maxDepth = depth;

}

}

if (mDbg) mSm.log(“completeConstruction: maxDepth=” + maxDepth);

mStateStack = new StateInfo[maxDepth];

mTempStateStack = new StateInfo[maxDepth];

setupInitialStateStack();

/** Sending SM_INIT_CMD message to invoke enter methods asynchronously */

sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));

if (mDbg) mSm.log(“completeConstruction: X”);

}

/**

  • Process the message. If the current state doesn’t handle

  • it, call the states parent and so on. If it is never handled then

  • call the state machines unhandledMessage method.

  • @return the state that processed the message

*/

private final State processMsg(Message msg) {

StateInfo curStateInfo = mStateStack[mStateStackTopIndex];

if (mDbg) {

mSm.log("processMsg: " + curStateInfo.state.getName());

}

if (isQuit(msg)) {

transitionTo(mQuittingState);

} else {

while (!curStateInfo.state.processMessage(msg)) {

/**

  • Not processed

*/

curStateInfo = curStateInfo.parentStateInfo;

if (curStateInfo == null) {

/**

  • No parents left so it’s not handled

*/

mSm.unhandledMessage(msg);

break;

}

if (mDbg) {

mSm.log("processMsg: " + curStateInfo.state.getName());

}

}

}

return (curStateInfo != null) ? curStateInfo.state : null;

}

/**

  • Call the exit method for each state from the top of stack

  • up to the common ancestor state.

*/

private final void invokeExitMethods(StateInfo commonStateInfo) {

while ((mStateStackTopIndex >= 0)

&& (mStateStack[mStateStackTopIndex] != commonStateInfo)) {

State curState = mStateStack[mStateStackTopIndex].state;

if (mDbg) mSm.log("invokeExitMethods: " + curState.getName());

curState.exit();

mStateStack[mStateStackTopIndex].active = false;

mStateStackTopIndex -= 1;

}

}

/**

  • Invoke the enter method starting at the entering index to top of state stack

*/

private final void invokeEnterMethods(int stateStackEnteringIndex) {

for (int i = stateStackEnteringIndex; i <= mStateStackTopIndex; i++) {

if (stateStackEnteringIndex == mStateStackTopIndex) {

// Last enter state for transition

mTransitionInProgress = false;

}

if (mDbg) mSm.log("invokeEnterMethods: " + mStateStack[i].state.getName());

mStateStack[i].state.enter();

mStateStack[i].active = true;

}

mTransitionInProgress = false; // ensure flag set to false if no methods called

}

/**

  • Move the deferred message to the front of the message queue.

*/

private final void moveDeferredMessageAtFrontOfQueue() {

/**

  • The oldest messages on the deferred list must be at

  • the front of the queue so start at the back, which

  • as the most resent message and end with the oldest

  • messages at the front of the queue.

*/

for (int i = mDeferredMessages.size() - 1; i >= 0; i–) {

Message curMsg = mDeferredMessages.get(i);

if (mDbg) mSm.log(“moveDeferredMessageAtFrontOfQueue; what=” + curMsg.what);

sendMessageAtFrontOfQueue(curMsg);

}

mDeferredMessages.clear();

}

/**

  • Move the contents of the temporary stack to the state stack

  • reversing the order of the items on the temporary stack as

  • they are moved.

  • @return index into mStateStack where entering needs to start

*/

private final int moveTempStateStackToStateStack() {

int startingIndex = mStateStackTopIndex + 1;

int i = mTempStateStackCount - 1;

int j = startingIndex;

while (i >= 0) {

if (mDbg) mSm.log(“moveTempStackToStateStack: i=” + i + “,j=” + j);

mStateStack[j] = mTempStateStack[i];

j += 1;

i -= 1;

}

mStateStackTopIndex = j - 1;

if (mDbg) {

mSm.log(“moveTempStackToStateStack: X mStateStackTop=” + mStateStackTopIndex

  • “,startingIndex=” + startingIndex + “,Top=”

  • mStateStack[mStateStackTopIndex].state.getName());

}

return startingIndex;

}

/**

  • Setup the mTempStateStack with the states we are going to enter.

  • This is found by searching up the destState’s ancestors for a

  • state that is already active i.e. StateInfo.active == true.

  • The destStae and all of its inactive parents will be on the

  • TempStateStack as the list of states to enter.

  • @return StateInfo of the common ancestor for the destState and

  • current state or null if there is no common parent.

*/

private final StateInfo setupTempStateStackWithStatesToEnter(State destState) {

/**

  • Search up the parent list of the destination state for an active

  • state. Use a do while() loop as the destState must always be entered

  • even if it is active. This can happen if we are exiting/entering

  • the current state.

*/

mTempStateStackCount = 0;

StateInfo curStateInfo = mStateInfo.get(destState);

do {

mTempStateStack[mTempStateStackCount++] = curStateInfo;

curStateInfo = curStateInfo.parentStateInfo;

} while ((curStateInfo != null) && !curStateInfo.active);

if (mDbg) {

mSm.log(“setupTempStateStackWithStatesToEnter: X mTempStateStackCount=”

  • mTempStateStackCount + ",curStateInfo: " + curStateInfo);

}

return curStateInfo;

}

/**

  • Initialize StateStack to mInitialState.

*/

private final void setupInitialStateStack() {

if (mDbg) {

mSm.log(“setupInitialStateStack: E mInitialState=” + mInitialState.getName());

}

StateInfo curStateInfo = mStateInfo.get(mInitialState);

for (mTempStateStackCount = 0; curStateInfo != null; mTempStateStackCount++) {

mTempStateStack[mTempStateStackCount] = curStateInfo;

curStateInfo = curStateInfo.parentStateInfo;

}

// Empty the StateStack

mStateStackTopIndex = -1;

moveTempStateStackToStateStack();

}

/**

  • @return current message

*/

private final Message getCurrentMessage() {

return mMsg;

}

/**

  • @return current state

*/

private final IState getCurrentState() {

return mStateStack[mStateStackTopIndex].state;

}

/**

  • Add a new state to the state machine. Bottom up addition

  • of states is allowed but the same state may only exist

  • in one hierarchy.

  • @param state the state to add

  • @param parent the parent of state

  • @return stateInfo for this state

*/

private final StateInfo addState(State state, State parent) {

if (mDbg) {

mSm.log(“addStateInternal: E state=” + state.getName() + “,parent=”

  • ((parent == null) ? “” : parent.getName()));

}

StateInfo parentStateInfo = null;

if (parent != null) {

parentStateInfo = mStateInfo.get(parent);

if (parentStateInfo == null) {

// Recursively add our parent as it’s not been added yet.

parentStateInfo = addState(parent, null);

}

}

StateInfo stateInfo = mStateInfo.get(state);

if (stateInfo == null) {

stateInfo = new StateInfo();

mStateInfo.put(state, stateInfo);

}

// Validate that we aren’t adding the same state in two different hierarchies.

if ((stateInfo.parentStateInfo != null)

&& (stateInfo.parentStateInfo != parentStateInfo)) {

throw new RuntimeException(“state already added”);

}

stateInfo.state = state;

stateInfo.parentStateInfo = parentStateInfo;

stateInfo.active = false;

if (mDbg) mSm.log("addStateInternal: X stateInfo: " + stateInfo);

return stateInfo;

}

/**

  • Remove a state from the state machine. Will not remove the state if it is currently

  • active or if it has any children in the hierarchy.

  • @param state the state to remove

*/

private void removeState(State state) {

StateInfo stateInfo = mStateInfo.get(state);

if (stateInfo == null || stateInfo.active) {

return;

}

boolean isParent = mStateInfo.values().stream()

.filter(si -> si.parentStateInfo == stateInfo)

.findAny()

.isPresent();

if (isParent) {

return;

}

mStateInfo.remove(state);

}

/**

  • Constructor

  • @param looper for dispatching messages

  • @param sm the hierarchical state machine

*/

private SmHandler(Looper looper, StateMachine sm) {

super(looper);

mSm = sm;

addState(mHaltingState, null);

addState(mQuittingState, null);

}

/** @see StateMachine#setInitialState(State) */

private final void setInitialState(State initialState) {

if (mDbg) mSm.log(“setInitialState: initialState=” + initialState.getName());

mInitialState = initialState;

}

/** @see StateMachine#transitionTo(IState) */

private final void transitionTo(IState destState) {

if (mTransitionInProgress) {

Log.wtf(mSm.mName, "transitionTo called while transition already in progress to " +

mDestState + “, new target state=” + destState);

}

mDestState = (State) destState;

if (mDbg) mSm.log(“transitionTo: destState=” + mDestState.getName());

}

/** @see StateMachine#deferMessage(Message) */

private final void deferMessage(Message msg) {

if (mDbg) mSm.log(“deferMessage: msg=” + msg.what);

/* Copy the “msg” to “newMsg” as “msg” will be recycled */

Message newMsg = obtainMessage();

newMsg.copyFrom(msg);

mDeferredMessages.add(newMsg);

}

/** @see StateMachine#quit() */

private final void quit() {

if (mDbg) mSm.log(“quit:”);

sendMessage(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));

}

/** @see StateMachine#quitNow() */

private final void quitNow() {

if (mDbg) mSm.log(“quitNow:”);

sendMessageAtFrontOfQueue(obtainMessage(SM_QUIT_CMD, mSmHandlerObj));

}

/** Validate that the message was sent by quit or quitNow. */

private final boolean isQuit(Message msg) {

return (msg.what == SM_QUIT_CMD) && (msg.obj == mSmHandlerObj);

}

/** @see StateMachine#isDbg() */

private final boolean isDbg() {

return mDbg;

}

/** @see StateMachine#setDbg(boolean) */

private final void setDbg(boolean dbg) {

mDbg = dbg;

}

}

private SmHandler mSmHandler;

private HandlerThread mSmThread;

/**

  • Initialize.

  • @param looper for this state machine

  • @param name of the state machine

*/

private void initStateMachine(String name, Looper looper) {

mName = name;

mSmHandler = new SmHandler(looper, this);

}

/**

  • Constructor creates a StateMachine with its own thread.

  • @param name of the state machine

*/

@UnsupportedAppUsage

protected StateMachine(String name) {

mSmThread = new HandlerThread(name);

mSmThread.start();

Looper looper = mSmThread.getLooper();

initStateMachine(name, looper);

}

/**

  • Constructor creates a StateMachine using the looper.

  • @param name of the state machine

*/

@UnsupportedAppUsage

protected StateMachine(String name, Looper looper) {

initStateMachine(name, looper);

}

/**

  • Constructor creates a StateMachine using the handler.

  • @param name of the state machine

*/

@UnsupportedAppUsage

protected StateMachine(String name, Handler handler) {

initStateMachine(name, handler.getLooper());

}

/**

  • Notifies subclass that the StateMachine handler is about to process the Message msg

  • @param msg The message that is being handled

*/

protected void onPreHandleMessage(Message msg) {

}

/**

  • Notifies subclass that the StateMachine handler has finished processing the Message msg and

  • has possibly transitioned to a new state.

  • @param msg The message that is being handled

*/

protected void onPostHandleMessage(Message msg) {

}

/**

  • Add a new state to the state machine

  • @param state the state to add

  • @param parent the parent of state

*/

public final void addState(State state, State parent) {

mSmHandler.addState(state, parent);

}

/**

  • Add a new state to the state machine, parent will be null

  • @param state to add

*/

@UnsupportedAppUsage

public final void addState(State state) {

mSmHandler.addState(state, null);

}

/**

  • Removes a state from the state machine, unless it is currently active or if it has children.

  • @param state state to remove

*/

public final void removeState(State state) {

mSmHandler.removeState(state);

}

/**

  • Set the initial state. This must be invoked before

  • and messages are sent to the state machine.

  • @param initialState is the state which will receive the first message.

*/

@UnsupportedAppUsage

public final void setInitialState(State initialState) {

mSmHandler.setInitialState(initialState);

}

/**

  • @return current message

*/

public final Message getCurrentMessage() {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return null;

return smh.getCurrentMessage();

}

/**

  • @return current state

*/

public final IState getCurrentState() {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return null;

return smh.getCurrentState();

}

/**

  • transition to destination state. Upon returning

  • from processMessage the current state’s exit will

  • be executed and upon the next message arriving

  • destState.enter will be invoked.

  • this function can also be called inside the enter function of the

  • previous transition target, but the behavior is undefined when it is

  • called mid-way through a previous transition (for example, calling this

  • in the enter() routine of a intermediate node when the current transition

  • target is one of the nodes descendants).

  • @param destState will be the state that receives the next message.

*/

@UnsupportedAppUsage

public final void transitionTo(IState destState) {

mSmHandler.transitionTo(destState);

}

/**

  • transition to halt state. Upon returning

  • from processMessage we will exit all current

  • states, execute the onHalting() method and then

  • for all subsequent messages haltedProcessMessage

  • will be called.

*/

public final void transitionToHaltingState() {

mSmHandler.transitionTo(mSmHandler.mHaltingState);

}

/**

  • Defer this message until next state transition.

  • Upon transitioning all deferred messages will be

  • placed on the queue and reprocessed in the original

  • order. (i.e. The next state the oldest messages will

  • be processed first)

  • @param msg is deferred until the next transition.

*/

public final void deferMessage(Message msg) {

mSmHandler.deferMessage(msg);

}

/**

  • Called when message wasn’t handled

  • @param msg that couldn’t be handled.

*/

protected void unhandledMessage(Message msg) {

if (mSmHandler.mDbg) loge(" - unhandledMessage: msg.what=" + msg.what);

}

/**

  • Called for any message that is received after

  • transitionToHalting is called.

*/

protected void haltedProcessMessage(Message msg) {

}

/**

  • This will be called once after handling a message that called

  • transitionToHalting. All subsequent messages will invoke

  • {@link StateMachine#haltedProcessMessage(Message)}

*/

protected void onHalting() {

}

/**

  • This will be called once after a quit message that was NOT handled by

  • the derived StateMachine. The StateMachine will stop and any subsequent messages will be

  • ignored. In addition, if this StateMachine created the thread, the thread will

  • be stopped after this method returns.

*/

protected void onQuitting() {

}

/**

  • @return the name

*/

public final String getName() {

return mName;

}

/**

  • Set number of log records to maintain and clears all current records.

  • @param maxSize number of messages to maintain at anyone time.

*/

public final void setLogRecSize(int maxSize) {

mSmHandler.mLogRecords.setSize(maxSize);

}

/**

  • Set to log only messages that cause a state transition

  • @param enable {@code true} to enable, {@code false} to disable

*/

public final void setLogOnlyTransitions(boolean enable) {

mSmHandler.mLogRecords.setLogOnlyTransitions(enable);

}

/**

  • @return the number of log records currently readable

*/

public final int getLogRecSize() {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return 0;

return smh.mLogRecords.size();

}

/**

  • @return the number of log records we can store

*/

@VisibleForTesting

public final int getLogRecMaxSize() {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return 0;

return smh.mLogRecords.mMaxSize;

}

/**

  • @return the total number of records processed

*/

public final int getLogRecCount() {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return 0;

return smh.mLogRecords.count();

}

/**

  • @return a log record, or null if index is out of range

*/

public final LogRec getLogRec(int index) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return null;

return smh.mLogRecords.get(index);

}

/**

  • @return a copy of LogRecs as a collection

*/

public final Collection copyLogRecs() {

Vector vlr = new Vector();

SmHandler smh = mSmHandler;

if (smh != null) {

for (LogRec lr : smh.mLogRecords.mLogRecVector) {

vlr.add(lr);

}

}

return vlr;

}

/**

  • Add the string to LogRecords.

  • @param string

*/

public void addLogRec(String string) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return;

smh.mLogRecords.add(this, smh.getCurrentMessage(), string, smh.getCurrentState(),

smh.mStateStack[smh.mStateStackTopIndex].state, smh.mDestState);

}

/**

  • @return true if msg should be saved in the log, default is true.

*/

protected boolean recordLogRec(Message msg) {

return true;

}

/**

  • Return a string to be logged by LogRec, default

  • is an empty string. Override if additional information is desired.

  • @param msg that was processed

  • @return information to be logged as a String

*/

protected String getLogRecString(Message msg) {

return “”;

}

/**

  • @return the string for msg.what

*/

protected String getWhatToString(int what) {

return null;

}

/**

  • @return Handler, maybe null if state machine has quit.

*/

public final Handler getHandler() {

return mSmHandler;

}

/**

  • Get a message and set Message.target state machine handler.

  • Note: The handler can be null if the state machine has quit,

  • which means target will be null and may cause a AndroidRuntimeException

  • in MessageQueue#enqueMessage if sent directly or if sent using

  • StateMachine#sendMessage the message will just be ignored.

  • @return A Message object from the global pool

*/

public final Message obtainMessage() {

return Message.obtain(mSmHandler);

}

/**

  • Get a message and set Message.target state machine handler, what.

  • Note: The handler can be null if the state machine has quit,

  • which means target will be null and may cause a AndroidRuntimeException

  • in MessageQueue#enqueMessage if sent directly or if sent using

  • StateMachine#sendMessage the message will just be ignored.

  • @param what is the assigned to Message.what.

  • @return A Message object from the global pool

*/

public final Message obtainMessage(int what) {

return Message.obtain(mSmHandler, what);

}

/**

  • Get a message and set Message.target state machine handler,

  • what and obj.

  • Note: The handler can be null if the state machine has quit,

  • which means target will be null and may cause a AndroidRuntimeException

  • in MessageQueue#enqueMessage if sent directly or if sent using

  • StateMachine#sendMessage the message will just be ignored.

  • @param what is the assigned to Message.what.

  • @param obj is assigned to Message.obj.

  • @return A Message object from the global pool

*/

public final Message obtainMessage(int what, Object obj) {

return Message.obtain(mSmHandler, what, obj);

}

/**

  • Get a message and set Message.target state machine handler,

  • what, arg1 and arg2

  • Note: The handler can be null if the state machine has quit,

  • which means target will be null and may cause a AndroidRuntimeException

  • in MessageQueue#enqueMessage if sent directly or if sent using

  • StateMachine#sendMessage the message will just be ignored.

  • @param what is assigned to Message.what

  • @param arg1 is assigned to Message.arg1

  • @return A Message object from the global pool

*/

public final Message obtainMessage(int what, int arg1) {

// use this obtain so we don’t match the obtain(h, what, Object) method

return Message.obtain(mSmHandler, what, arg1, 0);

}

/**

  • Get a message and set Message.target state machine handler,

  • what, arg1 and arg2

  • Note: The handler can be null if the state machine has quit,

  • which means target will be null and may cause a AndroidRuntimeException

  • in MessageQueue#enqueMessage if sent directly or if sent using

  • StateMachine#sendMessage the message will just be ignored.

  • @param what is assigned to Message.what

  • @param arg1 is assigned to Message.arg1

  • @param arg2 is assigned to Message.arg2

  • @return A Message object from the global pool

*/

@UnsupportedAppUsage

public final Message obtainMessage(int what, int arg1, int arg2) {

return Message.obtain(mSmHandler, what, arg1, arg2);

}

/**

  • Get a message and set Message.target state machine handler,

  • what, arg1, arg2 and obj

  • Note: The handler can be null if the state machine has quit,

  • which means target will be null and may cause a AndroidRuntimeException

  • in MessageQueue#enqueMessage if sent directly or if sent using

  • StateMachine#sendMessage the message will just be ignored.

  • @param what is assigned to Message.what

  • @param arg1 is assigned to Message.arg1

  • @param arg2 is assigned to Message.arg2

  • @param obj is assigned to Message.obj

  • @return A Message object from the global pool

*/

@UnsupportedAppUsage

public final Message obtainMessage(int what, int arg1, int arg2, Object obj) {

return Message.obtain(mSmHandler, what, arg1, arg2, obj);

}

/**

  • Enqueue a message to this state machine.

  • Message is ignored if state machine has quit.

*/

@UnsupportedAppUsage

public void sendMessage(int what) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return;

smh.sendMessage(obtainMessage(what));

}

/**

  • Enqueue a message to this state machine.

  • Message is ignored if state machine has quit.

*/

@UnsupportedAppUsage

public void sendMessage(int what, Object obj) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return;

smh.sendMessage(obtainMessage(what, obj));

}

/**

  • Enqueue a message to this state machine.

  • Message is ignored if state machine has quit.

*/

@UnsupportedAppUsage

public void sendMessage(int what, int arg1) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return;

smh.sendMessage(obtainMessage(what, arg1));

}

/**

  • Enqueue a message to this state machine.

  • Message is ignored if state machine has quit.

*/

public void sendMessage(int what, int arg1, int arg2) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return;

smh.sendMessage(obtainMessage(what, arg1, arg2));

}

/**

  • Enqueue a message to this state machine.

  • Message is ignored if state machine has quit.

*/

@UnsupportedAppUsage

public void sendMessage(int what, int arg1, int arg2, Object obj) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return;

smh.sendMessage(obtainMessage(what, arg1, arg2, obj));

}

/**

  • Enqueue a message to this state machine.

  • Message is ignored if state machine has quit.

*/

@UnsupportedAppUsage

public void sendMessage(Message msg) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return;

smh.sendMessage(msg);

}

/**

  • Enqueue a message to this state machine after a delay.

  • Message is ignored if state machine has quit.

*/

public void sendMessageDelayed(int what, long delayMillis) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return;

smh.sendMessageDelayed(obtainMessage(what), delayMillis);

}

/**

  • Enqueue a message to this state machine after a delay.

  • Message is ignored if state machine has quit.

*/

public void sendMessageDelayed(int what, Object obj, long delayMillis) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return;

smh.sendMessageDelayed(obtainMessage(what, obj), delayMillis);

}

/**

  • Enqueue a message to this state machine after a delay.

  • Message is ignored if state machine has quit.

*/

public void sendMessageDelayed(int what, int arg1, long delayMillis) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return;

smh.sendMessageDelayed(obtainMessage(what, arg1), delayMillis);

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

下面分享的腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题全套解析,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,下面只是以图片的形式给大家展示一部分。

image

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

image

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
mHandler smh = mSmHandler;

if (smh == null) return;

smh.sendMessage(obtainMessage(what, arg1));

}

/**

  • Enqueue a message to this state machine.

  • Message is ignored if state machine has quit.

*/

public void sendMessage(int what, int arg1, int arg2) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return;

smh.sendMessage(obtainMessage(what, arg1, arg2));

}

/**

  • Enqueue a message to this state machine.

  • Message is ignored if state machine has quit.

*/

@UnsupportedAppUsage

public void sendMessage(int what, int arg1, int arg2, Object obj) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return;

smh.sendMessage(obtainMessage(what, arg1, arg2, obj));

}

/**

  • Enqueue a message to this state machine.

  • Message is ignored if state machine has quit.

*/

@UnsupportedAppUsage

public void sendMessage(Message msg) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return;

smh.sendMessage(msg);

}

/**

  • Enqueue a message to this state machine after a delay.

  • Message is ignored if state machine has quit.

*/

public void sendMessageDelayed(int what, long delayMillis) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return;

smh.sendMessageDelayed(obtainMessage(what), delayMillis);

}

/**

  • Enqueue a message to this state machine after a delay.

  • Message is ignored if state machine has quit.

*/

public void sendMessageDelayed(int what, Object obj, long delayMillis) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return;

smh.sendMessageDelayed(obtainMessage(what, obj), delayMillis);

}

/**

  • Enqueue a message to this state machine after a delay.

  • Message is ignored if state machine has quit.

*/

public void sendMessageDelayed(int what, int arg1, long delayMillis) {

// mSmHandler can be null if the state machine has quit.

SmHandler smh = mSmHandler;

if (smh == null) return;

smh.sendMessageDelayed(obtainMessage(what, arg1), delayMillis);

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

下面分享的腾讯、头条、阿里、美团、字节跳动等公司2019-2021年的高频面试题全套解析,博主还把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,下面只是以图片的形式给大家展示一部分。

[外链图片转存中…(img-qMQtW6eQ-1714668217928)]

知识不体系?这里还有整理出来的Android进阶学习的思维脑图,给大家参考一个方向。

[外链图片转存中…(img-qED1b7nE-1714668217929)]

《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 7
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值