Handler處理消息相關(guān)源碼解析

一耕渴、handler的用法

1.1钧栖、用于線程切換

Handler handler = new Handler(Looper.getMainLooper());
new Thread(new Runnable() {
    @Override
    public void run() {
        handler.post(() -> {
            binding.tv.setText("hello world");
        });
    }
}).start();

1.2、線程間通信

為了防止handler導(dǎo)致內(nèi)存泄露副硅,handler對(duì)Activity采用弱引用,因弱引用不會(huì)影響Activity對(duì)象生命周期翅萤。

static class MyHandler extends Handler {
    WeakReference<MainActivity> mActivityWeakReference;恐疲、
    public MyHandler(MainActivity activity) {
        mActivityWeakReference = new WeakReference<>(activity);
    }
    @Override
    public void handleMessage(@NonNull Message msg) {
        MainActivity activity = mActivityWeakReference.get();
        if (activity != null && msg!=null && msg.obj!=null) {
             //todo 
        }
    }
}
MyHandler myHandler = new MyHandler(MainActivity.this);
new Thread(new Runnable() {
    @Override
    public void run() {
        Message message = Message.obtain();
        message.obj = "aaa";
        mMyHandler.sendMessage(message);
    }
}).start();

1.3、任務(wù)延時(shí)執(zhí)行

static class MyRunnalbe implements Runnable {
    @Override
    public void run() {
        Log.e("test", "test memory leak?");
    }
}
//發(fā)送延時(shí)任務(wù)
Handler handler3 = new Handler(Looper.getMainLooper());
handler3.postDelayed(new MyRunnalbe(), 10000);

最后大家不要忘了套么,一定記得在Activity的onDestroy及時(shí)解除Message對(duì)Handler的強(qiáng)引用培己。

@Override
protected void onDestroy() {
    super.onDestroy();
    mMyHandler.removeCallbacksAndMessages(null);
}

二、Handler(Loope(MessageQueue))的創(chuàng)建

2.1胚泌、Handler創(chuàng)建

先看看handler的構(gòu)造函數(shù)省咨。

構(gòu)造函數(shù)肯定是相互調(diào)用,只需要看最多的參數(shù)的那個(gè)

public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) { xxxx }

  • 從構(gòu)造可以看出玷室,Handler的創(chuàng)建需要Looper零蓉,沒(méi)有Looper玩不轉(zhuǎn)吶,我們之前寫handler用法的時(shí)候穷缤,都是在主線程創(chuàng)建Handler敌蜂,它構(gòu)造里面會(huì)調(diào)用Looper.myLoop()獲取Looper。

  • ThreadLocal可用于線程間的數(shù)據(jù)隔離津肛,這是解決多線程并發(fā)的一種手段 (不共享可變資源)紊册。將線程需要的Looper保存在ThreadLocal里面。

  • 我們App的入口函數(shù)就是ActivityThread的main方法,一般在main方法里面就做兩件事快耿,第一件就是啟動(dòng)創(chuàng)建主線程的Looper囊陡,啟動(dòng)Looper輪詢;第二件事就是將ApplicationThread的binder對(duì)象注冊(cè)給AMS(AMS拿到它后就可以調(diào)用App的方法)掀亥。loop輪詢一旦跳出了死循環(huán)撞反,App直接就拋異常了。

  • 如果最后一個(gè)參數(shù)async為true搪花,那么通過(guò)handler.sendMessage的message都會(huì)打上異步消息的標(biāo)簽遏片。

//from Handler
public Handler(@Nullable Callback callback, boolean async) {
   .....  
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
           "Can't create handler inside thread " + Thread.currentThread()
                                              + " that has not called Looper.prepare()");
    }
  .......
}

//from Looper
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
//myLooper直接從Threadlocal中取
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
//創(chuàng)建主線程Looper嘹害,因?yàn)橹骶€程Looper不支持退出
public static void prepareMainLooper() {
    prepare(false);
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();
    }
}
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

//from ActivityThread
public static void main(String[] args) {
    ....... 
    //創(chuàng)建主線程Looper
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    //向AMS注冊(cè),將app的binder對(duì)象給AMS
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    .....
    //啟動(dòng)輪詢
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

Q: 如果直接在子線程里面new Handler()創(chuàng)建吮便,從ThreadLocals里面肯定是取不到對(duì)應(yīng)的looper的笔呀,所以會(huì)拋異常。

需要顯式調(diào)用Looper.prepare()才行髓需。

子線程中彈吐司也要先通過(guò)Looper.prepare()創(chuàng)建Looper许师,不然也會(huì)奔潰。

原因同上??


2.1.1僚匆、ThreadLocal原理

都說(shuō)ThreadLocal可用于線程間數(shù)據(jù)隔離微渠,完美的解決并發(fā)問(wèn)題,那啥原理呢咧擂? 先看看用法逞盆。
public void set(T value) {
    Thread t = Thread.currentThread(); //獲取當(dāng)前線程
    ThreadLocalMap map = t.threadLocals; //線程的t.threadLocals
    if (map != null)
        map.set(this, value);  //直接添加
    else
        createMap(t, value);   //那就給當(dāng)前線程創(chuàng)建這個(gè)ThreadLocalMap類型的t.threadLocals成員
}
private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length; 
    int i = key.threadLocalHashCode & (len-1); //斐波那契散列算法
    for (Entry e = tab[i]; e != null;e = tab[i = nextIndex(i, len)]) { //開放尋址法解決沖突
        ThreadLocal<?> k = e.get();    
        if (k == key) {
            e.value = value;
            return;
        }
        if (k == null) {
            replaceStaleEntry(key, value, i); 
            return;
        }
    }
    tab[i] = new Entry(key, value); //key就是ThreadLocal
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)  rehash();
}
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

threadlocal.set(xxx)時(shí),會(huì)從當(dāng)前線程(主線程/子線程)取出它的ThreadLocalMap類型的成員threadLocals松申,不為null就直接添加了云芦,為空則先創(chuàng)建。ThreadLocalMap底層采用數(shù)組實(shí)現(xiàn)贸桶,不像HashMap采用數(shù)組+鏈表/紅黑樹實(shí)現(xiàn)舅逸。這里的元素的index采用斐波那契散列算法實(shí)現(xiàn),采用開放尋址法解決散列沖突刨啸,具體參考ThreadLocalMap的hash算法。數(shù)組元素Entry很有特點(diǎn)识脆。

static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

