Android基礎進階 - 消息機制 之Native層分析

目錄

  1. 基礎知識簡介
    • Linux eventfd事件等待/響應機制
    • Linux IO多路複用epoll
  2. Android消息機制Native層分析
    • nativeInit流程
    • nativePollOnce流程
    • nativeWake流程
  3. 資料
  4. 收穫

上一篇中關於ThreadLocal的使用,遺漏了一個點:ThreadLocal的回收,使用不當會操作內存泄漏。通過上一篇的分析我們知道了ThreadLocalMap.Entry中的key時ThreadLocal的弱引用,而value需要在堆上分配的內存,當key的強引用斷開之後,在gc的時候會被回收,就過時key爲null,但是value還存在。如果處理不當,可能會引發內存泄露。


圖片來自:徹底搞清楚ThreadLocal與弱引用

那麼key爲null了value何時被清除吶?
ThreadLocal的設計者考慮到這個問題,在每次調用ThreadLocal的set/get方法時會清除key爲null的value。但是誰知道下次什麼時候調用set/get,這是一種被動的清除方式。
除此之外ThreadLocal還提供了remove方法,可以有我們自動的進行清除。當ThreadLocal不再使用的時候,要記得調用remove。示例如下

public class ExampleUnitTest {
    private static ThreadLocal<String> sStrThreadlocal = new ThreadLocal<>();
    private static ThreadLocal<Integer> sIntegerThreadLocal = new ThreadLocal<>();

    @Test
    public void testThreadLocal(){

        //在主線程給Threadlocal賦值,請取出輸出

        sStrThreadlocal.set("aaa");
        String value = sStrThreadlocal.get();

        sIntegerThreadLocal.set(1);
        Integer intValue = sIntegerThreadLocal.get();
        System.out.println("111 curThreadId="+Thread.currentThread()+" strthreadLocalValue="+value
        +" intThreadLocalValue="+intValue);
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //最後在輸出下主線程的ThreadLocal值
        value = sStrThreadlocal.get();
        intValue = sIntegerThreadLocal.get();
        System.out.println("444 curThreadId="+Thread.currentThread()+" strthreadLocalValue="+value
                +" intThreadLocalValue="+intValue);

        //用完之後記得remove,雖然,ThreadLocalMap.Entry的key是ThreadLocal的弱引用,但是value佔用的內存空間還在。
        //被回收的場景有兩個,再次調用set或者get方法是會堅持map中是否有key爲空的Entry(由於key是弱引用,外部強引用依賴斷開後,gc時就會回收該key,
        // 回收後即可null),如果有則清除。

        //但是不知道什麼時候再次調用set或者get,這種被動的方式只能說是做了個保證,如果沒有調用set/get,就可能引發內存泄露
        //ThreadLocal提供了一個remove方法,由我們主動清除ThreadLocal對應的ThreadLocalMap.Entry中的堆內存
        sStrThreadlocal.remove();
        sIntegerThreadLocal.remove();
        value = sStrThreadlocal.get();
        intValue = sIntegerThreadLocal.get();
        System.out.println("after remove 444 curThreadId="+Thread.currentThread()+" strthreadLocalValue="+value
                +" intThreadLocalValue="+intValue);
    }
}

對應的輸出爲

111 curThreadId=Thread[main,5,main] strthreadLocalValue=aaa intThreadLocalValue=1
444 curThreadId=Thread[main,5,main] strthreadLocalValue=aaa intThreadLocalValue=1
after remove 444 curThreadId=Thread[main,5,main] strthreadLocalValue=null intThreadLocalValue=null

下面開始本篇的分析學習

我們在前面兩篇關於Android消息機制的分析學習中瞭解到MessageQueue中涉及到何Native的交互,比如nativeInit初始化Native層的MessageQueue;nativePollOnce阻塞;nativeWake喚醒。

問題是,爲什麼要這樣設計?直接Java層不是一樣可以管理維護消息隊列以及消息的讀取分發嗎?nativePollOnce真正的意義是什麼?這個問題一致困撓着我。通過反覆查資料翻源碼,終於瞭解了其流程。在消息機制Native層實現之前我們先了解下其中用到的一些Linux基礎知識。否則的話,很容易被卡住。

一、 基礎知識簡介

1.1 Linux eventfd事件等待/響應機制

