為什么Looper.loop()中的死循環(huán)不會導(dǎo)致ANR

如需轉(zhuǎn)載請評論或簡信例嘱,并注明出處,未經(jīng)允許不得轉(zhuǎn)載

目錄

前言

之前寫過一篇文章Android消息機(jī)制(Handler由境、Looper棚亩、MessageQueue),這篇文章主要是從Java層入手虏杰,分析了和我們實際開發(fā)最貼近的消息機(jī)制原理讥蟆。自認(rèn)為對java層的消息機(jī)制流程已經(jīng)非常熟悉了,但是面試是被問到“Handler是如何實現(xiàn)延遲消息的纺阔?”瘸彤,“為什么Looper.loop()中的死循環(huán)不會導(dǎo)致ANR?”這些問題的時候笛钝,還是不能完全說出個所以然來质况,所以就決定再去看看native層到底做了哪些事情。如果你對java層的消息機(jī)制還不是特別熟悉的話玻靡,建議先看上一篇文章哦

消息隊列的創(chuàng)建

從上一章中结榄,我們知道MessageQueue是在Looper的構(gòu)造方法中創(chuàng)建的,但是我們沒有對MessageQueue做更深入的分析

Looper.java

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

跟進(jìn)一下MessageQueue的構(gòu)造方法囤捻,看看里面做了什么

MessageQueue.java

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

這里有一個nativeInit()方法臼朗,這是一個native層的方法

core/jni/android_os_MessageQueue.cpp

static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    ...
    //增加引用計數(shù)
    nativeMessageQueue->incStrong(env);
    //使用強制類型轉(zhuǎn)換符reinterpret_cast把NativeMessageQueue指針強轉(zhuǎn)成long類型并返回到j(luò)ava層
    return reinterpret_cast<jlong>(nativeMessageQueue);
}
NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    //從當(dāng)前線程的本地緩存(相當(dāng)于ThreadLocal)拿到looper
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        //如果looper == null,創(chuàng)建一個looper加入到線程本地緩存中
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

我們發(fā)現(xiàn)蝎土,native層中也會創(chuàng)建一個LooperMessageQueue视哑,且native層的Looper創(chuàng)建和獲取方式和java層非常的相似。native層的Looper與Java層的Looper沒有必然的關(guān)系誊涯,只是在native層重實現(xiàn)了一套類似功能的邏輯

system/core/libutils/Looper.cpp

再來看看Looper的構(gòu)造方法做了什么

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    //創(chuàng)建fd(文件操作符)      
    mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd: %s",
                        strerror(errno));

    AutoMutex _l(mLock);
    //重建管道
    rebuildEpollLocked();
}

這個eventfd實際上就是一個文件描述符挡毅,那什么是文件描述符呢?

文件描述符:簡稱fd醋拧,它就是一個int值慷嗜,又叫做句柄淀弹,在Linux中,打開或新建一個文件庆械,它會返回一個文件描述符薇溃,讀寫文件需要使用文件描述符來指定待讀寫的文件,所以文件描述符就是指代被打開的文件缭乘,所有對這個文件的IO操作都要通過文件描述符

void Looper::rebuildEpollLocked() {
    //關(guān)閉舊的管道
    if (mEpollFd >= 0) {
        close(mEpollFd);
    }
    //創(chuàng)建一個新的epoll文件描述符沐序,并注冊wake管道
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); 
  
    //設(shè)置mWakeEventFd的可讀事件(EPOLLIN)監(jiān)聽
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd;

    //將喚醒事件fd(mWakeEventFd)添加到epoll文件描述符(mEpollFd),進(jìn)行監(jiān)聽
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);   
    
    //將各種事件堕绩,如鍵盤策幼、鼠標(biāo)等事件的fd添加到epoll文件描述符(mEpollFd),進(jìn)行監(jiān)聽
    for (size_t i = 0; i < mRequests.size(); i++) {
        const Request& request = mRequests.valueAt(i);
        struct epoll_event eventItem;
        request.initEventItem(&eventItem);

        int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem);
        if (epollResult < 0) {
            ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s",
                  request.fd, strerror(errno));
        }
    }
}
}

rebuildEpollLocked()方法的主要作用其實就是通過epoll機(jī)制設(shè)置了mWakeEventFd的可讀事件和其他各種事件的監(jiān)聽

epoll機(jī)制是Linux最高效的I/O復(fù)用機(jī)制奴紧,使用一個文件描述符管理多個描述符