Entry是一個(gè)弱引用類设联,直接通過(guò)entry.get()可以獲取到Threadlocal引用,因其作為entry的key灼捂,只能對(duì)應(yīng)一個(gè)值value离例。同時(shí)也參與table數(shù)組index的hash算法(i = key.threadLocalHashCode & (len-1))。如通過(guò)entry.get()獲取threadLocal為null時(shí)悉稠,此時(shí)value不為null宫蛆,就有發(fā)生泄漏可能,一般建議就是用完及時(shí)進(jìn)行remove掉的猛。

2.2耀盗、Looper的創(chuàng)建

public static void prepare() {
    prepare(true);
}
public static void prepare() {
    prepare(true);
}
//quitAllowed 是否支持退出,默認(rèn)是支持退出的卦尊。但是主線程的looper不支持退出叛拷。
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();

Handler創(chuàng)建需要Looper,Looper創(chuàng)建需要MessageQueue岂却。如果多次調(diào)用Loopr.prepare來(lái)創(chuàng)建Looper會(huì)報(bào)錯(cuò)忿薇,即在一個(gè)線程里面創(chuàng)建多個(gè)Looper會(huì)報(bào)錯(cuò)裙椭。

主線程的Looper當(dāng)然不支持退出,不然隨便調(diào)用looper.quit署浩,App就奔潰了揉燃。用戶子線程中通過(guò)Looper.prepare()創(chuàng)建Looper默認(rèn)是true,支持退出的筋栋。

2.3炊汤、MessageQueue的創(chuàng)建

private long mPtr; // used by native code
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

MessageQueue創(chuàng)建時(shí),創(chuàng)建了一個(gè)long類型的成員二汛,native可以通過(guò)這個(gè)long指針直接強(qiáng)轉(zhuǎn)成native對(duì)象用婿崭,這里是底層的NativeMessageQueue的指針

從這里可以看出肴颊,一個(gè)線程可以創(chuàng)建無(wú)數(shù)個(gè)Handler氓栈,但只能有一個(gè)Looper,一個(gè)MessageQueue婿着。

三授瘦、Looper.loop() 與 handler.sendMessage(xx)

3.1、開啟輪詢消息Looper.loop()

入口函數(shù)ActivityThread.main()方法中竟宋,就開啟了Looper.loop()提完。

//from Looper
public static void loop() {
    ......
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    for (;;) {
        Message msg = queue.next(); // 可能阻塞
        ......
        msg.target.dispatchMessage(msg);
       ...... 
        msg.recycleUnchecked(); //消息進(jìn)入循環(huán)池
    }
}

Q: 如果讓我們寫個(gè)Handler分發(fā)、處理Message丘侠,你會(huì)怎么寫徒欣?
因?yàn)镠andler的分發(fā)事件可能在其他線程,被稱為生產(chǎn)者蜗字,處理Message的Handler被稱為消費(fèi)者打肝,典型的生產(chǎn)消費(fèi)模型,肯定會(huì)考慮生產(chǎn)多了+消費(fèi)慢挪捕,生產(chǎn)少了+消費(fèi)快的場(chǎng)景粗梭,此時(shí)偽代碼如下:

public synchronized void loop() {
    long now = System.currentTimeMillis();
    int count;                                   //mMessageQueue.top()就是獲得一個(gè)Message
    while ((count = mMessageQueue.size()) == 0 || mMessageQueue.top().when > now) {
        long diff = (count == 0) ? 0 : (now - mMessageQueue.top().when);
        try {
            this.wait(diff);           //這里就類似Message msg = queue.next();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    ......
    this.notifyAll();
}

系統(tǒng)肯定不會(huì)用wait/notify+synchronized這種性能欠佳的組合實(shí)現(xiàn),對(duì)的级零,底層用的是Epoll機(jī)制實(shí)現(xiàn)
來(lái)看看MessageQueue的Next方法断医。

Message next() {
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();  //被調(diào)用說(shuō)明后面的代碼可能會(huì)引起線程阻塞(此處不懂)
        }
        nativePollOnce(ptr, nextPollTimeoutMillis)  //epoll_wait
        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            /此處msg沒(méi)有target(handler),那就表示這個(gè)msg為同步屏障消息奏纪,配合異步消息使用的
            if (msg != null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;    
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    //獲取需要等待的時(shí)間
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                     //終于拿到一個(gè)可用的Message了鉴嗤,單鏈表取出Message的操作,模板代碼
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;  //所以mMessage就是下一個(gè)要處理的Message序调。記住了
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                nextPollTimeoutMillis = -1;  //-1傳給native的epoll_wait躬窜,表示永遠(yuǎn)不能喚醒,需要主動(dòng)被wake
            }
              //messageQueue為empty了 或者 mMessage是將來(lái)才處理的(when還沒(méi)到)
            if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                mBlocked = true;   //那就繼續(xù)阻塞
                continue;
            }
            //每次處理的handler最多只能是4個(gè)炕置,多了不處理荣挨?男韧??默垄?
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler
            boolean keep = idler.queueIdle(); //idleHandler的queueIdle返回值true/false處理不同
            if (!keep) {                      
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;  //處理完idle消息后此虑,立馬就喚醒了,接下來(lái)再取新的口锭。
    }
}

nextPollTimeoutMillis(阻塞的超時(shí)時(shí)間)有兩種情況

  • 第一種:首次啟動(dòng)loop朦前,nextPollTimeoutMillis = 0,進(jìn)入到nativePollOnce(ptr, nextPollTimeoutMillis) 這里鹃操,其底層epoll_wait(epFd,events,MAX_EVENTS,timeout)阻塞韭寸,這個(gè)阻塞可因超時(shí)時(shí)間到了或者關(guān)注的事件可讀了被喚醒,這里timeout為0荆隘,所以很快會(huì)被喚醒恩伺。

  • 第二種:非首次,nextPollTimeoutMillis就來(lái)自 (msg.when-now)椰拒,epoll_wait的超時(shí)時(shí)間就可能不是0了晶渠,時(shí)間未到,就阻塞著燃观,直到被喚醒褒脯。

3.1.1、nativePollOnce(ptr, nextPollTimeoutMillis)的底層調(diào)用

看源碼C++代碼可以使用官方網(wǎng)站:Android Code Search

private native void nativePollOnce(long ptr, int timeoutMillis); 

之前說(shuō)java層創(chuàng)建MessageQueue的時(shí)候缆毁,native層返回一個(gè)NativeMessageQueue的指針番川,NativeMessageQueue的創(chuàng)建伴隨著native層的Looper的創(chuàng)建。

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue(); //源碼是CPP所以new的
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }
    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue); //long類型的指針
}
//NativeMessageQueue的構(gòu)造函數(shù):通過(guò)參數(shù)化表進(jìn)行構(gòu)造
NativeMessageQueue::NativeMessageQueue() :mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);  //創(chuàng)建了一個(gè)native層Looper脊框,給native線程綁定了一個(gè)Looper
        Looper::setForThread(mLooper);
    }
}

