主頁 > 移動端開發 > Handler原始碼學習記錄(java層、native層)

Handler原始碼學習記錄(java層、native層)

2021-02-04 13:42:29 移動端開發

Handler原始碼學習記錄(java層、native層)

宗旨:學習記錄我看得懂就行!!!

模仿Handler原理,使用eventfd+epoll實作Handler基礎功能的小案例 -> gayhub地址(MessageQueueDemo)

java層

Handler.java(執行緒間切換的工具類)

三種訊息型別

同步訊息:最常用的訊息;
屏障訊息(同步屏障):該訊息無target,在訊息佇列中插入后會擋住后邊的所有同步訊息讓異步訊息先走,撤銷該屏障同步訊息才能繼續通行;
異步訊息:享有優先權的訊息,

//構函式中有boolean async傳參的,都是隱藏的不希望開發者使用,
//mAsynchronous 作用是讓該Handler發送的訊息全部都是異步訊息,
//開發者如果需要用到異步訊息,將Message手動setAsynchronous就可以了,
@hide
public Handler(boolean async) {
    ...
    mAsynchronous = async;
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
}

Looper.java

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed); //創建佇列
        mThread = Thread.currentThread(); //用來判斷是否在當前執行緒
}

//靜態方法
private static void prepare(boolean quitAllowed) { 
		// 引數quitAllowed,是否允許Looper退出,
		// MainLooper prepareMainLooper 中是false,主執行緒的Looper不允許退出,子執行緒的Looper是允許退出的,
        ... 
        sThreadLocal.set(new Looper(quitAllowed)); //Looper保證執行緒唯一
		...        
}

//靜態方法
public static void loop() {
			//由于靜態方法,無法直接使用mQueue
			//從sThreadLocal中取Looper,確保取到的是對應的執行緒Looper
			final Looper me = myLooper();
        	if (me == null) { //prepare檢查
            	throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        	}
        	final MessageQueue queue = me.mQueue;
					...
	        for (;;) { //死回圈,這就是主執行緒不退出的原因,
	          //從佇列獲取Message
            Message msg = queue.next(); //會阻塞
            ...
            try {
                msg.target.dispatchMessage(msg); //執行事件
                ...
            } 
            ...
            msg.recycleUnchecked(); //釋放該Message
        }
        ...
}

//注意,這不是靜態方法,能直接使用mQueue
public void quit() { //不再接受訊息,并且清空所有訊息(包括:延遲訊息、非延遲訊息),最后退出,
    mQueue.quit(false);
}

//注意,這不是靜態方法,能直接使用mQueue
public void quitSafely() { //安全退出,不再接受訊息,并且清空所有延遲訊息,會將所有非延遲訊息都派發出去,才退出,
    mQueue.quit(true);
}

MessageQueue.java

備注:注釋中 Msg代表同步訊息,Msg(A)代表異步訊息,|代表屏障訊息

//native方法
private native static long nativeInit();
private native static void nativeDestroy(long ptr);
private native void nativePollOnce(long ptr, int timeoutMillis);
private native static void nativeWake(long ptr);
private native static boolean nativeIsPolling(long ptr); //是否正在輪詢

private final boolean mQuitAllowed; //是否允許退出(主執行緒是false的)
private long mPtr; //NativeMessageQueue指標地址,靠它強轉回NativeMessageQueue*物件
Message mMessages; //訊息佇列Head(鏈表)
private boolean mQuitting; //是否退出中
private boolean mBlocked; //是否在阻塞中

MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

private void dispose() {
        if (mPtr != 0) {
            nativeDestroy(mPtr);
            mPtr = 0;
        }
    }