I/O復(fù)用機(jī)制:多人聊天室就是一種非常適合I/O多路復(fù)用的例子特姐,可能會同時有很多個用戶登錄,但是不會同時在同一個時刻發(fā)言。如果用普通I/O模型,則需要開很多的線程黍氮,大部分線程是空閑的,而且在處理多個客戶的消息的時候需要切換線程唐含,對系統(tǒng)來講也是比較重的。而使用I/O多路復(fù)用則可以重復(fù)使用一條線程,減少線程空閑和切換的情況

epoll模型主要就是三個部分

epoll_create:創(chuàng)建epoll文件描述符

epoll_ctl:添加文件描述符監(jiān)聽(如監(jiān)聽mWakeEventFd的EPOLLIN事件)

epoll_wait:阻塞監(jiān)聽add進(jìn)來的描述符沫浆,只要其中任意一個或多個描述符可用或者超時就會返回(這個我們稍后會介紹)

在進(jìn)一步分析消息機(jī)制的native層原理之前捷枯,我們可以先畫一個基本的系統(tǒng)架構(gòu)圖,捋一捋他們之間的關(guān)系

發(fā)送消息

我們應(yīng)用層調(diào)用Handlersendmessage()专执、sendEmptyMessageDelayed()淮捆、postDelayed()post()最終都會調(diào)用到MessageQueue#enqueueMessage()

MessageQueue.java

boolean enqueueMessage(Message msg, long when) {
    synchronized (this) {
        ....
        //將消息延遲時間賦值給msg的成員變量
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        //① p == null,代表消息隊列為空
        //② when == 0本股,當(dāng)我們調(diào)用sendMessageAtFrontOfQueue的時候
        //③ when < p.when攀痊,新來的消息比消息隊列中第一個消息還早
        if (p == null || when == 0 || when < p.when) {
            //插到頭結(jié)點
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
                    //如果不能插到頭結(jié)點,按時間將消息進(jìn)行排序
            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;
            prev.next = msg;
        }

        if (needWake) {
            //發(fā)送一個eventFd可讀事件
            nativeWake(mPtr);
        }
    }
    return true;
}

從這個函數(shù)中我們了解到痊末,所謂的延遲消息蚕苇,并不是延遲去發(fā)送消息,而是延遲去處理消息凿叠。延遲消息和普通消息一樣涩笤,都會在第一時間發(fā)送到消息隊列中

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();
}

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

system/core/libutils/Looper.cpp

void Looper::wake() {
    uint64_t inc = 1;
    //使用write函數(shù)往mWakeEventFd寫入字符inc,epoll就會收到可讀事件盒件,被喚醒
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    ...
}

還記得這個mWakeEventFd是什么嗎蹬碧?它就是我們一開始在native Looper的構(gòu)造方法中創(chuàng)建的用于可讀事件通知的文件操作符

總結(jié)一下發(fā)送消息就是根據(jù)消息的時間戳順序,往消息隊列中插入消息炒刁,每插入一條消息恩沽,就會往mWakeEventFd寫入字符inc,epoll就會收到可讀事件翔始,被喚醒

接下來我們看看epoll收到可讀事件后罗心,會發(fā)生什么里伯?

讀取消息

Looper.loop()內(nèi)部實際上調(diào)用的是MessageQueue#next()來讀取消息

MessageQueue.java

這里最重要的就是nativePollOnce()方法,他是一個native層方法渤闷,它會一直阻塞疾瓮,這里面就包含了epoll接收可讀事件的相關(guān)邏輯

Message next() {
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }
    ...
    //nextPollTimeoutMillis表示nativePollOnce的超時時間
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        //這是個native層方法,會一直阻塞飒箭,直到下一條可用的消息返回
        //ptr就是NativeMessageQueue的指針
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message msg = mMessages;
            if (msg != null) {
                if (now < msg.when) {
                    //如果消息觸發(fā)時間還沒到狼电,計算還需要等多久
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                  //如果觸發(fā)時間到了,取出消息
                   mMessages = msg.next;
                   msg.next = null;
                   return msg;
                }
            } else {
                //nextPollTimeoutMillis設(shè)置成-1代表沒有消息弦蹂,nativePollOnce就會無限等待
                nextPollTimeoutMillis = -1;
            }
          
          ...
    }
}

core/jni/android_os_MessageQueue.cpp

static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

system/core/libutils/Looper.cpp

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
    int result = 0;
    for (;;) {
                ...
        //當(dāng)result不等于0時肩碟,就會跳出循環(huán),返回到j(luò)ava層
        if (result != 0) {
            return result;
        }

        //處理內(nèi)部輪詢
        result = pollInner(timeoutMillis);    
    }
}