nativePollOnce的調(diào)用颁督,發(fā)現(xiàn)java層的long類型指針強(qiáng)轉(zhuǎn)成nativeMessageQueue,這是常規(guī)操作缚陷,java層傳遞過(guò)來(lái)的timeoutMillis一路攜帶适篙。

//nativePollOnce方法
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {  //java的long類型指針這里又強(qiáng)轉(zhuǎn)成nativeMessageQueue
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
   ....
    mLooper->pollOnce(timeoutMillis);
  ......
}
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
        ......
        //與handler功能關(guān)系不大的邏輯略去往核,這里是用底層Looper干其他的事情箫爷。
        ......
        result = pollInner(timeoutMillis);
    }
}

下面??的方法是重點(diǎn) epoll_wait出來(lái)了。

//timeoutMillis一路攜帶
int Looper::pollInner(int timeoutMillis) {
    // Adjust the timeout based on when the next message is due.
    if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime);
        if (messageTimeoutMillis >= 0
                && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) {
            timeoutMillis = messageTimeoutMillis;
        }
    }
   .....
    int result = POLL_WAKE;
    mPolling = true;
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];  //經(jīng)典的Epoll機(jī)制
    int eventCount = epoll_wait(mEpollFd.get(), eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    mPolling = false;
    .....
    for (int i = 0; i < eventCount; i++) {
        const SequenceNumber seq = eventItems[i].data.u64;
        uint32_t epollEvents = eventItems[i].events;
        if (seq == WAKE_EVENT_FD_SEQ) {
            if (epollEvents & EPOLLIN) {
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } else {
           ......//處理其他非handler的功能
        }
    }
    return result;
}
void Looper::awoken() {
    uint64_t counter;  
    //及時(shí)從管道m(xù)WakeEventFd.get()讀描述符中讀取數(shù)據(jù)
    TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));
}

3.1.1.1聂儒、為了理解這里的調(diào)用可以看個(gè)管道配合Epoll的例子 具體來(lái)自這個(gè)博客

早期版本4.4及其以下版本虎锚,handler底層用的管道+Epoll實(shí)現(xiàn),現(xiàn)在采用eventfd()+Epoll機(jī)制實(shí)現(xiàn)衩婚。

..... //Mac上用CLion跑demo發(fā)現(xiàn)Mac找不到epoll的庫(kù)
int main(){ 
    int ret,pid;
    int pipe_fd[2];          
    if((ret=pipe(pipe_fd))<0) {     //調(diào)用linux的pipe方法窜护,獲取讀寫描述符,讀0 寫1
        return -1;
    }
    const int MAXEVENTS = 1024;  
    struct epoll_event events[MAXEVENTS];  //就緒列表   
    struct epoll_event ev;             //epoll_event事件                
    ev.data.fd = pipe_fd[0];          //讀描述
    ev.events = EPOLLIN|EPOLLET;  //EPOLLIN事件觸發(fā)表示對(duì)應(yīng)的文件描述符上有可讀數(shù)據(jù)非春。EPOLLET邊緣觸發(fā) 
    int epfd=epoll_create(MAXEVENTS);  //創(chuàng)建epoll的句柄柱徙,size(MAXEVENTS)用來(lái)告知內(nèi)核監(jiān)聽的數(shù)目
    ret=epoll_ctl(epfd,EPOLL_CTL_ADD,pipe_fd[0],&ev);//注冊(cè)要監(jiān)聽的事件缓屠,pipe_fd[0]再次傳入。
    if (ret != 0){
        close(pipe_fd[0]);
        close(pipe_fd[1]);
        close(epfd);
        return -1;
    }
    //fork()有意思:會(huì)返回兩次分別在父子進(jìn)程返回护侮,pid>0表示的是父進(jìn)程敌完,返回的pid==0表示子進(jìn)程,
    if((pid=fork())>0) {  
        int count=epoll_wait(epfd,events,MAXEVENTS,5000); 
        char r_buf[100];
        for(int i=0;i<count;i++){
            if((events[i].data.fd==pipe_fd[0])&&(events[0].events&EPOLLIN)){
                int r_num=read(pipe_fd[0],r_buf,100); //及時(shí)從管道中讀
                cout<<"read "<<r_num<<" bytes data from the pipe : "<<r_buf<<endl;
            }
        } 
        close(pipe_fd[1]);
        close(pipe_fd[0]);
        close(epfd); 
    }else if(pid==0){
        close(pipe_fd[0]); //子進(jìn)程的資源繼承自父進(jìn)程
        char w_buf[100];   
        strcpy(w_buf,"hello world");
        if(write(pipe_fd[1],w_buf,12)!=-1) //往寫描述符中寫入
        cout<<"data had written!\n";
        close(pipe_fd[1]);//write
    }
    return 0;
}

epoll的三個(gè)關(guān)鍵函數(shù)epoll_create返回epollFd羊初,epoll_ctl注冊(cè)要監(jiān)聽的事件滨溉,epoll_wait阻塞等待關(guān)注的事件到來(lái)衡瓶。

epoll_wait函數(shù)說(shuō)明

  • 返回值:成功時(shí)返回就緒列表中文件描述符的個(gè)數(shù)驾胆,超時(shí)返回0,異常返回-1端辱。
  • timeout:指定epoll的超時(shí)時(shí)間得哆,單位是毫秒脯颜。當(dāng)timeout為-1是,epoll_wait調(diào)用將永遠(yuǎn)阻塞柳恐,直到某個(gè)事件發(fā)生伐脖。當(dāng)timeout為0時(shí),epoll_wait調(diào)用將立即返回乐设。
  • maxevents:指定最多監(jiān)聽多少個(gè)事件讼庇,必須大于0。
  • events:檢測(cè)到事件近尚,將所有就緒的事件從內(nèi)核事件表中復(fù)制到它的第二個(gè)參數(shù)events指向的數(shù)組中蠕啄。
3.1.1.2、mWakeEventFd

Looper創(chuàng)建時(shí)會(huì)創(chuàng)建Epoll實(shí)例戈锻,然后注冊(cè) mWakeEventFd歼跟。