//訊息入隊方法
boolean enqueueMessage(Message msg, long when) {
        ...
        synchronized (this) {
            ...
            msg.when = when; //執行時間 (系統時間 + 延遲時間)
            Message p = mMessages;
            boolean needWake; //是否需要喚醒

            //情況1:當前佇列無訊息
            //情況2:使用sendMessageAtFrontOfQueue方法入隊,這個放啊when就是為0
            //情況3:新來的這條訊息執行時間比佇列中所有訊息的執行時間都要快,給它先執行,
            if (p == null || when == 0 || when < p.when) {
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            }else {
                //當前正在阻塞中
                //p.target == null 佇列頭是屏障訊息
                //新來的這條訊息是異步訊息
                //需要出發喚醒
                needWake = mBlocked && p.target == null && msg.isAsynchronous();

                //情況1
                //入隊的是Msg2
                //當前佇列 Msg1 -> null
                //上邊p的賦值,p = Msg1
                //for (;;) {
                //    prev = p;         //prev = Msg1
                //    p = p.next;       //p = null
                //    if (p == null) {  //退出回圈
                //        break;
                //    }
                //}
                //msg.next = p;         //Msg2 -> null
                //prev.next = msg;      //Msg1 -> Msg2 -> null

                //情況2 (如果Msg3 when 小于 Msg2,那么會走if的情況3)
                //入隊的是Msg3(when 15)
                //當前佇列 Msg2(when 10) -> Msg1(when 20) -> null
                //上邊p的賦值,p = Msg2
                //for (;;) {
                //    prev = p;            //prev = Msg2
                //    p = p.next;          //p = Msg1
                //    if (when < p.when) { //15 < 20 退出回圈
                //        break;
                //    }
                //}
                //msg.next = p;            //Msg3 -> Msg1 -> null
                //prev.next = msg;         //Msg2 -> Msg3 -> Msg1 -> null

                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    
                  	//情況3
                    //入隊的是Msg3
                    //當前佇列 | -> Msg2(A) -> Msg1 -> null
                    //prv = |
                    //p = Msg2(A)
                    //msg.next = p;    // Msg3 -> Msg2(A) -> Msg1 -> null
                    //prev.next = msg; // | -> Msg3 -> Msg2(A) -> Msg1 -> null
                    if (needWake && p.isAsynchronous()) {
                        //有屏障訊息會先執行Msg2(A),但是呢Msg2(A)時辰未到,不能喚醒,
                        needWake = false;
                    }
                }
                //新伙伴入隊后連接鏈表
                msg.next = p;
                prev.next = msg;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

//訊息出隊方法
Message next() {
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0; //下一次回圈的休眠時長
        for (;;) {

            ...

            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) { //當前msg是屏障訊息
                    //尋找異步訊息,一個個找,直到找到異步訊息就退出回圈
                    //這時prevMsg肯定是一個同步訊息,msg肯定是異步訊息
                    //例子 | -> Msg3 -> Msg2(A) -> Msg1 -> null
                    do {
                        prevMsg = msg; //prevMsg = Msg3
                        msg = msg.next; //msg = Msg2(A)
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // 這條訊息執行時間還沒到,設定一個休眠時長,準備進入休眠,
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        mBlocked = false;
                        if (prevMsg != null) {
                            //prevMsg不為空,證明msg是異步訊息,那么把佇列給連接上
                            //例子 佇列變成 | -> Msg3 -> Msg1 -> null
                            prevMsg.next = msg.next;
                        } else {
                            //msg為同步訊息
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    //沒有訊息
                    nextPollTimeoutMillis = -1;
                }

                if (mQuitting) { //Looper.quit()呼叫,觸發退出邏輯,
                    dispose();
                    return null;
                }

                //既然都要MessageQueue都準備要阻塞了,那我們來干點別的吧!!!
                //MessageQueue提供了IdleHandler佇列,讓我們在當前執行緒空閑的時候,做一些不那么耗時的事情,
                //這樣就可以做優先級低的業務邏輯從而提高性能,(例如:在主執行緒中,防止訊息過多導致ui卡頓,可以適當將優先級低的邏輯放到IdleHandler去處理)
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size(); //獲取IdleHandler佇列數量
                }
                if (pendingIdleHandlerCount <= 0) { //連IdleHandler佇列都沒東西處理,那就阻塞吧
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            //遍歷IdleHandler佇列,呼叫其queueIdle方法,處理開發者的邏輯,
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                //keep是提供給開發者選擇的,該IdleHandler是一次性的還是重復利用的,
                //true:執行完后不從IdleHandler佇列中移除,下一次空閑繼續執行,
                //false:執行完后就從IdleHandler佇列中移除了
                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

//屏障訊息入隊方法
private int postSyncBarrier(long when) {
        synchronized (this) {
            final int token = mNextBarrierToken++; //屏障訊息的身份id,用于移除令牌的
            final Message msg = Message.obtain();
            msg.markInUse(); //默認就是使用中了
            msg.when = when;
            msg.arg1 = token;

            Message prev = null;
            Message p = mMessages;
            if (when != 0) {
                //|(when )
                //例子:屏障訊息:|(when 5),當前佇列:Msg2(when 2)-> Msg1(when 5)-> null
                //回圈后:prev = Msg1,p = null
                while (p != null && p.when <= when) {
                    prev = p;
                    p = p.next;
                }
            }
            if (prev != null) {
                msg.next = p; // | -> null
                prev.next = msg;// Msg1 -> | -> null
                //佇列變成:Msg2 -> Msg1 -> | -> null
            } else {
                msg.next = p;
                mMessages = msg;
            }
            return token; //回傳屏障訊息的身份id
        }
    }

//移除屏障訊息方法
//注意:從next()邏輯可以看到,屏障訊息是不會出隊的,只能使用removeSyncBarrier方法才能移除掉,
public void removeSyncBarrier(int token) { //傳入訊息屏障身份id
        synchronized (this) {
            Message prev = null;
            Message p = mMessages;
            //找到token對應的屏障訊息
            //走完回圈時,p就是該屏障訊息
            while (p != null && (p.target != null || p.arg1 != token)) {
                prev = p;
                p = p.next;
            }
            if (p == null) {
                throw new IllegalStateException("The specified message queue synchronization "
                        + " barrier token has not been posted or has already been removed.");
            }
            final boolean needWake;
            //prev不為空,證明這個屏障訊息之前還有沒有處理的訊息
            //什么情況下prev不為空?可能是這個屏障訊息不是第一個屏障吧,第2個?第3個?...
            if (prev != null) { 
                prev.next = p.next; //這里了移除屏障訊息,讓鏈表重新連接起來
                needWake = false;
            } else {
                mMessages = p.next; //這里移除了屏障訊息,讓鏈表重新連接起來
                needWake = mMessages == null || mMessages.target != null;
            }
            p.recycleUnchecked();

            // If the loop is quitting then it is already awake.
            // We can assume mPtr != 0 when mQuitting is false.
            if (needWake && !mQuitting) {
                nativeWake(mPtr);
            }
        }
    }

//退出訊息佇列
void quit(boolean safe) { //是否為安全退出
        ...
        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

//不安全退出方法
//一個個訊息釋放掉
private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }

//安全退出方法
//將延時訊息都釋放掉,保留非延時訊息,讓這些訊息執行完,
private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
            if (p.when > now) {
                removeAllMessagesLocked();
            } else {
                //例如:
                //now = 5; 佇列:Msg3(when 2)-> Msg2(when 4)-> Msg1(when 6)-> null
                Message n;
                for (;;) {
                    n = p.next;
                    if (n == null) {
                        return;
                    }
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                //經過回圈后,n = Msg1,p = Msg2
                p.next = null; //斷開Msg2后邊的隊伍
                
                //釋放后邊的延時訊息
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }

native層(eventfd + epoll)

我看的是android6.0原始碼,eventfd負責通知,不知道啥版本之前是用pipe(管道)實作的通知后面被eventfd取代,epoll(IO多路復用)負責監聽,這兩個系統呼叫的科普在下面注釋會有,

為啥?我理解是eventfd占用的fd比pipe要少,pipe要占用兩個一個讀一個寫,

原始碼檔案整合(位置 -> MessageQueueDemo/native_source_code/)

原始碼地址:

android-6.0/system/core/libutils/Looper.cpp
android-6.0/system/core/include/utils/Looper.h
android-6.0/frameworks/base/core/jni/android_os_MessageQueue.cpp
android-6.0/frameworks/base/core/jni/android_os_MessageQueue.h

/*
 備注:
 native Looper中
    函式
    addFd
    removeFd
    sendMessage
    sendMessageDelayed
    removeMessages
    ...

    結構體
    Message
    Request
    Response
    ...

    類
    MessageHandler
    WeakMessageHandler
    ...

    還有向量mRequest、mResponse等等
    都是提供給native開發者使用訊息佇列相關邏輯(可以理解為native層的handler),
    與java層無關的,(o(╥﹏╥)o痛苦,剛開始看一臉懵逼,)
*/

//java層中native方法對應的函式
static JNINativeMethod gMessageQueueMethods[] = {
    /* name, signature, funcPtr */
    { "nativeInit", "()J", (void*)android_os_MessageQueue_nativeInit },
    { "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy },
    { "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },
    { "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },
    { "nativeIsPolling", "(J)Z", (void*)android_os_MessageQueue_nativeIsPolling },
    { "nativeSetFileDescriptorEvents", "(JII)V",
            (void*)android_os_MessageQueue_nativeSetFileDescriptorEvents },
};

//注冊JNI方法
int register_android_os_MessageQueue(JNIEnv* env) {
    int res = RegisterMethodsOrDie(env, "android/os/MessageQueue", gMessageQueueMethods,
                                   NELEM(gMessageQueueMethods));
    jclass clazz = FindClassOrDie(env, "android/os/MessageQueue");
    gMessageQueueClassInfo.mPtr = GetFieldIDOrDie(env, clazz, "mPtr", "J");
    gMessageQueueClassInfo.dispatchEvents = GetMethodIDOrDie(env, clazz,
            "dispatchEvents", "(II)I");
    return res;
}

//??????????????????????????????????????????????

//native層核心是Looper對eventfb + epoll的封裝,
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); //創建一個本地的訊息佇列
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }

    //RefBase相關文章 https://blog.csdn.net/u012124438/article/details/71075423
    nativeMessageQueue->incStrong(env); //強參考指標計數,智能指標(RefBase)

    //強轉為jlong,這個jlong是nativeMessageQueue地址
    //并保存到java層,之后java層便可以通過這個地址,強轉回nativeMessageQueue指標
    return reinterpret_cast<jlong>(nativeMessageQueue); 
}

NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread(); //從當前執行緒中獲取Looper
    if (mLooper == NULL) { //為空則創建并保存
        mLooper = new Looper(false);
        //通過 pthread_getpecific 和 pthread_setspecific 保證執行緒唯一
        //類似于java ThreadLocal
        Looper::setForThread(mLooper);
    }
}

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    /*
    eventfd相關知識

	api:
	創建一個eventfd物件(就像是打開一個eventfd的檔案,類似普通檔案的open操作,)
    int eventfd(unsigned int initval, int flags) 用來實作行程(執行緒)間的 等待/通知(wait/notify) 機制
    initval:該物件是一個內核維護的無符號的64位整型計數器,初始化為initval的值,
    flags:
        EFD_CLOEXEC:檔案被設定成 O_CLOEXEC,簡單說就是fork子行程時不繼承,對于多執行緒的程式設上這個值不會有錯的,
        EFD_NONBLOCK:功能同open的O_NONBLOCK,設物件為非阻塞狀態,
	        如果沒有設定這個狀態的話,read讀eventfd,并且計數器的值為0就一直堵塞在read呼叫當中,
	        要是設定了這個標志,就會回傳一個EAGAIN錯誤(errno = EAGAIN),
        EFD_SEMAPHORE:支持semophore語意的read,簡單說read一次值就減1
    return:用于事件通知的檔案描述符

    write():設定counter值,多次呼叫counter會累加,例: write(1);write(2); write(3); -> counter為6
    read():讀取counter值,并將counter值置0,如果是semophore就減1,

    */
    mWakeEventFd = eventfd(0, EFD_NONBLOCK); //mWakeEventFd這里表示喚醒Looper的檔案描述符
    LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd.  errno=%d", errno);

    AutoMutex _l(mLock);
    rebuildEpollLocked();
}

void Looper::rebuildEpollLocked() {
    if (mEpollFd >= 0) {
#if DEBUG_CALLBACKS
        ALOGD("%p ~ rebuildEpollLocked - rebuilding epoll set", this);
#endif
        //如果存在舊的epoll句柄,就先關閉,
        close(mEpollFd);
    }

    /*
	epoll相關知識
	(select/poll/epoll都是IO多路復用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程式進行讀或寫操作,本質上select/poll/epoll都是同步I/O,即讀寫是阻塞的,)
	在 select/poll中,行程只有在呼叫一定的方法后,內核才對所有監視的檔案描述符進行掃描,
	而epoll事先通過epoll_ctl()來注冊一個檔案描述符,一旦基于某個檔案描述符就緒時,
	內核會采用類似callback的回呼機制,迅速激活這個檔案描述符,當行程呼叫epoll_wait() 
	時便得到通知,(此處去掉了遍歷檔案描述符,而是通過監聽回呼的的機制,這正是epoll的魅力所在,)
	epoll優勢
	監視的描述符數量不受限制,所支持的FD上限是最大可以打開檔案的數目,具體數目可以cat /proc/sys/fs/file-max查看,一般來說這個數目和系統記憶體關系很大,以3G的手機來說這個值為20-30萬,
	IO性能不會隨著監視fd的數量增長而下降,epoll不同于select和poll輪詢的方式,而是通過每個fd定義的回呼函式來實作的,只有就緒的fd才會執行回呼函式,
	如果沒有大量的空閑或者死亡連接,epoll的效率并不會比select/poll高很多,但當遇到大量的空閑連接的場景下,epoll的效率大大高于select/poll,

	api:
	創建函式
	int epoll_create(int size);
	size:監聽的描述符個數,內部支持動態擴展的,
	return:回傳epoll的fd(ls /proc/<pid>/fd/ 可查;用完epoll后必須呼叫close()關閉否則可能導致fd被耗盡,)

	事件注冊函式
	int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
	epfd:是epoll_create()的回傳值;
	op:表示op操作,用三個宏來表示,分別代表添加、洗掉和修改對fd的監聽事件;
		EPOLL_CTL_ADD (添加)
		EPOLL_CTL_DEL (洗掉)
		EPOLL_CTL_MOD(修改)
	fd:需要監聽的檔案描述符;
	epoll_event:需要監聽的事件,struct epoll_event結構如下:
		struct epoll_event {
	    	__uint32_t events;  //Epoll事件
	    		events可取值:(表示對應的檔案描述符的操作)
				EPOLLIN :可讀(包括對端SOCKET正常關閉);
				EPOLLOUT:可寫;
				EPOLLERR:錯誤;
				EPOLLHUP:中斷;
				EPOLLPRI:高優先級的可讀(這里應該表示有帶外資料到來);
				EPOLLET: 將EPOLL設為邊緣觸發模式,這是相對于水平觸發來說的,
				EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后就不再監聽該事件
	    	epoll_data_t data;  //用戶可用資料
	  	};
    return:0:注冊成功  <0:出現錯誤,需要檢查 errno錯誤碼判斷錯誤型別

	等待事件
	int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
	epfd:等待epfd上的io事件,最多回傳maxevents個事件;
	events:用來從內核得到事件的集合;
	maxevents:events數量,該maxevents值不能大于創建epoll_create()時的size;
	timeout:超時時間(毫秒,0會立即回傳),
    return:0:超時回傳  >0:有n個fd觸發事件  <0:出現錯誤,需要檢查 errno錯誤碼判斷錯誤型別
    */
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);

    struct epoll_event eventItem;
  	//memset函式:作用是在一段記憶體塊中填充某個給定的值,它對較大的結構體或陣列進行清零操作的一種最快方法
    memset(& eventItem, 0, sizeof(epoll_event));
    eventItem.events = EPOLLIN; //可讀事件
    eventItem.data.fd = mWakeEventFd; //eventfd的fd排上用場了,
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem); //注冊epoll事件監聽

    ...

}

//??????????????????????????????????????????????

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    mPollEnv = env;
    mPollObj = pollObj;
    mLooper->pollOnce(timeoutMillis);
    mPollObj = NULL;
    mPollEnv = NULL;

    ...
}

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) { //死回圈

        ...

        /*
        enum {
            POLL_WAKE = -1, //表示Looper的wake方法被呼叫,write事件觸發
            POLL_CALLBACK = -2, //表示某個被監聽fd被觸發,
            POLL_TIMEOUT = -3, //表示等待超時
            POLL_ERROR = -4, //表示等待期間發生錯誤
        };
        */
        if (result != 0) { //當result不等于0時,就會跳出回圈,回傳到java層

            ...

            return result;
        }

        result = pollInner(timeoutMillis);
    }
}