Linux常用的進程/線程間通信機制有管道、信號量、消息隊列、信號、共享內存、socket等等,其中主要作爲進程/線程間通知/等待的有管道pipe和socket。從Linux 2.6.27版本開始增加了eventfd,主要用於進程或者線程間的通信,eventfd用於進程/線程間通信,效率比pipe高.

1.2 Linux IO多路複用epoll

epoll是Linux中最高效的IO多路複用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程序進行讀或寫操作

epoll有3個方法,epoll_create(), epoll_ctl(),epoll_wait()_

epoll_create :創建epoll句柄

int epoll_create(int size);
:用於創建一個epoll的句柄,size是指監聽的描述符個數, 現在內核支持動態擴展,該值的意義僅僅是初次分配的fd個數,後面空間不夠時會動態擴容。 當創建完epoll句柄後,佔用一個fd值.

使用完epoll後,必須調用close()關閉,否則可能導致fd被耗盡。

epoll_ctl:執行對需要監聽的文件描述符(fd)的操作

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

用於執行對需要監聽的文件描述符(fd)的操作,比如EPOLL_CTL_ADD
fd:需要監聽的文件描述符;
epoll_event:需要監聽的事件

epoll_wait:等待事件的到來

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

等待事件的到來,有三種情況會觸發返回
1. 發生錯誤 返回值爲負數
2. 等待超時(timeout)
3. 監測到某些文件句柄上有事件發生

epoll和eventfd結合使用
eventfd中無數據,線程觸發epoll_wait()的等待,該線程處於阻塞,另外一個線程通過往eventfd中write數據,使描述符可讀,epoll返回,這就達到了喚醒的目的。_

二、Android消息機制Native層簡單分析

我們先來回顧下消息機制中Java層MessageQueue的往隊列里加消息和從隊列裏面取消息的調用

//android.os.MessageQueue#enqueueMessage
boolean enqueueMessage(Message msg, long when) {
        ......

        synchronized (this) {
          
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            
            //如果消息鏈表爲空,或者插入的Message比消息鏈表第一個消息要執行的更早,直接插入到頭部
            if (p == null || when == 0 || when < p.when) {
                
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
               
                //否則在鏈表中找到合適位置插入
                //通常情況下不需要喚醒事件隊列,除非鏈表的頭是一個同步屏障,並且該條消息是第一條異步消息
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                //具體實現如下,這個畫張圖來說明
                //鏈表引入一個prev變量,該變量指向p也message(如果是for循環的內部第一次執行),然後把p進行向next移動,和需要插入的Message進行比較when
                Message prev;
                
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; 
                prev.next = msg;
            }

            //如果插入的是異步消息,並且消息鏈表第一條消息是同步屏障消息。
    //或者消息鏈表中只有剛插入的這一個Message,並且mBlocked爲true即,正在阻塞狀態,收到一個消息後也進入喚醒
喚醒誰?MessageQueue.next中被阻塞的nativePollOnce
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
//android.os.MessageQueue#next
Message next() {

        //native層NativeMessageQueue的指針
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

     ......
        for (;;) {
           
            //阻塞操作,當等待nextPollTimeoutMillis時長,或者消息隊列被喚醒
            //nativePollOnce用於“等待”, 直到下一條消息可用爲止. 如果在此調用期間花費的時間很長, 表明對應線程沒有實際工作要做,或者Native層的message有耗時的操作在執行
            nativePollOnce(ptr, nextPollTimeoutMillis);

            }

可以看到在MessageQueue#next會調用課程阻塞的native方法nativePollOnce,在MessageQueue#enqueueMessage 中如果需要喚醒會調用native方法nativeWake
問題是怎麼阻塞的,怎麼喚醒的,爲什麼要這樣設計,直接在Java層完成處理不可以嗎?

帶着這樣的困惑,開始Native層消息機制的分析學習。

2.1 MessageQueue Init流程

圖片來自:Android消息機制2-Handler(Native層)

調用Native方法初始化,返回值爲native層的NativeMessageQueue指針地址

//android.os.MessageQueue#MessageQueue   
 MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        //調用Native方法初始化,返回值爲native層的NativeMessageQueue指針地址
        mPtr = nativeInit();
    }

android_os_MessageQueue_nativeInit

//java native方法 nativeInit 的jni方法,返回類型long,即 mptr
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    //NativeMessageQueue是一個內部類
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();

    //返回爲NativeMessageQueue指針地址
    ......
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

NativeMessageQueue構造方法

會進行Native層的Looper創建。Java層創建Looper然後再創建MessageQueue,而在Native層則剛剛相反,先創建NativeMessageQueue然後再創建Looper。

// core/jni/android_os_MessageQueue.cpp

//NativeMessageQueue構造方法
//Java層創建Looper然後再創建MessageQueue,
//而在Native層則剛剛相反,先創建NativeMessageQueue然後再創建Looper

//MessageQueue是在Java層與Native層有着緊密的聯繫,
//但是Native層的Looper與Java層的Looper沒有任何關係
NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
            //Looper::getForThread()獲取當前線程的Looper,相當於Java層的Looper.myLooper()
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
    //如果爲空, new一個native層的Looper
        mLooper = new Looper(false);
        //相當於java層的ThreadLocal.set() ?
        Looper::setForThread(mLooper);
    }
}