//Looper創(chuàng)建
Looper::Looper(bool allowNonCallbacks):.....{
    mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
    AutoMutex _l(mLock);
    if (mEpollFd >= 0) {
        mEpollFd.reset();
    }
    // Allocate the new epoll instance and register the WakeEventFd.
    mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC));
    epoll_event wakeEvent = createEpollEvent(EPOLLIN, WAKE_EVENT_FD_SEQ);
    int result = epoll_ctl(mEpollFd.get(), EPOLL_CTL_ADD, mWakeEventFd.get(), &wakeEvent);
   ......
}
//捕捉一下WakeEventFd的操作
using unique_fd = unique_fd_impl<DefaultCloser>;
android::base::unique_fd mWakeEventFd;
mWakeEventFd.reset(eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC));
void Looper::wake() {
    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
   ......
}
void Looper::awoken() {
    uint64_t counter;  
    TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));
}

之前例子采用管道讀寫,通過(guò)pipe方法實(shí)現(xiàn)格遭,輕易獲得讀哈街、寫描述符,讀寫描述符分開的拒迅,而這里讀寫都是mWakeEventFd.get()骚秦,通過(guò)eventfd函數(shù)創(chuàng)建fd。
什么是eventfd
eventfd是Linux 2.6提供的一種系統(tǒng)調(diào)用璧微,它可以用來(lái)實(shí)現(xiàn)事件通知作箍。eventfd包含一個(gè)由內(nèi)核維護(hù)的64位無(wú)符號(hào)整型計(jì)數(shù)器,創(chuàng)建eventfd時(shí)會(huì)返回一個(gè)文件描述符前硫,進(jìn)程可以通過(guò)對(duì)這個(gè)文件描述符進(jìn)行read/write來(lái)讀取/改變計(jì)數(shù)器的值胞得,從而實(shí)現(xiàn)進(jìn)程間通信。(mac完全用不了這個(gè)epoll和eventfd屹电,尷尬)
EPOLLIN的觸發(fā)條件是內(nèi)核接收到新發(fā)來(lái)的數(shù)據(jù)阶剑,從不可讀狀態(tài)變?yōu)榭勺x狀態(tài)跃巡。

一旦我們 nativePollOnce(ptr, nextPollTimeoutMillis)經(jīng)過(guò)底層走一圈,混到timeout時(shí)間到了牧愁,epoll_wait返回瓷炮,不阻塞了,就進(jìn)入java層做取消息的操作递宅。

因java取消息它是優(yōu)先判斷鏈表頭是不是同步屏障娘香,所以接下來(lái)先看同步屏障消息和異步消息??。

3.1.2办龄、同步屏障消息和異步消息

Message的三種類型 普通消息烘绽、同步屏障、異步消息俐填,區(qū)分它們很簡(jiǎn)單安接。

  • 普通消息 我們一般發(fā)送的Message默認(rèn)就是,其target就是handler英融。
  • 同步屏障 因沒(méi)有message.target盏檐,所以不需要分發(fā),(正常發(fā)送的消息必須指定target)驶悟,沒(méi)有handler的消息就可以認(rèn)定為同步屏障胡野。它也有時(shí)間戳,只會(huì)影響后面的消息痕鳍。消息隊(duì)列可以插入多個(gè)同步屏障硫豆,插入同步屏障沒(méi)有喚醒線程處理消息,普通消息插入會(huì)喚醒笼呆。插入同步屏障的會(huì)返回token(序列號(hào))熊响,作為remvoe同步屏障方法的參數(shù)用。
  • 異步消息 設(shè)置過(guò)msg.setAsynchronous(true)的Message诗赌。
 //MessageQueue的next方法部分邏輯  被喚醒后汗茄,優(yōu)先判斷是否同步屏障+異步消息組合
 if (msg != null && msg.target == null) {
    do {
        prevMsg = msg;
        msg = msg.next;    
    } while (msg != null && !msg.isAsynchronous());
 }

如果鏈表頭(msg != null && msg.target == null)是同步屏障消息,那就去找下一個(gè)異步消息铭若,二者狼狽為奸洪碳,阻擋正常普通消息的處理,此時(shí)普通消息就算達(dá)到when了奥喻,也只能乖乖等著偶宫。一般同步屏障+異步消息用于UI繪制這種需要立即響應(yīng)的場(chǎng)景非迹。

3.1.1.1环鲤、系統(tǒng)中使用同步屏障的場(chǎng)景
//當(dāng)我們更新Ui時(shí)喜歡調(diào)用:
binding.tv.requestLayout();
    // ||||其調(diào)用路徑||||
    view->requestLayout()
       viewRootImpl->requestLayout()
           viewRootImpl->scheduleTraversals()
-----------------------------------------------------------------------------
//from viewRootImpl
void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;        //標(biāo)記位為true
                                           //發(fā)送同步屏障消息
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
                                          //編舞者添加監(jiān)聽
        mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        ......
    }
} 

//from MessageQueue
//插入同步屏障消息到鏈表,不走h(yuǎn)andler插入憎兽,直接在MessagQueue里面插入
private int postSyncBarrier() {
   long when = SystemClock.uptimeMillis();
    synchronized (this) {
        final int token = mNextBarrierToken++;   
        final Message msg = Message.obtain();    //沒(méi)有設(shè)置target操作
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;
        Message prev = null;
        Message p = mMessages;          //鏈表頭消息
        if (when != 0) {               //同步屏障也遵循按處理時(shí)間插入冷离,沒(méi)搞特殊化
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) {     // 插入同步消息
            msg.next = p;
            prev.next = msg;
        } else {                //插入到鏈表頭
            msg.next = p;
            mMessages = msg;
        }
        return token;         //同步屏障的token,用于取消用
    }
}

當(dāng)我們調(diào)用View的requestLayout時(shí),沒(méi)有立馬繪制,而是由ViewRootImpl來(lái)協(xié)助走繪制流程吵冒,先發(fā)送同步屏障消息,注冊(cè)Choreographer監(jiān)聽(用戶添加監(jiān)聽還可用于屏幕幀率獲取西剥,固定脈沖頻率60Hz或者120Hz痹栖,它向SurfaceFlinger發(fā)起請(qǐng)求,獲取脈沖Vsync的到來(lái))瞭空,脈沖信號(hào)到來(lái)的回調(diào)-->FrameDisplayEventReceiver的onVsync()揪阿。

