主頁 > 移動端開發 > AMessage-ALooper-AHandler訊息機制流程分析

AMessage-ALooper-AHandler訊息機制流程分析

2021-02-22 10:26:33 移動端開發

android底層的通信方式采用C++實作訊息機制,簡單來說就是ALooper-AHandler-AMessage機制,其能實作異步處理訊息佇列中的訊息,具體的點就是AMessage是作為訊息傳遞類、ALooper維持一個執行緒函式,接受來自不同地方的訊息,之后將訊息佇列中訊息分配給訊息系結的Handler處理、AHandler是針對不同訊息進行具體處理的類,實際上訊息機制不僅僅用在android底層多媒體中,其還用于其他地方,所以明白訊息機制是學習android底層多媒體的重中之重,
具體的處理流程可以用下圖簡單的表示:
在這里插入圖片描述

1、AMessage訊息類

AMessage是整個框架中的訊息載體,其原始碼的存放路徑如下:

libstagefright\foundation\AMessage.cppmedia\stagefright\foundation\AMessage.h

AMessage訊息類是整個訊息處理機制的橋梁作用,具體原始碼如下:

   struct AMessage : public RefBase {
    AMessage();
    AMessage(uint32_t what, const sp<const AHandler> &handler);
    static sp<AMessage> FromParcel(const Parcel &parcel,
                                   size_t maxNestingLevel = 255);
    void writeToParcel(Parcel *parcel) const;
    void setWhat(uint32_t what);
    uint32_t what() const;
    void setTarget(const sp<const AHandler> &handler);
    void clear();
    void setInt32(const char *name, int32_t value);
    void setInt64(const char *name, int64_t value);
    void setSize(const char *name, size_t value);
    void setFloat(const char *name, float value);
    void setDouble(const char *name, double value);
    void setPointer(const char *name, void *value);
    void setString(const char *name, const char *s, ssize_t len = -1);
    void setString(const char *name, const AString &s);
    void setObject(const char *name, const sp<RefBase> &obj);
    void setBuffer(const char *name, const sp<ABuffer> &buffer);
    void setMessage(const char *name, const sp<AMessage> &obj);
    void setRect(const char *name,int32_t left, int32_t top, int32_t right, int32_t bottom);
    bool contains(const char *name) const;
    bool findInt32(const char *name, int32_t *value) const;
    bool findInt64(const char *name, int64_t *value) const;
    bool findSize(const char *name, size_t *value) const;
    bool findFloat(const char *name, float *value) const;
    bool findDouble(const char *name, double *value) const;
    bool findPointer(const char *name, void **value) const;
    bool findString(const char *name, AString *value) const;
    bool findObject(const char *name, sp<RefBase> *obj) const;
    bool findBuffer(const char *name, sp<ABuffer> *buffer) const;
    bool findMessage(const char *name, sp<AMessage> *obj) const;
    bool findAsInt64(const char *name, int64_t *value) const;
    bool findAsFloat(const char *name, float *value) const;
    bool findRect( const char *name, int32_t *left, int32_t *top, int32_t *right, int32_t *bottom) const;
    status_t post(int64_t delayUs = 0);
    status_t postAndAwaitResponse(sp<AMessage> *response);
    bool senderAwaitsResponse(sp<AReplyToken> *replyID);
    void extend(const sp<AMessage> &other);
    sp<AMessage> changesFrom(const sp<const AMessage> &other, bool deep = false) const;
    AString debugString(int32_t indent = 0) const;
    enum Type {
        kTypeInt32,
        kTypeInt64,
        kTypeSize,
        kTypeFloat,
        kTypeDouble,
        kTypePointer,
        kTypeString,
        kTypeObject,
        kTypeMessage,
        kTypeRect,
        kTypeBuffer,
    };

    struct Rect {
        int32_t mLeft, mTop, mRight, mBottom;
    };

    size_t countEntries() const;
    const char *getEntryNameAt(size_t index, Type *type) const;

    typedef AData<
        int32_t, int64_t, size_t, float, double, Rect, AString,
        void *, sp<AMessage>, sp<ABuffer>, sp<RefBase>>::Basic ItemData;
    ItemData findItem(const char *name) const;
    void setItem(const char *name, const ItemData &item);

    ItemData getEntryAt(size_t index) const;
    size_t findEntryByName(const char *name) const;
    status_t setEntryNameAt(size_t index, const char *name);
    status_t setEntryAt(size_t index, const ItemData &item);
    status_t removeEntryAt(size_t index);

	protected:
	virtual ~AMessage();
	
	private:
	friend struct ALooper; 
	uint32_t mWhat;
	ALooper::handler_id mTarget;
	
	wp<AHandler> mHandler;
	wp<ALooper> mLooper;

    struct Item {
        union {
            int32_t int32Value;
            int64_t int64Value;
            size_t sizeValue;
            float floatValue;
            double doubleValue;
            void *ptrValue;
            RefBase *refValue;
            AString *stringValue;
            Rect rectValue;
        } u;
        const char *mName;
        size_t      mNameLength;
        Type mType;
        void setName(const char *name, size_t len);
    };
    enum {
        kMaxNumItems = 64
    };
    Item mItems[kMaxNumItems];
    size_t mNumItems;

    Item *allocateItem(const char *name);
    void freeItemValue(Item *item);
    const Item *findItem(const char *name, Type type) const;
    void setObjectInternal(
            const char *name, const sp<RefBase> &obj, Type type);
    size_t findItemIndex(const char *name, size_t len) const;
    void deliver();
    DISALLOW_EVIL_CONSTRUCTORS(AMessage);
	};

