使用C++11實現Android系統的Handler機制
封面出自:板栗懶得很背景
執行緒作為系統的基礎資源,相信大多數讀者都有使用到。一般情況下我們會直接開一個執行緒做一些耗時操作,處理完之後讓執行緒自動結束,資源被系統回收。這種簡單粗暴的方法不少讀者、甚至一些大廠的APP都在用。以Java語言為例,我們可以直接new一個Thread物件,然後覆蓋run方法,最後調一下start方法便可以成功執行一個執行緒。如果我們每次非同步做一些耗時處理都單獨開啟一個執行緒,比如非同步載入網路圖片這種高併發操作,每張圖片都開一個執行緒的話,必然會造成執行緒資源的浪費,而且也沒有很好的方法去處理跨執行緒通訊的問題。由於語言層面的低成本導致系統的執行緒資源被濫用,已經成為了一個很普遍的現象。
new Thread(){ @Override public void run() { //Do somethings } }.start()
Handler
Handler機制通過開啟一個子執行緒,並進入死迴圈,不停消費其它執行緒傳送過來的訊息,從而達到跨執行緒通訊的目的。Handler主要用於跨執行緒通訊,但同時也能在一定程度上覆用執行緒,是一種比較理想的執行緒使用方式。Android系統Handler主要包含以下三部分:
- Handler
- Looper
- Message & MessageQueue
Handler顧名思義就是訊息的處理類,同時也是訊息傳送的代理入口,通過呼叫Handler的相關介面傳送一條訊息,最終會被轉發到Looper,由Looper把Message加入到佇列的尾部。Looper是訊息迴圈驅動的動力所在,我們規定同一個執行緒只能擁有一個Looper,當Looper準備好之後會讓執行緒進入死迴圈,如果內部的Message佇列不為空時,則會不停的從訊息佇列頭部取出一條Message進行消費,直到佇列為空,Looper阻塞執行緒進入等待狀態。Message內部會記錄著傳送訊息的Handler,當被消費時就可以找到對應的Handler進行訊息處理,最終形成閉環。
實現
下面嘗試使用C++11來實現Android系統Handler機制,該實現主要由AlHandlerThread、AlHandler、AlLooperManager、AlLooper、AlMessageQueue和AlMessage六個類組成。我們規定一個執行緒只能擁有一個AlLooper,因此需要一個AlLooperManager負責對所有執行緒的AlLooper物件進行管理,如果當前執行緒已經擁有了AlLooper物件,則直接使用當前執行緒的物件,保證AlLooper唯一。而AlMessageQueue則是一個支援執行緒阻塞和喚醒的訊息佇列。AlHandlerThread則是一個封裝了std::thread和AlLooper的簡單執行緒實現,僅僅是為了方便使用AlLooper,與Android系統中的HandlerThread實現是一致的。
AlHandler
AlHandler提供兩個建構函式,第一個只有Callback引數,該建構函式會預設獲取當前執行緒的AlLooper,如果當前沒有AlLooper,則會丟擲異常。第二個建構函式支援傳入一個AlLooper,該AlLooper物件將會從AlHandlerThread獲取。sendMessage函式負責把AlMessage轉發到AlLooper,值得注意的是,在傳送到AlLooper之前會先給AlMessage的成員變數target賦值,也就是當前AlHandler物件的指標。dispatchMessage函式用於在AlLooper中消費訊息。
class AlHandler { public: typedef function<void(AlMessage *msg)> Callback; public: AlHandler(Callback callback); AlHandler(AlLooper *looper,Callback callback); void sendMessage(AlMessage *msg) { _enqueueMessage(msg); } void dispatchMessage(AlMessage *msg) { if (callback) { callback(msg); } } private: void _enqueueMessage(AlMessage *msg) { if (this->looper) { msg->target = this; this->looper->sendMessage(msg); } } private: AlLooper *looper = nullptr; Callback callback = nullptr; };
AlLooperManager
AlLooperManager只有一個功能,那就是管理所有建立的AlLooper物件,所以它是一個單例,程式碼雖然簡單,但卻很重要。由於作業系統會為每一個執行緒分配一個唯一的tid(Thread ID,Linux下可以使用pthread_self獲取到),所以我們可以通過tid的唯一性來管理所有執行緒建立的AlLooper物件。該類的create和get函式分別用於建立新的AlLooper物件,以及獲取快取的物件。建立一個物件時首先需要檢查快取中是否存在該執行緒對應的AlLooper,如果已經存在則應該避免重複建立,直接返回空指標即可。而get函式用於從快取中獲取一個物件,如果快取中沒有則返回空指標。remove用於銷燬一個AlLooper,一般會線上程銷燬時使用。這幾個函式都需要保證執行緒安全。
private: AlLooperManager() : Object() {} AlLooperManager(AlLooperManager &e) : Object() {} ~AlLooperManager() {} /** * 為當前執行緒建立Looper * @return 當前執行緒的Looper */ AlLooper *create(long tid) { std::lock_guard<std::mutex> guard(mtx); auto it = looperMap.find(tid); if (looperMap.end() == it) { auto *looper = new AlLooper(); looperMap[tid] = looper; return looper; } return nullptr; } /** * 獲取當前執行緒的Looper * @return 當前執行緒的Looper */ AlLooper *get(long tid) { std::lock_guard<std::mutex> guard(mtx); auto it = looperMap.find(tid); if (looperMap.end() == it) { return nullptr; } return it->second; } /** * 銷燬當前執行緒的Looper */ void remove(long tid) { std::lock_guard<std::mutex> guard(mtx); auto it = looperMap.find(tid); if (looperMap.end() != it) { looperMap.erase(it); auto *looper = it->second; delete looper; } } private: static AlLooperManager *instance; std::map<long,AlLooper *> looperMap; std::mutex mtx; };
AlLooper
AlLooper主要有prepare、myLooper和loop三個靜態函式。prepare用於為當前執行緒準備一個AlLooper,因為我們規定同一個執行緒只能擁有一個AlLooper物件,如果嘗試在一個執行緒重複呼叫該函式函式將引發異常。myLooper用於獲取當前執行緒的AlLooper,如果在該函式呼叫之前沒有呼叫過prepare將會獲得一個空指標。loop是AlLooper的核心函式,呼叫該函式後執行緒將進入死迴圈,AlLooper會依次從訊息佇列頭部取出AlMessage進行消費。前面提到AlMessage有一個名為target的成員變數,這個變數是一個AlHandler物件,所以這裡直接呼叫AlHandler::dispatchMessage函式把訊息回傳,由AlHandler進行處理。sendMessage函式則用於在訊息佇列尾部插入一條訊息。
class AlLooper : public Object { public: /** * 為執行緒準備一個Looper,如果執行緒已經存在Looper,則報錯 */ static void prepare() { AlLooper *looper = AlLooperManager::getInstance()->create(Thread::currentThreadId()); assert(nullptr != looper); } /** * 獲取當前執行緒的Looper * @return 前執行緒的Looper */ static AlLooper *myLooper() { AlLooper *looper = AlLooperManager::getInstance()->get(Thread::currentThreadId()); assert(nullptr != looper); return looper; } static void exit(); /** * 迴圈消費訊息 */ static void loop() { myLooper()->_loop(); } void _loop() { for (;;) { AlMessage *msg = queue.take(); if (msg) { if (msg->target) { msg->target->dispatchMessage(msg); } delete msg; } queue.pop(); } } void sendMessage(AlMessage *msg) { queue.offer(msg); } private: AlLooper(); AlLooper(AlLooper &e) : Object() {} ~AlLooper(); private: AlMessageQueue queue; };
AlMessageQueue和AlMessage
AlMessage比較簡單,主要包含幾個public的成員變數,用於區分訊息型別以及附帶一些資訊。AlMessageQueue則是一個阻塞佇列,當嘗試從一個空佇列獲取AlMessage時將會造成執行緒阻塞,如果其它執行緒向空佇列新增一個AlMessage物件將會喚醒阻塞的執行緒。這是驅動訊息迴圈消費的重要一環。
class AlMessage { public: int32_t what = 0; int32_t arg1 = 0; int64_t arg2 = 0; Object *obj = nullptr; } class AlMessageQueue : public Object { public: AlMessageQueue() { pthread_mutex_init(&mutex,nullptr); pthread_cond_init(&cond,nullptr); } virtual ~AlMessageQueue() { pthread_mutex_lock(&mutex); invalid = true; pthread_mutex_unlock(&mutex); clear(); pthread_mutex_destroy(&mutex); pthread_cond_destroy(&cond); } void offer(AlMessage *msg) { pthread_mutex_lock(&mutex); if (invalid) { pthread_mutex_unlock(&mutex); return; } queue.push_back(msg); pthread_cond_broadcast(&cond); pthread_mutex_unlock(&mutex); } AlMessage *take() { pthread_mutex_lock(&mutex); if (invalid) { pthread_mutex_unlock(&mutex); return nullptr; } if (size() <= 0) { if (0 != pthread_cond_wait(&cond,&mutex)) { pthread_mutex_unlock(&mutex); return nullptr; } } if (queue.empty()) { pthread_mutex_unlock(&mutex); return nullptr; } AlMessage *e = queue.front(); queue.pop_front(); pthread_mutex_unlock(&mutex); return e; } int size(); void clear(); private: pthread_mutex_t mutex; pthread_cond_t cond; std::list<AlMessage *> queue; bool invalid = false; };
AlHandlerThread
AlLooper準備好後就可以線上程中使用了,這裡我們把執行緒和AlLooper封裝到一起方便使用。AlHandlerThread會在內部開啟一個執行緒,該執行緒會呼叫run函式,線上程開始執行後依次呼叫AlLooper的prepare和loop函式即可進入訊息消費流程,AlLooper::exit()用於線上程結束前銷燬AlLooper物件。
class AlHandlerThread { public: AlLooper *getLooper() { return mLooper; } private: void run() { AlLooper::prepare(); mLooper = AlLooper::myLooper(); AlLooper::loop(); AlLooper::exit(); } private: std::thread mThread = thread(&AlHandlerThread::run,this); AlLooper *mLooper = nullptr; };
最後我們建立一個AlHandler物件,並傳入一個從AlHandlerThread獲取的AlLooper物件和一個處理回撥函式Callback,便可以讓Handler機制執行起來。由於AlLooper可以是任意一個執行緒的物件,所以便實現了跨執行緒的通訊。如果我們把AlMessage封裝成一個"Task",當我們要處理一個耗時任務時,把任務封裝成一個"Task"傳送到Handler進行處理,通過該方法可以輕易實現執行緒的複用,而不需要重複申請銷燬執行緒。
mThread = AlHandlerThread::create(name); mHandler = new AlHandler(mThread->getLooper(),[this](AlMessage *msg) { /// Do something. });
結語
以上便是Android系統Handler機制的介紹,以及使用C++11的實現。上面展示的是部分核心程式碼,省略了很多,實際操作還需要處理很多問題,比如執行緒安全、執行緒的退出、AlLooper的銷燬等。文章原始碼出自hwvc專案,感興趣的讀者可以閱讀完整的AlHandlerThread原始碼實現。
hwvc專案:
https://github.com/imalimin/hwvc/tree/develop
AlHandlerThread原始碼:
https://github.com/imalimin/hwvc/blob/develop/src/common/thread/AlHandlerThread.cpp
到此這篇關於使用C++11實現Android系統的Handler機制的文章就介紹到這了,更多相關C++11 Handler機制內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!