該方法內(nèi)部是一個死循環(huán)凸椿,核心在于調(diào)用了pollInner方法削祈,pollInner方法返回一個int值result,代表著本次輪詢是否成功處理了消息削饵,當(dāng)result不等于0時岩瘦,就會跳出循環(huán),返回到j(luò)ava層繼續(xù)處理java層消息

接下來我們重點看一下pollInner()做了什么窿撬,這是整個消息機(jī)制的核心

system/core/libutils/Looper.cpp

int Looper::pollInner(int timeoutMillis) {    
    //...
    //事件集合(eventItems),EPOLL_MAX_EVENTS為最大事件數(shù)量叙凡,它的值為16
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    
    //1.epoll_wait有四種情況
    //① 當(dāng)timeoutMillis == -1或timeoutMillis > 0劈伴,進(jìn)入休眠等待
    //② 如果有事件發(fā)生,且timeoutMillis == 0握爷,就從管道中讀取事件放入事件集合(eventItems)返回事件個數(shù)
    //③ 如果休眠timeoutMillis時間后還沒有被喚醒跛璧,就會返回0
    //④ 如果出錯,就會返回-1
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);  
 
        ....      
    //獲取鎖
    mLock.lock();
    //2.遍歷事件集合(eventItems)
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        //如果文件描述符為mWakeEventFd
        if (fd == mWakeEventFd) {
            //并且事件類型為EPOLLIN(可讀事件)新啼,這說明當(dāng)前線程關(guān)聯(lián)的管道的另外一端寫入了新數(shù)據(jù)
            if (epollEvents & EPOLLIN) {
                //調(diào)用awoken方法不斷的讀取管道數(shù)據(jù)
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } else {
            //如果是其他文件描述符追城,就進(jìn)行它們自己的處理邏輯
            ...
        }
    }
  
    //3、下面是處理Native的Message
    Done:;
    mNextMessageUptime = LLONG_MAX;
    //mMessageEnvelopes是一個Vector集合燥撞,它代表著native中的消息隊列
    while (mMessageEnvelopes.size() != 0) {
        nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
        //取出MessageEnvelope座柱,MessageEnvelop有收件人Hanlder和消息內(nèi)容Message
        const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
        //判斷消息的執(zhí)行時間
        if (messageEnvelope.uptime <= now) {//消息到達(dá)執(zhí)行時間
            {
                //獲取native層的Handler
                sp<MessageHandler> handler = messageEnvelope.handler;
                //獲取native層的消息
                Message message = messageEnvelope.message;
                mMessageEnvelopes.removeAt(0);
                mSendingMessage = true;
                //釋放鎖
                mLock.unlock();
                //通過MessageHandler的handleMessage方法處理native層的消息
                handler->handleMessage(message);
            }
            mLock.lock();
            mSendingMessage = false;
            //result等于POLL_CALLBACK,表示某個監(jiān)聽事件被觸發(fā)
            result = POLL_CALLBACK;
        } else {//消息還沒到執(zhí)行時間
            mNextMessageUptime = messageEnvelope.uptime;
            //跳出循環(huán)物舒,進(jìn)入下一次輪詢
            break;
        }
    }
    //釋放鎖
    mLock.unlock();
    ...
    return result;
}

總結(jié)一下色洞,pollInner主要做了三件事情:

  1. 執(zhí)行epoll_wait方法,等待事件發(fā)生或者超時(重要)

1)當(dāng)timeoutMillis == -1或timeoutMillis > 0冠胯,進(jìn)入休眠等待
2)如果有事件發(fā)生火诸,且timeoutMillis == 0,就從管道中讀取事件放入事件集合(eventItems)返回事件個數(shù)
3)如果休眠timeoutMillis時間后還沒有被喚醒荠察,就會返回0
4)如果出錯置蜀,就會返回-1

  1. 遍歷事件集合(eventItems)奈搜,檢測哪一個文件描述符發(fā)生了IO事件

遍歷事件集合中,如果是mWakeEventFd盯荤,就調(diào)用awoken方法不斷的讀取管道數(shù)據(jù)馋吗,直到清空管道,如果是其他的文件描述符發(fā)生了IO事件廷雅,讓它們自己處理相應(yīng)邏輯

  1. 處理native層的Message

只要epoll_wait方法返回后耗美,都會進(jìn)入Done標(biāo)記位的代碼段,就開始處理處理native層的Message航缀,處理完native層消息后商架,又會返回到j(luò)ava層處理java層的消息

用一個圖總結(jié)一下前面的過程