Natvie層的Looper的構造
MessageQueue是在Java層與Native層有着緊密的聯繫,但是Native層的Looper與Java層的Looper沒有任何關係

// libutils/Looper.cpp

Looper::Looper(bool allowNonCallbacks)
    : mAllowNonCallbacks(allowNonCallbacks),
      mSendingMessage(false),
      mPolling(false),
      mEpollRebuildRequired(false),
      mNextRequestSeq(0),
      mResponseIndex(0),
      mNextMessageUptime(LLONG_MAX) {
      //eventfd事件句柄,負責線程通信,替換了之前版本的pipe 構造喚醒事件fd
    mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
    LOG_ALWAYS_FATAL_IF(mWakeEventFd.get() < 0, "Could not make wake event fd: %s", strerror(errno));

    AutoMutex _l(mLock);
    //進行epoll句柄的創建和初始化
    rebuildEpollLocked();
}

epoll句柄的創建、添加喚醒事件句柄到epoll

//libutils/Looper.cpp

void Looper::rebuildEpollLocked() {
    ......

    //epoll_create1創建一個epoll句柄實例,並註冊wake管道
    mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));

    ......

    //epoll事件結構體
    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd.get();

    int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &eventItem);
    
    for (size_t i = 0; i < mRequests.size(); i++) {
        const Request& request = mRequests.valueAt(i);
        struct epoll_event eventItem;
        request.initEventItem(&eventItem);

        //將喚醒事件句柄(request.fd),添加到epolled句柄(mEpollFd.get()),爲epoll添加一個喚醒機制
        int epollResult = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, request.fd, &eventItem);

       ......
    }
}

Native層的init流程主要內容如下:

  1. NativeQueueMessage和Looper的初始化。
  2. 構建了epoll句柄,向epoll中添加epoll事件註冊

2.2 消息讀取流程

圖片來自:Android消息機制2-Handler(Native層)

nativePollOnce

//core/jni/android_os_MessageQueue.cpp

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
        //將Java層傳過來的mPtr 轉換爲 nativeMessageQueue
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
         //調用nativeMessageQueue的pollOnce
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

NativeMessageQueue :: pollOnce

//core/jni/android_os_MessageQueue.cpp
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    ......
    //又調用了Natvie的Looper的pollOnce
    mLooper->pollOnce(timeoutMillis);
    ......
}

Native層Looper::pollOnce

//libutils/Looper.cpp

/**
* timeoutMillis:超時時長
* outFd:發生事件的文件描述符
* outEvents:當前outFd上發生的事件,包含以下4類事件
  EVENT_INPUT:可讀
  EVENT_OUTPUT:可寫
  EVENT_ERROR:錯誤
  EVENT_HANGUP:中斷

*outData:上下文數據

**/
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        ......
        //關鍵實現在pollInner中
        result = pollInner(timeoutMillis);
    }
}

Looper::pollInner
先會調用epoll_wait進入阻塞專題,喚醒的場景 想epoll中添加的epollevent等待事件發生或者超時觸發nativeWake()方法,會向eventfd寫入字符,進行喚醒。

然後進性要處理的事件收集,然後在做處理。Natvie的消息的處理順序如下

  1. 處理Native的Message,調用Native的Handler來處理該Message
  2. 處理Resposne數組,POLL_CALLBACK類型的事件
//libutils/Looper.cpp