其中 AMessage有AMessage和AMessage(uint32_t what, const sp &handler)兩套建構式,可以直接的第二個建構式使用AMessage指定訊息并初始化其handler,非常的銀性,當然,如果沒有采用第二個建構式,也可以呼叫類的函式setWhat和setTarget指定what和handler,具體的在如下:

    AMessage(uint32_t what, const sp<const AHandler> &handler);
    void setWhat(uint32_t what);
    void setTarget(const sp<const AHandler> &handler);

AMessage構造完成之后,可以呼叫其setXXX設定對應的引數,針對不同的訊息,更是有不同的函式,最后通過post(呼叫mLooper.promote()->post)即可將訊息投遞到AHandler的訊息佇列中,在我們的接收端呢,也是通過findXXX獲取傳遞的引數,

	void setString(const char *name, const AString &s);
    void setPointer(const char *name, void *value);
    bool findString(const char *name, AString *value) const;
    bool findPointer(const char *name, void **value) const;

AMessage屬性中存在指向 wp mHandler 和 wp mLooper的指標,用于系結AHandler和ALooper,
Post發送訊息到ALooper中,具體如下:

	wp<AHandler> mHandler;
    wp<ALooper> mLooper;

AMessage的Post函式是呼叫其系結的mLooper的post函式,傳遞的引數是AMessage和delayUs,具體的ALooper的post函式的處理程序在下面的Alooper中有分析,

    status_t AMessage::post(int64_t delayUs) {
    sp<ALooper> looper = mLooper.promote();
    if (looper == NULL) {
        ALOGW("failed to post message as target looper for handler %d is gone.", mTarget);
        return -ENOENT;
    }

    looper->post(this, delayUs);
    return OK;
	}

AMessage中deliver()是獲取AHandler物件呼叫其deliverMessage()函式,傳遞AMessage給Handler,

	void AMessage::deliver() {
    sp<AHandler> handler = mHandler.promote();
    if (handler == NULL) {
        ALOGW("failed to deliver message as target handler %d is gone.", mTarget);
        return;
    }

    handler->deliverMessage(this);
	}

2、ALooper訊息傳遞類及其管理類(ALooperRooster)

如上文得知ALooper是維護訊息傳遞的類,其原始碼路徑如下:

frameworks/av/media/libstagefright/foundation/include/media/stagefright/foundation/ALooper.h

ALooper介面分析

 struct ALooper : public RefBase {
	    typedef int32_t event_id;
	    typedef int32_t handler_id;
	    ALooper();
	    void setName(const char *name);
	    handler_id registerHandler(const sp<AHandler> &handler);
	    void unregisterHandler(handler_id handlerID);
	    status_t start(
	            bool runOnCallingThread = false,
	            bool canCallJava = false,
	            int32_t priority = PRIORITY_DEFAULT
	            );
	
	    status_t stop();
	    static int64_t GetNowUs();
	    const char *getName() const {
	        return mName.c_str();
	    }
	protected:
	    virtual ~ALooper();
	
	private:
	    friend struct AMessage;// post()
	    struct Event {
	        int64_t mWhenUs;
	        sp<AMessage> mMessage;
	    };
	
	    Mutex mLock;
	    Condition mQueueChangedCondition;
	    AString mName;
	    List<Event> mEventQueue;
	    struct LooperThread;
	    sp<LooperThread> mThread;
	    bool mRunningLocally;
	    Mutex mRepliesLock;
	    Condition mRepliesCondition;
	    void post(const sp<AMessage> &msg, int64_t delayUs);
	    sp<AReplyToken> createReplyToken();
	    status_t awaitResponse(const sp<AReplyToken> &replyToken, sp<AMessage> *response);
	    status_t postReply(const sp<AReplyToken> &replyToken, const sp<AMessage> &msg);
	    bool loop();
	    DISALLOW_EVIL_CONSTRUCT
	};