int Looper::pollInner(int timeoutMillis) {
    
    ...

    struct epoll_event eventItems[EPOLL_MAX_EVENTS]; //事件集合(eventItems),EPOLL_MAX_EVENTS為最大事件數量,它的值為16
    //等待事件發生或者超時(timeoutMillis),如果有事件發生就會將放入事件集合(eventItems),回傳的eventCount為事件數量
    //如果沒有事件發生進入休眠等待,如果timeoutMillis時間后還沒有被喚醒,也會回傳0
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    ...

    // Check for poll error.
    if (eventCount < 0) {
        ...
        result = POLL_ERROR;
        ...
    }

    // Check for poll timeout.
    if (eventCount == 0) {
        ...
        result = POLL_TIMEOUT;
        ...
    }

    // Handle all events.
    ...
    for (int i = 0; i < eventCount; i++) { //遍歷事件集合(eventItems),處理事件
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd) { //處理eventfd(java層的事件)
            if (epollEvents & EPOLLIN) {
                awoken();
            } else { //其他檔案描述符,就進行它們自己的處理邏輯
                ...
            }
        } else {

            ...

        }
    }

    //下面是處理Native的Message
    ...

    return result;
}

void Looper::awoken() {
    ...
    uint64_t counter;
    //該TEMP_FAILURE_RETRY宏定義 用于忽略系統中斷造成的錯誤,常用于系統呼叫,
    //將eventfd的資料讀出來,其實就是一個消費的動作,
    TEMP_FAILURE_RETRY(read(mWakeEventFd, &counter, sizeof(uint64_t))); 
}

//??????????????????????????????????????????????

void NativeMessageQueue::wake() {
    mLooper->wake();
}

void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif

    uint64_t inc = 1;
    //向eventfd寫入1,write呼叫這樣就會激活epoll,從而讓pollOnce回傳到java層
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
        if (errno != EAGAIN) {
            ALOGW("Could not write wake signal, errno=%d", errno);
        }
    }
}

//??????????????????????????????????????????????

static void android_os_MessageQueue_nativeDestroy(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->decStrong(env); //智能指標 強參考計數減1,當參考數為0會自動呼叫解構式
}

總結:

read the fucking source code.

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

標籤:其他

上一篇:微信小程式之藍牙列印

下一篇:學習Makefile的記錄

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