int Looper::pollInner(int timeoutMillis) {

    ......

    // Poll.
    int result = POLL_WAKE;
    mResponses.clear();
    mResponseIndex = 0;

    // We are about to idle.
    mPolling = true;

    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    
    //等待事件發生或者超時觸發nativeWake()方法,會向eventfd寫入字符
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // No longer idling.
    mPolling = false;

    // Acquire lock.
    mLock.lock();

    // Rebuild epoll set if needed.
    if (mEpollRebuildRequired) {
        mEpollRebuildRequired = false;
        rebuildEpollLocked();
        goto Done;
    }

    // Check for poll error.
    if (eventCount < 0) {
        if (errno == EINTR) {
            goto Done;
        }
        an unexpected error: %s", strerror(errno));
        result = POLL_ERROR;
        goto Done;
    }

    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd.get()) {
            //已經喚醒,如果是EPOLLIN類型事件,讀取並清空eventfd中的數據
            if (epollEvents & EPOLLIN) {  
                awoken();
            } 
        } else {
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                int events = 0;
                if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
                if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
                if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
                if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
                pushResponse(events, mRequests.valueAt(requestIndex));
            } 
        }
    }

//上面是收集,Done中是處理的部分,很多設計都是採用這種設計,邏輯分離

Done: ;
        //1. 先處理Native的Message,調用Native的Handler來處理該Message
 
    mNextMessageUptime = LLONG_MAX;
    while (mMessageEnvelopes.size() != 0) {
        ......
        if (messageEnvelope.uptime <= now) {
            { 
                ......
                handler->handleMessage(message);
            } 
        } else {
              mNextMessageUptime = messageEnvelope.uptime;
            break;
        }
    }

   
//2. 再處理Resposne數組,POLL_CALLBACK類型的事件,比如一些
    // Invoke all response callbacks.
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
        }
    }
    return result;
}

消息獲取流程主要如下

  1. 依此調用NativeMessageQueue和looper中的pollonce,最終調用到Looper的pollInner
  2. pollInner會調用epoll_wait進入阻塞,喚醒的條件是epoll中添加的事件響應了或者發生了超時等。_
  3. 先執行Native層的Message,再執行Native層的Resposne數組,POLL_CALLBACK類型的事件(比如一些按鍵事件等)_
  4. 返回到Java層後再調用Java層MessageQueue中的讀取Message。

2.3 消息發送流程

圖片來自:Android消息機制2-Handler(Native層)

nativeWake

//core/jni/android_os_MessageQueue.cpp

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    //調用NativeMessageQueue的wake
    nativeMessageQueue->wake();
}

NativeMessageQueue::wake

//core/jni/android_os_MessageQueue.cpp

void NativeMessageQueue::wake() {
    //調用Native層Looper的wake
    mLooper->wake();
}

Looper::wake

libutils/Looper.cpp

void Looper::wake() {

    //write方法 向mWakeEventfd中寫一些內容,會把epoll_wait喚醒,線程就不阻塞了繼續發送
    //Native層的消息,然後處理之前的addFd事件,然後處理Java層的消息
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
    ......
}

消息發送流程如下

  1. Java層的enqueueMessage想java層的MessageQueue添加消息
  2. 如果需要喚醒調用native方法nativeWake
  3. 依次調用NativeMessageQueue和Looper的wake方法
  4. 最終調用writ方法想eventfd中寫入一些內容進行epoll_wait的喚醒。

Android消息機制的分析到這裏就結束了,最後來張消息處理家族類的關係圖


圖片來自:圖書:《深入理解Android 卷3》

三、資料

  1. Android源碼
  2. 圖書:《深入理解Android 卷3》
  3. Android消息機制2-Handler(Native層)
  4. Android Handler機制10之Native的實現
  5. Android 中 MessageQueue 的 nativePollOnce
  6. IO多路複用之epoll總結
  7. select/poll/epoll對比分析
  8. 深入分析 ThreadLocal 內存泄漏問題
  9. 徹底搞清楚ThreadLocal與弱引用

四、收穫

  1. 瞭解了消息機制在Native層處理流程
  2. 通過IO多路複用的epoll和eventfd進行事件的等待和喚醒實現
  3. 每次獲取消息時,先執行Native層的Message,再執行Natvice層addfd的response,之後執行Java層的MessageQueue中消息

感謝你的閱讀

Android消息機制就到這篇了,下篇我們進入Binder的分析學習,歡迎關注公衆號“音視頻開發之旅”,一起學習成長。

歡迎交流

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章