ALooper本身是維持訊息傳遞的類,它的使用方法比較簡單,具體流程就是初始化ALooper物件,接著呼叫setName和start函式,最后完成的是AHandler注冊,具體的使用如下:

mLooper(new ALooper);
	mLooper->setName("XXX ALooper");
	mLooper->start(
				  false,/*runOnCallingThread*/
				  true,/*canCallJava*/
				  PRIORITY_AUDIO);
	mLooper->registerHandler(Handler);

ALooper中的start函式中會創建一個執行緒,如下所示:

status_t ALooper::start(
        bool runOnCallingThread, bool canCallJava, int32_t priority) {
    if (runOnCallingThread) {
        {
            Mutex::Autolock autoLock(mLock);
            if (mThread != NULL || mRunningLocally) {
                return INVALID_OPERATION;
            }
            mRunningLocally = true;
        }
        do {
        } while (loop());

        return OK;
    }
    Mutex::Autolock autoLock(mLock);
    if (mThread != NULL || mRunningLocally) {
        return INVALID_OPERATION;
    }
    mThread = new LooperThread(this, canCallJava);
    status_t err = mThread->run(
            mName.empty() ? "ALooper" : mName.c_str(), priority);
    if (err != OK) {
        mThread.clear();
    }
    return err;
	}

執行緒中,主要是呼叫的是Loop函式,具體loop函式原始碼如下:

bool ALooper::loop() {
    Event event;
    {
        Mutex::Autolock autoLock(mLock);
        if (mThread == NULL && !mRunningLocally) {
            return false;
        }
        if (mEventQueue.empty()) {
            mQueueChangedCondition.wait(mLock);
            return true;
        }
        int64_t whenUs = (*mEventQueue.begin()).mWhenUs;
        int64_t nowUs = GetNowUs();

        if (whenUs > nowUs) {
            int64_t delayUs = whenUs - nowUs;
            if (delayUs > INT64_MAX / 1000) {
                delayUs = INT64_MAX / 1000;
            }
            mQueueChangedCondition.waitRelative(mLock, delayUs * 1000ll);

            return true;
        }

        event = *mEventQueue.begin();
        mEventQueue.erase(mEventQueue.begin());
    }

    event.mMessage->deliver();
    return true;
	}

那么到了這一步,已經可以看到了,需要把上文介紹的訊息添加進ALooper的mEventQueue鏈表中了,上文AMessage呼叫post函式是呼叫ALooper的Post函式,通過Post函式將AMessage添加到鏈表中,具體Post函式如下:

void ALooper::post(const sp<AMessage> &msg, int64_t delayUs) {
    Mutex::Autolock autoLock(mLock);
    int64_t whenUs;
    if (delayUs > 0) {
        int64_t nowUs = GetNowUs();
        whenUs = (delayUs > INT64_MAX - nowUs ? INT64_MAX : nowUs + delayUs);
    } else {
        whenUs = GetNowUs();
    }
    List<Event>::iterator it = mEventQueue.begin();
    while (it != mEventQueue.end() && (*it).mWhenUs <= whenUs) {
        ++it;
    }
    Event event;
    event.mWhenUs = whenUs;
    event.mMessage = msg;
    if (it == mEventQueue.begin()) {
        mQueueChangedCondition.signal();
    }
    mEventQueue.insert(it, event);
	}

在ALooper::loop()函式會一直的監聽這個mEventQueue鏈表,如果里面有訊息的話,就會取出來,接著呼叫AMessage的deliver函式,AMessage::deliver()函式中,就會去獲取這個AMessage的Ahandler,然后呼叫對應AHandler的deliverMessage函式:handler->deliverMessage(this),

在AHandler::deliverMessage(const sp &msg)函式中,就會呼叫AHandler中的onMessageReceived(msg)函式,如果這時候傳入的是AHandler子類的話,就會去呼叫AHandler子類的onMessageReceived(msg)函式,這里的流程具體在下面AHandler有詳細分析,

接下來分析的是管理類ALooperRooster

ALooperRooster的原始碼路徑如下:

/frameworks/av/media/libstagefright/foundation/include/media/stagefright/foundation/ALooperRoster.h
struct ALooperRoster {
	    ALooperRoster();
	
	    ALooper::handler_id registerHandler(
	            const sp<ALooper> &looper, const sp<AHandler> &handler);
	
	    void unregisterHandler(ALooper::handler_id handlerID);
	    void unregisterStaleHandlers();
	
