主頁 > 移動端開發 > Android狀態機StateMachine使用舉例及原始碼決議

Android狀態機StateMachine使用舉例及原始碼決議

2020-09-14 06:25:40 移動端開發

Android frameworks原始碼StateMachine使用舉例及原始碼決議

作業中有一同事說到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.javaStateIState

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

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

  • PersonStateMachine.java案例代碼
  • PersonStateMachine 使用
  • 案例的簡單說明
  • 案例原始碼下載

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);
  • SleepState狀態收到MSG_WAKEUP訊息后,會執行對應狀態的processMessage方法
  • SleepState類中processMessage方法收到MSG_WAKEUP訊息后,執行transitionTo(mWorkState)方法,完成狀態轉換,轉換到WorkState狀態,

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案例地址

二、實作原理學習

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);
}
  • StateMachine的構造方法中,創建并啟動了一個執行緒HandlerThread
  • initStateMachine方法中,創建了HandlerThread執行緒對應的Handler SmHandler
private void initStateMachine(String name, Looper looper) {
    mName = name;
    mSmHandler = new SmHandler(looper, this);
}
  • SmHandler構造方法中,向狀態機中添加了兩個狀態:一個狀態為狀態機的暫停狀態mHaltingState、一個狀態為狀態機的退出狀態mQuittingState
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();
}
  • 從以上代碼可以看到,其中只有一個方法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));
}
  • maxDepth是狀態機中,最長依賴鏈的長度,
  • mStateStackmTempStateStack為兩個用陣列實作的堆疊,這兩個堆疊的最大長度,即為maxDepth,其用來存盤當前活躍狀態當前活躍狀態的父狀態、父父狀態、...等
  • setupInitialStateStack();完成狀態的初始化,將當前的活躍狀態放入到mStateStack堆疊中,

下邊來具體說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();
}
  • 拿案例中狀態來舉例,將初始化狀態放入 mTempStateStack堆疊中
  • 初始化狀態父狀態父父狀態父父父狀態... 都一一放入到mTempStateStack堆疊中

enter description here

  • 然后moveTempStateStackToStateStack()方法中,mTempStateStack出堆疊,mStateStack入堆疊,將所有狀態資訊匯入到mStateStack堆疊,并清空mTempStateStack堆疊,

enter description here

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

// 發送初始化完成的訊息(訊息放入到佇列的最前邊)
sendMessageAtFrontOfQueue(obtainMessage(SM_INIT_CMD, mSmHandlerObj));
  • 發送一個初始化完成的訊息到SmHandler當中,

下邊來看一下SmHandlerhandleMessage(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堆疊中的所有狀態設定為活躍狀態,并由父—>子的順序,執行堆疊中狀態的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;
    }
}
  • 可以看到,其將mStateStack堆疊中的所有狀態設定為活躍狀態,并由父—>子的順序,執行堆疊中狀態的enter()方法

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

2.3、狀態轉化

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

// 獲取 狀態機參考
PersonStateMachine personStateMachine = PersonStateMachine.makePerson();
// 初始狀態為SleepState,發送訊息MSG_WAKEUP
personStateMachine.sendMessage(PersonStateMachine.MSG_WAKEUP);
  • 通過呼叫sendMessage(PersonStateMachine.MSG_WAKEUP);方法,向SmHandler中發送一個訊息,來觸發狀態轉化,
  • 可以說 sendMessage(PersonStateMachine.MSG_WAKEUP);訊息,為狀態轉化的導火索,

下邊,再次看一下SmHandlerhandleMessage(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);方法中,

我們來看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;
}
  • 在SleepState狀態的processMessage(Message msg)方法中,其收到MSG_WAKEUP訊息后,會呼叫transitionTo(mWorkState);方法,將目標狀態設定為mWorkState

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

private final void transitionTo(IState destState) {
    mDestState = (State) destState;
}
  • 可以看到,transitionTo(IState destState)方法,只是一個簡單的狀態賦值,

下邊我們回到SmHandlerhandleMessage(Message msg)方法:

  • 代碼會執行到SmHandler.handleMessage(Message msg)performTransitions(msgProcessedState, msg);方法之中,
  • 而這里我們傳入的引數msgProcessedStatemSleepState
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;
    }
	// ...
}
  • 以上方法中 傳入的引數msgProcessedStatemSleepState
  • 方法中destState目標狀態為 mWorkState

此時此刻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 here

B、commonStateInfo狀態在mStateStack堆疊中的子狀態退堆疊

commonStateInfosetupTempStateStackWithStatesToEnter(destState);方法的回傳引數,這里是BoringState

// commonStateInfo 狀態的子狀態全部退堆疊
invokeExitMethods(commonStateInfo);
  • 1、BoringState作為引數傳入到invokeExitMethods(commonStateInfo);方法中
  • 2、其方法內容為,將BoringState狀態的全部子狀態退堆疊

以上代碼的執行示意圖如下:
在這里插入圖片描述

C、mTempStateStack全部狀態出堆疊,mStateStack入堆疊
// 目標狀態入堆疊
int stateStackEnteringIndex = moveTempStateStackToStateStack();
// 入堆疊狀態 活躍
invokeEnterMethods(stateStackEnteringIndex);
  • moveTempStateStackToStateStack方法中:mTempStateStack全部狀態出堆疊,mStateStack入堆疊
  • invokeEnterMethods(stateStackEnteringIndex);方法中,將新加入的狀態設定為活躍狀態;并呼叫其對應的enter()方法,

最終的堆疊狀態為:

在這里插入圖片描述

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

========== THE END ==========

wx_gzh.jpg

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/31683.html

標籤:Android

上一篇:Android應用程式與SurfaceFlinger服務的連接程序分析

下一篇:安全軟體禁止第三方鎖屏軟體侵犯鎖屏界面,該如何做

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more