到此為止,native源碼分析的差不多了芥玉,對于native層消息機(jī)制蛇摸,我們已經(jīng)具備了不錯的理論基礎(chǔ),接下來開始回答文章一開始的問題吧

如何實現(xiàn)延遲消息

我們先來看看應(yīng)用層是如何調(diào)用的灿巧,這個大家應(yīng)該都非常清楚

SystemClock.uptimeMillis()是從開機(jī)到當(dāng)前的毫秒數(shù)赶袄,也就是說最終傳給Looper::pollInner(int timeoutMillis)的時間是以開機(jī)時間為基準(zhǔn)的一個時間戳

public final boolean sendMessageDelayed(Message msg, long delayMillis){
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

我們上面介紹epoll_wait的時候,說過這個方法的四種情況

1)當(dāng)timeoutMillis == -1或timeoutMillis > 0抠藕,進(jìn)入休眠等待
2)如果有事件發(fā)生饿肺,且timeoutMillis == 0,就從管道中讀取事件放入事件集合(eventItems)返回事件個數(shù)
3)如果休眠timeoutMillis時間后還沒有被喚醒盾似,就會返回0
4)如果出錯敬辣,就會返回-1

所以當(dāng)epoll收到一條延遲消息的時候,timeoutMillis > 0零院,所以就符合第一條溉跃,這時候就會epoll進(jìn)入休眠等待,直到休眠了timeoutMillis的時間告抄,就會被喚醒并返回撰茎。也就是說此時nativePollOnce不再阻塞,就會從消息隊列中取出消息進(jìn)行分發(fā)打洼。所以龄糊,消息延遲就是基于epoll_wait的超時時間來實現(xiàn)的

為什么Looper.loop()中的死循環(huán)不會導(dǎo)致ANR

首先,我們得需要知道ANR是怎么產(chǎn)生的拟蜻,有的人可能會說主線程執(zhí)行了耗時操作绎签、Activity的最長執(zhí)行時間是5秒、BroadcastReceiver的最長執(zhí)行時間則是10秒酝锅、Service的最長執(zhí)行時間是20秒等等這些诡必。但是我們今天要說的,其實是要更深一步

ANR是怎么產(chǎn)生的

ANR的時候,我們會看到系統(tǒng)會彈出一個對話框爸舒,我們先找到這個對話框是從哪里彈出來的

ActivityManagerService.java

final void appNotResponding(ProcessRecord app, ActivityRecord activity,
         ActivityRecord parent, boolean aboveSystem, final String annotation) {
  ...
  Message msg = Message.obtain();
  msg.what = ActivityManagerService.SHOW_NOT_RESPONDING_UI_MSG;
  msg.obj = new AppNotRespondingDialog.Data(app, activity, aboveSystem);

  mService.mUiHandler.sendMessage(msg);
}

這里的SHOW_NOT_RESPONDING_UI_MSG消息最終會執(zhí)行到AppErrors#handleShowAnrUi()蟋字,從而打開一個AppNotRespondingDialog

AppErrors.java

void handleShowAnrUi(Message msg) {
        ...
        Dialog dialogToShow = new AppNotRespondingDialog(mService, mContext, data);
        ...
        dialogToShow.show()
}

接下來我們來看看這個超時是如何觸發(fā)的,這里拿Service舉例

ActiveService.java

這是Service開始啟動的方法

private final void realStartServiceLocked(ServiceRecord r,
ProcessRecord app, boolean execInFg){
  ...
  bumpServiceExecutingLocked(r, execInFg, "create")
  ...
  app.thread.scheduleCreateService(...)
    ...
}
private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
    ...
    scheduleServiceTimeoutLocked(r.app);
    ...
}
static final int SERVICE_TIMEOUT = 20*1000;
// How long we wait for a service to finish executing.
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;
    //發(fā)送一個超時的消息扭勉,這個超時消息如果最終被接收鹊奖,將會執(zhí)行appNotResponding()
    mAm.mHandler.sendMessageDelayed(msg,
            proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
}

通過看源碼我們發(fā)現(xiàn),實際上就是在Service啟動的時候涂炎,會往消息隊列中發(fā)送一個20s的延遲消息忠聚。如果這個消息在20s之內(nèi)沒有被remove(),那就會彈出ANR的彈窗唱捣!為了不彈出這個ANR的彈窗两蟀,如果Android系統(tǒng)交給你們設(shè)計,你們會怎么做呢震缭?

我想你們應(yīng)該很容易想到吧赂毯,Andorid系統(tǒng)在Service啟動完成后就會remove消息,同樣的道理拣宰,輸入事件党涕,BroadcastReceiver啟動等都會發(fā)送一個延遲消息,之后等到成功響應(yīng)后就會remove這個延遲消息