//from Choreographer
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable {
    @Override
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
        ........
        mTimestampNanos = timestampNanos;
        mFrame = frame;
        Message msg = Message.obtain(mHandler, this); //this就是??的run方法
        msg.setAsynchronous(true);  //發(fā)送異步消息
        mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    }
    @Override
    public void run() {
        mHavePendingVsync = false;
        doFrame(mTimestampNanos, mFrame);
    }
}
void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {
        long intendedFrameTimeNanos = frameTimeNanos;
        startNanos = System.nanoTime(); //當(dāng)前時(shí)間跟脈沖時(shí)間diff
        final long jitterNanos = startNanos - frameTimeNanos;
        if (jitterNanos >= mFrameIntervalNanos) { 
            final long skippedFrames = jitterNanos / mFrameIntervalNanos;
            if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {  //這個(gè)日志太常見--丟幀提示
                Log.i(TAG, "Skipped " + skippedFrames + " frames!  "
                        + "The application may be doing too much work on its main thread.");
            }
            final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
            frameTimeNanos = startNanos - lastFrameOffset;
        }
        if (frameTimeNanos < mLastFrameTimeNanos) {
            //請(qǐng)求下一個(gè)垂直同步信號(hào)
            scheduleVsyncLocked();
            return;
        }
       ......
        mLastFrameTimeNanos = frameTimeNanos;
    }
    ......
    //Choreographer的一些回調(diào)
   doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
   doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
   doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
}

// frmo ViewRootImpl 前面mChoreographer.postCallbac設(shè)置的回調(diào)轉(zhuǎn)到mTraversalRunnable
final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}
void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;   
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); //移除同步屏障消息
        performTraversals();  //接下里就是測(cè)量、布局咆畏、繪制了
    }
}

同步屏障插入了南捂,脈沖信號(hào)來(lái)臨時(shí),onVsync回調(diào)異步消息插入旧找,當(dāng)從消息隊(duì)列里面取出消息時(shí)溺健,如果同步屏障消息在鏈表頭,那么插入的異步消息會(huì)很快得到處理钮蛛,在真正開始執(zhí)行繪制前鞭缭,移除同步屏障。

如果消息隊(duì)列為空 or 也沒(méi)有同步消息 or 待處理的普通消息還沒(méi)有到時(shí)間魏颓,那就該處理IdleHandler了??岭辣。

3.1.3、IdleHandler的處理

  • 處理時(shí)機(jī) 當(dāng)MessageQueue為空或者接下來(lái)的Message的when還沒(méi)有到的時(shí)候甸饱。
  • 使用場(chǎng)景 可用于啟動(dòng)優(yōu)化易结,針對(duì)一些不是啟動(dòng)必須的操作,可以放到首頁(yè)idleHandler中做延遲處理柜候。
  • idler.queueIdle()的返回值 默認(rèn)是false搞动,就只處理一次后就移除了,如果返回true渣刷,可能會(huì)處理多次鹦肿。

看來(lái)確實(shí)只有在目前沒(méi)有可處理的Message了才會(huì)考慮處理idleHandler。

3.2辅柴、發(fā)送消息之handler.sendMessage(xx)

//from Handler
public final boolean post(Runnable r){
   return  sendMessageDelayed(getPostMessage(r), 0);
}
public final boolean postDelayed(Runnable r, long delayMillis){
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}
public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0)  delayMillis = 0;
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this; //這個(gè)this就是Handler
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

用戶使用的各種post/postDalay/sendMessage最終都是會(huì)走到enqueueMessage方法箩溃,在這里給Message設(shè)置target,target就是Handler碌嘀。如果handler構(gòu)造參數(shù)asynchronous傳入true,就在這里給Message添加異步標(biāo)志涣旨。

boolean enqueueMessage(Message msg, long when) {
   .....
   //考慮多線程,這里用了 synchronized
    synchronized (this) {
        ......
        msg.markInUse();
        msg.when = when;
        Message p = mMessages; //mMessages表示接下來(lái)要處理的Message股冗,可能是null
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            msg.next = p;          
            mMessages = msg;
            needWake = mBlocked;  //前面處在nativePollOnce的epoll_wait那就阻塞了為true
        } else {
            //要插入的消息比接下來(lái)要處理的消息(mMessages)的when還晚
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

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

在這里傳入的msg不可能是同步屏障消息霹陡,因同步屏障消息直接通過(guò)postSyncBarrier插到隊(duì)列里面。(注意postSyncBarrier是私有api,我們用不了烹棉,除了反射)

  • if (p == null || when == 0 || when < p.when) 表示鏈表頭為null或要處理的比鏈表頭mMessages還要早攒霹,那肯定要將msg插入到鏈表頭了。一般mBlocked為true浆洗,然后就喚醒了催束。
  • needWake = mBlocked && p.target == null && msg.isAsynchronous() 能走到這里就說(shuō)明插入的消息處理時(shí)間比隊(duì)列頭的msg晚,一般是不喚醒的伏社,但是有特殊情況(鏈表頭為同步屏障+首個(gè)異步消息插入)抠刺。
    假設(shè)當(dāng)前mBlocked是true,隊(duì)頭的mMessages是同步屏障摘昌,要插入的消息為異步消息矫付,neekWake此時(shí)為true。
    • 1第焰、進(jìn)入循環(huán)买优,同步屏障消息在鏈表頭且隊(duì)列中無(wú)異步消息,如果要插入的消息是異步消息挺举,就會(huì)被喚醒杀赢。
    • 2、進(jìn)入循環(huán)湘纵,同步屏障消息在鏈表頭且隊(duì)列中有異步消息脂崔,即使再插入消息,也不會(huì)喚醒梧喷。

總結(jié)一下插入消息能被喚醒的場(chǎng)景
1砌左、鏈表頭為null 或者 插入的普通消息/異步消息比鏈表頭消息的處理時(shí)間早。
2铺敌、鏈表頭為同步屏障+首個(gè)異步消息插入汇歹。

如當(dāng)前鏈表頭是同步屏障消息,插入的異步消息按時(shí)間處理順序即使插入到隊(duì)列中很靠后的位置偿凭,也不影響異步消息的處理時(shí)效产弹,仍是優(yōu)先處理的。

//loop取消息時(shí)弯囊,如果鏈表頭是同步屏障消息痰哨,優(yōu)先從隊(duì)列里面找異步消息處理。
 ......
 nativePollOnce(ptr, nextPollTimeoutMillis)
    synchronized (this) {
        final long now = SystemClock.uptimeMillis();
        Message prevMsg = null;
        Message msg = mMessages;
        //看到msg沒(méi)有target(handler)匾嘱,那就表示這個(gè)Message為同步屏障消息斤斧,配合異步消息使用
        if (msg != null && msg.target == null) {
            do {
                prevMsg = msg;
                msg = msg.next;    
            } while (msg != null && !msg.isAsynchronous());
        }
        ......
        //處理普通消息
        ......

3.2.1、nativeWake(mPtr)

//from java MessageQueue
private native static void nativeWake(long ptr);
//from NativeMessageQueue
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();
}
void NativeMessageQueue::wake() {
    mLooper->wake();
}

// from native的Looper
void Looper::wake() {
    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
    .....
}

nativeWake是在mWakeEventFd.get()里面寫入了unit64_t的1霎烙。mWakeEventFd.get()的eventfd包含一個(gè)由內(nèi)核維護(hù)的64位無(wú)符號(hào)整型計(jì)數(shù)器撬讽,進(jìn)程可以通過(guò)對(duì)這個(gè)文件描述符進(jìn)行read/write來(lái)讀取/改變計(jì)數(shù)器的值蕊连。當(dāng)這里寫入數(shù)值時(shí),那epoll_wait就能返回了锐秦,這里的inc改成其他值應(yīng)該也可以,因?yàn)槲覀冎魂P(guān)心epoll_wait是否返回不再阻塞盗忱。
總結(jié)一下:epoll_wait在timeout超時(shí)時(shí)間到來(lái)時(shí)或mWakeEventFd.get()有數(shù)據(jù)可讀時(shí)會(huì)返回酱床,不再阻塞。

3.3趟佃、處理消息之handleMessage

用戶可能用3種方式去處理消息

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {  //1
        handleCallback(msg);   
    } else {
        if (mCallback != null) {  //2
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);  //3
    }
}