	    void dump(int fd, const Vector<String16>& args);
	
	private:
	    struct HandlerInfo {
	        wp<ALooper> mLooper;
	        wp<AHandler> mHandler;
	    };
	
	    Mutex mLock;
	    KeyedVector<ALooper::handler_id, HandlerInfo> mHandlers;
	    ALooper::handler_id mNextHandlerID;
	
	    DISALLOW_EVIL_CONSTRUCTORS(ALooperRoster);
	 };
	} 

上面需要注意的點是 KeyedVector<ALooper::handler_id, HandlerInfo> mHandlers這個容器和registerHandler函式,可以看到HandlerInfo中有ALooper和AHandler,這是構成了mHandlers的基本單元,這里實作的就是一個Looper多個Hander的狀態的資料結構基礎,具體的注冊流程如下:

ALooper::handler_id ALooperRoster::registerHandler(
        const sp<ALooper> &looper, const sp<AHandler> &handler) {
    Mutex::Autolock autoLock(mLock);

    if (handler->id() != 0) {
        CHECK(!"A handler must only be registered once.");
        return INVALID_OPERATION;
    }
    HandlerInfo info;
    info.mLooper = looper;
    info.mHandler = handler;
    ALooper::handler_id handlerID = mNextHandlerID++;
    mHandlers.add(handlerID, info);

    handler->setID(handlerID, looper);

    return handlerID;
}

3、AHandler處理訊息類

AHandler作為訊息處理的類,簡單來說就是處理分發的各種訊息,具體原始碼路徑如下:

frameworks/av/include/media/stagefright/foundation/AHandler.h
struct AHandler : public RefBase {
		AHandler()
		: mID(0),
		mVerboseStats(false),
		mMessageCounter(0) {
		}
		
		ALooper::handler_id id() const {
		return mID;
		}
		
		sp<ALooper> looper() const {
		return mLooper.promote();
		}
		
		wp<ALooper> getLooper() const {
		return mLooper;
		}
		
		wp<AHandler> getHandler() const {
		// allow getting a weak reference to a const handler
		return const_cast<AHandler *>(this);
		}
	
	protected:
	virtual void onMessageReceived(const sp<AMessage> &msg) = 0;
	private:
	friend struct AMessage;      // deliverMessage()
	friend struct ALooperRoster; // setID()
	ALooper::handler_id mID;
	wp<ALooper> mLooper;
	
	inline void setID(ALooper::handler_id id, const wp<ALooper> &looper) {
		mID = id;
		mLooper = looper;
	}
	
	bool mVerboseStats;
	uint32_t mMessageCounter;
	KeyedVector<uint32_t, uint32_t> mMessages;
	
	void deliverMessage(const sp<AMessage> &msg);
	
	DISALLOW_EVIL_CONSTRUCTORS(AHandler);
	};

從上面的定義中,需要格外注意的是virtual void onMessageReceived(const sp &msg)函式,這個是子類繼承AHandler接受訊息的介面函式,并且是處理訊息的重點,
其中,ALooper::handler_id mID是區分AHandler的標識,mMessages是存放接受訊息的容器,AHandler中的deliverMessage函式是呼叫子類的onMessageReceived的,
具體如下:

void AHandler::deliverMessage(const sp<AMessage> &msg) {
onMessageReceived(msg);
mMessageCounter++;
    if (mVerboseStats) {
        uint32_t what = msg->what();
        ssize_t idx = mMessages.indexOfKey(what);
        if (idx < 0) {
            mMessages.add(what, 1);
        } else {
            mMessages.editValueAt(idx)++;
        }
    }
}

接著上文ALooper中的loop函式監聽mEventQueue,如果有訊息時,呼叫訊息的deliver函式,而在上面的AMessage分析中,AMessage的deliver()呼叫的是AHandler的deliverMessage函式,
簡單的呼叫邏輯如下:

ALooper::loop()->AMessage::deliver()->AHandler::deliverMessage->AHandler::onMessageReceived()

AHandler中也有 AMessage 的有元 和 mLooper 指定相關的AMessage和ALooper,
并且在setID的時候是指了mLooper,

總評:

整個機制不是特別復雜,先單個類進行分析之后按照訊息流動程序串起來就能明白具體原因了,這里借鑒一句別人說話,閱讀原始碼不能將其看成靜態的狀態,而是每個訊息,每個機制都是流動的,按照流的思想閱讀便會事半功倍,到此,就整個流程就分析完了,如果有什么不對的地方,望大佬斧正啦,

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

標籤:其他

上一篇:[NOIP1998 提高組] 拼數

下一篇:30行代碼—基于auto.js自動給女朋友發早安

標籤雲
其他(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