我們通過兩個圖來對比一下啟動Service正常的情況和發(fā)生ANR的情況

正常情況
發(fā)生ANR

looper為什么不會ANR

了解了ANR發(fā)生的原理巡社,我們再來回答looper()為什么不會ANR應(yīng)該會更準(zhǔn)確一些

我認(rèn)為這里要回答兩點膛堤,一點是epoll的原理,還有一點就是ANR的觸發(fā)原理

  1. 在Linux中晌该,文件骑祟、socket、管道(pipe)等可以進(jìn)行IO操作的對象都可以稱之為流气笙,既然是IO流,那肯定會有兩端:read端和write端怯晕,我們可以創(chuàng)建兩個文件描述符wiretFd和readFd潜圃,對應(yīng)read端和write端,當(dāng)流中沒有數(shù)據(jù)時舟茶,讀線程就會阻塞(休眠)等待谭期,當(dāng)寫線程通過wiretFd往流的wiret端寫入數(shù)據(jù)后,readFd對應(yīng)的read端就會感應(yīng)到吧凉,喚醒讀線程讀取數(shù)據(jù)隧出,大概就是這樣的一個讀寫過程。讀線程進(jìn)入阻塞后阀捅,并不會消耗CPU時間胀瞪,這是epoll機(jī)制高效的原因之一

  2. Activity啟動事件時一個Message,ANR也是一個延遲Message,當(dāng)Activity啟動完成后就移除ANR的Message凄诞,這是多么自然的事情圆雁,怎么會導(dǎo)致ANR呢?

其他問題

主線程中進(jìn)行耗時操作一定會ANR嗎

通過閱讀源碼可以找到四種ANR類型(下面推薦了四篇文章帆谍,感興趣的可以跟著代碼看一看)

如果觸發(fā)了上面這些情景伪朽,就會發(fā)生ANR,反之即使在主線程做了耗時操作汛蝙,你也看不到ANR彈窗

可以做下面這個實驗

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
          try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

這樣僅僅會讓這個Activtiy延遲了10s才真正啟動,但是由于Activity沒有接收輸入時間窖剑,所以不會出現(xiàn)ANR彈窗讯私。但是如果在Service中sleep超過20秒,就會出現(xiàn)ANR彈窗稼跳,這里Service和Activity其實是有區(qū)別的

總結(jié)

本文分析了native層MessageQueue骤星、Looper的創(chuàng)建過程,也通過了解epoll的喚醒和阻塞機(jī)制翠储,解決了“Handler是如何實現(xiàn)延遲消息的绘雁?”,“為什么Looper.loop()中的死循環(huán)不會導(dǎo)致ANR援所?”這兩個問題庐舟。看源碼有時候雖然辛苦住拭,但是看完之后感覺還是有不少收獲的挪略。所以大家以后有什么非常不解的問題,不妨研究研究源碼吧

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載滔岳,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者杠娱。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谱煤,隨后出現(xiàn)的幾起案子摊求,更是在濱河造成了極大的恐慌,老刑警劉巖刘离,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件室叉,死亡現(xiàn)場離奇詭異,居然都是意外死亡硫惕,警方通過查閱死者的電腦和手機(jī)茧痕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恼除,“玉大人踪旷,你說我怎么就攤上這事。” “怎么了埃脏?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵搪锣,是天一觀的道長。 經(jīng)常有香客問我彩掐,道長构舟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任堵幽,我火速辦了婚禮狗超,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘朴下。我一直安慰自己努咐,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布殴胧。 她就那樣靜靜地躺著渗稍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪团滥。 梳的紋絲不亂的頭發(fā)上竿屹,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天,我揣著相機(jī)與錄音灸姊,去河邊找鬼拱燃。 笑死,一個胖子當(dāng)著我的面吹牛力惯,可吹牛的內(nèi)容都是我干的碗誉。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼父晶,長吁一口氣:“原來是場噩夢啊……” “哼哮缺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起甲喝,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蝴蜓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后俺猿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡格仲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年押袍,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片凯肋。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡谊惭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情圈盔,我是刑警寧澤豹芯,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站驱敲,受9級特大地震影響铁蹈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜众眨,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一握牧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧娩梨,春花似錦沿腰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至纽什,卻和暖如春措嵌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背稿湿。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工铅匹, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人饺藤。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓包斑,卻偏偏與公主長得像,于是被迫代替她去往敵國和親涕俗。 傳聞我的和親對象是個殘疾皇子罗丰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,543評論 2 349

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