Message在獲取時(shí)扇谣,有傳入callback就走1,handler構(gòu)造函數(shù)傳入了Callback闲昭,就走2罐寨,默認(rèn)(mCallback.handleMessage(msg))的值為false,false表示我們還可以繼續(xù)處理3序矩,如果是返回值為true鸯绿,一旦被處理過(guò)了,那我們重寫的handleMessage方法還是沒(méi)法調(diào)用簸淀。

四瓶蝴、關(guān)于Handler的其他問(wèn)題

4.1、一般我們固定頻率的任務(wù)(定時(shí)器)怎么實(shí)現(xiàn)比較好呢租幕?

  • 方式一舷手、handler.postDelayed,但是精度么呵呵呵劲绪,handler發(fā)送普通的延時(shí)消息男窟,通過(guò)epoll_wait的超時(shí)機(jī)制實(shí)現(xiàn),而且前面有Message如果處理不及時(shí)會(huì)導(dǎo)致精度更差了贾富。
  • 方式二歉眷、Timer:底層通過(guò)synchronize+wait(diff)實(shí)現(xiàn)。
  • 方式三颤枪、AlarmManager姥芥,用的少。
  • 方式四:固定頻率的線程池執(zhí)行任務(wù)汇鞭。
  • 方式五:Choreographer添加監(jiān)聽實(shí)現(xiàn)凉唐,頻繁獲取幀率可用它,相當(dāng)于16ms的定時(shí)器霍骄。不知道有沒(méi)得兄弟在實(shí)際場(chǎng)景中用過(guò)台囱。

4.2、主線程Looper為啥沒(méi)有ANR?

入口函數(shù)ActivityThread的main中開啟了Loop.loop()读整,在queue.next()獲取下一個(gè)Message時(shí)會(huì)走底層的epoll機(jī)制簿训,進(jìn)行阻塞等到超時(shí)時(shí)間到來(lái)時(shí)喚醒,就跟我們用synchronize+wait(diff)類似,wait是線程阻塞强品。
ANR機(jī)制 ANR大家都知道是應(yīng)用程序無(wú)響應(yīng)膘侮,而且這個(gè)應(yīng)用程序無(wú)響應(yīng)的彈窗不是在我們app進(jìn)程,而是在AMS所在的進(jìn)程(SystemServer進(jìn)程)的榛。
Service TimeOut: 前臺(tái)20s琼了,后臺(tái)服務(wù)200s。
廣播 Timeout: 前臺(tái)廣播10s夫晌,后臺(tái)廣播60s
contentProvider Timeout: 10s
InputDispatching Timeout: 5s
這里的超時(shí)雕薪,前面3大組件都是啟動(dòng)創(chuàng)建超時(shí)導(dǎo)致ANR,最后一個(gè)是用戶操作導(dǎo)致ANR晓淀。

先就從最簡(jiǎn)單的Service啟動(dòng)看起吧所袁。

//通過(guò)startService啟動(dòng)service
Intent intent = new Intent(MainActivity.this, MyService.class);
startService(intent);

//from ContextImpl 很關(guān)鍵的一個(gè)類,四大組件中只要attach上下文就需要它
@Override
public ComponentName startService(Intent service) {
    .......
    return startServiceCommon(service, false, mUser);
}
private ComponentName startServiceCommon(Intent service, boolean requireForeground,UserHandle user) {
        ...... //binder調(diào)用來(lái)了
        ComponentName cn = ActivityManager.getService().startService(
            mMainThread.getApplicationThread(), service, service.resolveTypeIfNeeded(
                        getContentResolver()), requireForeground,
                        getOpPackageName(), user.getIdentifier());
        ........
        return cn;
}
public static IActivityManager getService() {
    return IActivityManagerSingleton.get();
}

我們的app能夠調(diào)用AMS的方法凶掰,肯定要先拿到AMS的binder代理對(duì)象燥爷,這里通過(guò)ActivityManager.getService()就可以搞到,發(fā)起binder調(diào)用AMS的startService懦窘。(AMS是在SystemServer進(jìn)程局劲,跟我們app不在同一個(gè)進(jìn)程,看到源碼里面以I開頭的類奶赠,基本就當(dāng)做binder對(duì)象類處理)鱼填,同樣AMS想要調(diào)用我們app的方法,也要通過(guò)binder調(diào)用毅戈,也要搞到我們app的binder代理對(duì)象(IApplicationThread)才行苹丸。
AMS有啥用,四大組件的創(chuàng)建啟動(dòng)都?xì)w他管苇经,我們可以通過(guò)Instrumentation去捕捉四大組件的生命周期方法赘理。

//from AMS
AMS-->startService(XXX)
    AMS--->startServiceLocked(XXX)
        AMS--->startServiceInnerLocked(XXX)
           AMS--->bringUpServiceLocked(XXX)
                 AMS--->realStartServiceLocked(XXX)
                 
private final void realStartServiceLocked(ServiceRecord r,
        ProcessRecord app, boolean execInFg) throws RemoteException {
  //創(chuàng)建前埋入炸彈
    bumpServiceExecutingLocked(r, execInFg, "create");
    boolean created = false;
    try {
        ....... //創(chuàng)建Service
        app.thread.scheduleCreateService(r, r.serviceInfo,
                mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                app.repProcState);
    } catch (DeadObjectException e) {
      .......
    } finally {
        if (!created) {
            //服務(wù)創(chuàng)建done 拆除炸彈
            serviceDoneExecutingLocked(r, inDestroying, inDestroying);
        }
    }
.......
}

Service創(chuàng)建前,肯定有很多準(zhǔn)備工作扇单,先判斷要啟動(dòng)的service的進(jìn)程是否存在商模,不存在還得走Zygote創(chuàng)建進(jìn)程,然后進(jìn)程創(chuàng)建后還得向AMS注冊(cè)蜘澜,如果進(jìn)程存在施流,且ServiceRecord沒(méi)有值,就表示之前沒(méi)有創(chuàng)建過(guò)service鄙信,就走首次創(chuàng)建Service邏輯瞪醋,Service創(chuàng)建前,就埋入bomb装诡,Service創(chuàng)建完成银受,拆除bomb践盼。

//from AMS
AMS----bumpServiceExecutingLocked(XXX)
    AMS----scheduleServiceTimeoutLocked(XXX)
    
static final int SERVICE_TIMEOUT = 20*1000;
static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10; 
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
   .......
    Message msg = mAm.mHandler.obtainMessage(ActivityManagerService.SERVICE_TIMEOUT_MSG);
    msg.obj = proc;
    mAm.mHandler.sendMessageDelayed(msg,
       proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}

所謂的埋入炸彈就是通過(guò)handler發(fā)了一個(gè)延時(shí)消息消息,延時(shí)的值前臺(tái)服務(wù)20s宾巍、后臺(tái)服務(wù)200s咕幻,這個(gè)Handler不是我們app的mH,是AMS進(jìn)程中的Handler顶霞。

再看看真正創(chuàng)建Service的操作

//from ApplicationThread
public final void scheduleCreateService(IBinder token,
        ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
    updateProcessState(processState, false);
    CreateServiceData s = new CreateServiceData();
    s.token = token;
    s.info = info;
    s.compatInfo = compatInfo;
    sendMessage(H.CREATE_SERVICE, s);
}
public void handleMessage(Message msg) {
    if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
    switch (msg.what) {
         .....
        case CREATE_SERVICE:
            handleCreateService((CreateServiceData)msg.obj);
            break;
          ......
//真正創(chuàng)建Service的邏輯
private void handleCreateService(CreateServiceData data) {
   LoadedApk packageInfo = getPackageInfoNoCheck(data.info.applicationInfo, data.compatInfo);
   Service service = null;
    try {//先創(chuàng)建上下文ContextImpl
        ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
        //獲取所在的進(jìn)程
        Application app = packageInfo.makeApplication(false, mInstrumentation);
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        //直接反射創(chuàng)建Service實(shí)例
        service = packageInfo.getAppFactory()
                .instantiateService(cl, data.info.name, data.intent);
        //加載資源        
        context.getResources().addLoaders(
                app.getResources().getLoaders().toArray(new ResourcesLoader[0]));
       .....
      //添加上下文
        service.attach(context, this, data.info.name, data.token, app,
                        ActivityManager.getService());
        service.onCreate();
        mServices.put(data.token, service);
        try { //binder調(diào)用AMS的serviceDoneExecuting
            ActivityManager.getService().serviceDoneExecuting(
                    data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    } catch (Exception e) {
        ......
    }
}

Service創(chuàng)建是在我們自己的app進(jìn)程肄程,自己通過(guò)反射構(gòu)造的Service實(shí)例,調(diào)用service的onCreate的后确丢,及時(shí)binder調(diào)用AMS的方法serviceDoneExecuting拆除??(remove掉那個(gè)延時(shí)Message)绷耍。

//from AMS
AMS---serviceDoneExecuting(XXX)
   AMS---serviceDoneExecutingLocked(XXX)

private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying,
        boolean finishing) {
    if (r.executeNesting <= 0) {
        if (r.app != null) {
            .......
            r.app.execServicesFg = false;
            r.app.executingServices.remove(r);
            if (r.app.executingServices.size() == 0) {
                //要啟動(dòng)的Service搞完了就移除消息
                mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
            } else if (r.executeFg) {
              ......
            }
            ......
        }
       .....
    }
}

如果說(shuō)Service創(chuàng)建時(shí)間很長(zhǎng)吐限,那就處理ActivityManagerService.SERVICE_TIMEOUT_MSG的超時(shí)Message鲜侥。

//from AMS
final class MainHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {
        .....
        case SERVICE_TIMEOUT_MSG: {
            mServices.serviceTimeout((ProcessRecord)msg.obj);
    ........
void serviceTimeout(ProcessRecord proc) {
   ......
    if (anrMessage != null) {
        mAm.mAppErrors.appNotResponding(proc, null, null, false, anrMessage);
    }
}

//from AppErrors
final void appNotResponding(ProcessRecord app, ActivityRecord activity,
        ActivityRecord parent, boolean aboveSystem, final String annotation) {
   ......
        // Bring up the infamous App Not Responding dialog (infamous--臭名昭著的)
        Message msg = Message.obtain();
        msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
        msg.obj = new AppNotRespondingDialog.Data(app, activity, aboveSystem);
        mService.mUiHandler.sendMessage(msg);
    }
}

ANR的彈窗就可以顯示出來(lái)了。(infamous--臭名昭著的)诸典。
所以ANR跟Loopr.loop()完全是兩個(gè)不同的功能描函,ANR需要Looper的支持,沒(méi)得Looper狐粱,ANR不知道何時(shí)彈窗舀寓。

4.3、BlockCanary如何檢測(cè)UI卡頓肌蜻。

先看看Loopr.loop()互墓,仔細(xì)看看在message處理前后有啥。

//from Looper
public static void loop() {
    ......
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    for (;;) {
        Message msg = queue.next(); // 可能阻塞
        .....
        //logger--   分發(fā)message
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to " + msg.target + " " +
                    msg.callback + ": " + msg.what);
        }
        ......
        msg.target.dispatchMessage(msg);
       ...... 
        //logger--  message處理完成
        if (logging != null) {
            logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
        }
        msg.recycleUnchecked(); //消息進(jìn)入循環(huán)池
    }
}
public void setMessageLogging(@Nullable Printer printer) {
    mLogging = printer;
}

從上面看logging.println在msg處理前后都打印日志了蒋搜,如果能夠捕捉到日志打印的時(shí)機(jī)篡撵,基本是就可以知道handleMessage耗時(shí)多少了,拿到耗時(shí)就可以判斷出是否卡頓了豆挽,而且貼心的是這個(gè)Printer是Looper的成員變量育谬,可以讓用戶設(shè)置自己的Printer。

我們看看blockCanary的自定義Printer以及設(shè)置時(shí)機(jī)

class LooperMonitor implements Printer {
    private long mStartTimestamp = 0;
    private long mStartThreadTimestamp = 0;
   ......
    @Override
    public void println(String x) {
        if (mStopWhenDebugging && Debug.isDebuggerConnected()) {
            return;
        }
        if (!mPrintingStarted) {
            mStartTimestamp = System.currentTimeMillis();
            ......
        } else {
            final long endTime = System.currentTimeMillis();
            if (isBlock(endTime)) {
                notifyBlockEvent(endTime);
            }
           ......
        }
    }
private boolean isBlock(long endTime) {
    return endTime - mStartTimestamp > 3000;
}  

//設(shè)置Looper的Printer
//BlockCanary
public void start() {
    if (!mMonitorStarted) {
        mMonitorStarted = true;
        Looper.getMainLooper().setMessageLogging(new LooperMonitor(xxxx));
    }
}

BlockCanary自定義了Printer帮哈,然后捕捉兩次時(shí)間diff膛檀,diff大于3s就認(rèn)定有卡頓發(fā)生,可以dump出堆棧顯示娘侍。設(shè)置Printer的時(shí)機(jī)是在入口類BlockCanary的start方法里面咖刃。

4.4、池化技術(shù)

池化技術(shù)采用享元設(shè)計(jì)模式實(shí)現(xiàn)憾筏,Message的池化僵缺、Glide中的BitmapPool、EventBus中的PendingPost等等踩叭。
我們關(guān)心的是池的設(shè)計(jì)磕潮,采用啥容器存儲(chǔ)翠胰、size選取、以及recycle自脯、obtain方法的處理之景。
Message的池化:存儲(chǔ)采用單鏈表實(shí)現(xiàn),鏈表長(zhǎng)度是50膏潮,主要看obtain和recycle方法锻狗。

//Handler中Message的池化操作
public final class Message implements Parcelable {
    public static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;
    private static final int MAX_POOL_SIZE = 50;  //size最大50
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; 
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }
    public void recycle() {
        if (isInUse()) {
            return;
        }
        recycleUnchecked();
    }
    void recycleUnchecked() {
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
}

sPool、sPoolSync都是靜態(tài)的焕参,每個(gè)Message對(duì)象共享轻纪,obtain就是從單鏈表中取出一個(gè)Message,單鏈表沒(méi)有就new 新的Message叠纷,recycle是先重置Message刻帚,然后看message池中size是否大于MAX_POOL_SIZE,超了就不加入涩嚣,單鏈表的常規(guī)操作崇众,簡(jiǎn)直不要太簡(jiǎn)單。

Glide中的BitmapPool

//LruBitmapPool默認(rèn): 只有1 or 4張的屏幕大小的Bitmap可以復(fù)用航厚;
GroupedLinkedMap<Key, Bitmap> groupedMap = new GroupedLinkedMap<>();
存put 取get 基于有限的size來(lái)看是否需要trim顷歌。


EventBus采用ArrayList來(lái)存儲(chǔ),超了就不添加

private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();
static PendingPost obtainPendingPost(Subscription subscription, Object event) {
    synchronized (pendingPostPool) {
        int size = pendingPostPool.size();
        if (size > 0) {
            PendingPost pendingPost = pendingPostPool.remove(size - 1);
            pendingPost.event = event;
            pendingPost.subscription = subscription;
            pendingPost.next = null;
            return pendingPost;
        }
    }
    return new PendingPost(event, subscription);
}
static void releasePendingPost(PendingPost pendingPost) {
    pendingPost.event = null;
    pendingPost.subscription = null;
    pendingPost.next = null;
    synchronized (pendingPostPool) {
        // Don't let the pool grow indefinitely
        if (pendingPostPool.size() < 10000) {
            pendingPostPool.add(pendingPost);
        }
    }
}


五幔睬、補(bǔ)充

MessageQueue 中還有一些native方法未涉及到眯漩,就是基于NativeMessagQueue做些其他的功能實(shí)現(xiàn)。是public的Api麻顶,目前暫未使用過(guò)赦抖。如有童鞋知道,可以評(píng)論告知澈蚌。

private native static void nativeSetFileDescriptorEvents(long ptr, int fd, int events);
public void addOnFileDescriptorEventListener(@NonNull FileDescriptor fd,
        @OnFileDescriptorEventListener.Events int events,
        @NonNull OnFileDescriptorEventListener listener) {
   .....
    synchronized (this) {
        updateOnFileDescriptorEventListenerLocked(fd, events, listener);
    }
}

六摹芙、相關(guān)推薦

【Android源碼解析】Android中高級(jí)架構(gòu)進(jìn)階學(xué)習(xí)——百大框架源碼解析Retrofit/OkHttp/Glide/RxJava/EventBus...._嗶哩嗶哩_bilibili

本文轉(zhuǎn)自 https://juejin.cn/post/7016291737039536142,如有侵權(quán)宛瞄,請(qǐng)聯(lián)系刪除浮禾。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市份汗,隨后出現(xiàn)的幾起案子盈电,更是在濱河造成了極大的恐慌,老刑警劉巖杯活,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件匆帚,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡旁钧,警方通過(guò)查閱死者的電腦和手機(jī)吸重,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門互拾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人嚎幸,你說(shuō)我怎么就攤上這事颜矿。” “怎么了嫉晶?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵骑疆,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我替废,道長(zhǎng)箍铭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任椎镣,我火速辦了婚禮诈火,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘衣陶。我一直安慰自己柄瑰,他們只是感情好闸氮,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布剪况。 她就那樣靜靜地躺著,像睡著了一般蒲跨。 火紅的嫁衣襯著肌膚如雪译断。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天或悲,我揣著相機(jī)與錄音孙咪,去河邊找鬼。 笑死巡语,一個(gè)胖子當(dāng)著我的面吹牛翎蹈,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播男公,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼荤堪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了枢赔?” 一聲冷哼從身側(cè)響起澄阳,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎踏拜,沒(méi)想到半個(gè)月后碎赢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡速梗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年肮塞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了襟齿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡枕赵,死狀恐怖蕊唐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情烁设,我是刑警寧澤替梨,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站装黑,受9級(jí)特大地震影響副瀑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜恋谭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一糠睡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧疚颊,春花似錦狈孔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至其掂,卻和暖如春油挥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背款熬。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工深寥, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人贤牛。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓惋鹅,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親殉簸。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闰集,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容