ANR原理分析

四大組件的ANR觸發(fā)機制

Service奉芦、BroadcastReceiver和ContentProvider的ANR觸發(fā)機制都可以簡述為在AMS端的埋炸彈和拆炸彈過程。
以Service為例
AMS端啟動service時往handler post一條超時消息剧蹂,消息到了之后就觸發(fā)anr声功,期間如果service正常起來,會回調(diào)AMS宠叼,remove這條消息先巴。
埋炸彈

com/android/server/am/ActiveServices.java
void scheduleServiceTimeoutLocked(ProcessRecord proc) {
    if (proc.executingServices.size() == 0 || proc.thread == null) {
        return;
    }
    long now = SystemClock.uptimeMillis();
    Message msg = mAm.mHandler.obtainMessage(
            ActivityManagerService.SERVICE_TIMEOUT_MSG);
    msg.obj = proc;
    
    //當超時后仍沒有remove該SERVICE_TIMEOUT_MSG消息,則執(zhí)行service Timeout流程    
    mAm.mHandler.sendMessageAtTime(msg,
        proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
}

拆炸彈

ActivityThread.java
private void handleCreateService(CreateServiceData data) {
        ...
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        Service service = (Service) cl.loadClass(data.info.name).newInstance();
        ...


        try {
            //創(chuàng)建ContextImpl對象
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);
            //創(chuàng)建Application對象
            Application app = packageInfo.makeApplication(false, mInstrumentation);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManagerNative.getDefault());
            //調(diào)用服務onCreate()方法             service.onCreate();
            
            //             ActivityManagerNative.getDefault().serviceDoneExecuting(
                    data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
        } catch (Exception e) {
            ...
        }
    }
ActivityThread.java
private void handleCreateService(CreateServiceData data) {
        ...
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        Service service = (Service) cl.loadClass(data.info.name).newInstance();
        ...


        try {
            //創(chuàng)建ContextImpl對象
            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
            context.setOuterContext(service);
            //創(chuàng)建Application對象
            Application app = packageInfo.makeApplication(false, mInstrumentation);
            service.attach(context, this, data.info.name, data.token, app,
                    ActivityManagerNative.getDefault());
            //調(diào)用服務onCreate()方法             service.onCreate();
            
            //             ActivityManagerNative.getDefault().serviceDoneExecuting(
                    data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
        } catch (Exception e) {
            ...
        }
    }

引爆炸彈

// ActivityManagerService.java ::MainHandler
final class MainHandler extends Handler {
        public MainHandler(Looper looper) {
            super(looper, null, true);
        }


        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
        ......case SERVICE_TIMEOUT_MSG: {
                mServices.serviceTimeout((ProcessRecord)msg.obj);
            } break;
     }
}
void serviceTimeout(ProcessRecord proc) {
    String anrMessage = null;


    synchronized(mAm) {
        if (proc.executingServices.size() == 0 || proc.thread == null) {
            return;
        }
        final long now = SystemClock.uptimeMillis();
        final long maxTime =  now -
                (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT);
        ServiceRecord timeout = null;
        long nextTime = 0;
        for (int i=proc.executingServices.size()-1; i>=0; i--) {       // 從進程里面獲取正在運行的 service
            ServiceRecord sr = proc.executingServices.valueAt(i);
            if (sr.executingStart < maxTime) {
                timeout = sr;
                break;
            }
            if (sr.executingStart > nextTime) {
                nextTime = sr.executingStart;
            }
        }
        if (timeout != null && mAm.mLruProcesses.contains(proc)) {
            Slog.w(TAG, "Timeout executing service: " + timeout);
            StringWriter sw = new StringWriter();
            PrintWriter pw = new FastPrintWriter(sw, false, 1024);
            pw.println(timeout);
            timeout.dump(pw, " ");
            pw.close();
            mLastAnrDump = sw.toString();
            mAm.mHandler.removeCallbacks(mLastAnrDumpClearer);
            mAm.mHandler.postDelayed(mLastAnrDumpClearer, LAST_ANR_LIFETIME_DURATION_MSECS);
            anrMessage = "executing service " + timeout.shortName;
        }
    }


    if (anrMessage != null) {
        //當存在timeout的service冒冬,則執(zhí)行appNotResponding
        mAm.appNotResponding(proc, null, null, false, anrMessage);
    }
}

注意ContentProvider埋炸彈的時機是在進程啟動過程相關(因為ContentProvider是在進程啟動時開啟生命周期伸蚯,并把自己publish到AMS)。

input 觸發(fā)anr機制

input擁有不同的的超時機制窄驹,即在每次上報事件的時候檢測一把是否爆炸朝卒。
對于key事件,每次分發(fā)的時候檢測一把是否有事件沒有分發(fā)完或者沒有收到app分發(fā)完成的回調(diào);
對于觸摸事件乐埠,則檢測一把是否距離上一次分發(fā)給app的事件是否超過500ms。
上述原因滿足一條則進入anr邏輯囚企,在下一次事件分發(fā)時檢測時間是否超過5s且期間沒有收到app分發(fā)完成的回調(diào)丈咐,則正式觸發(fā)anr。

std::string InputDispatcher::checkWindowReadyForMoreInputLocked(nsecs_t currentTime,
        const sp<InputWindowHandle>& windowHandle, const EventEntry* eventEntry,
        const char* targetType) {
    ...

    // Ensure that the dispatch queues aren't too far backed up for this event.
    if (eventEntry->type == EventEntry::TYPE_KEY) {
        // If the event is a key event, then we must wait for all previous events to
        // complete before delivering it because previous events may have the
        // side-effect of transferring focus to a different window and we want to
        // ensure that the following keys are sent to the new window.
        //
        // Suppose the user touches a button in a window then immediately presses "A".
        // If the button causes a pop-up window to appear then we want to ensure that
        // the "A" key is delivered to the new pop-up window.  This is because users
        // often anticipate pending UI changes when typing on a keyboard.
        // To obtain this behavior, we must serialize key events with respect to all
        // prior input events.
        if (!connection->outboundQueue.isEmpty() || !connection->waitQueue.isEmpty()) {
            return StringPrintf("Waiting to send key event because the %s window has not "
                    "finished processing all of the input events that were previously "
                    "delivered to it.  Outbound queue length: %d.  Wait queue length: %d.",
                    targetType, connection->outboundQueue.count(), connection->waitQueue.count());
        }
    } else {
        // Touch events can always be sent to a window immediately because the user intended
        // to touch whatever was visible at the time.  Even if focus changes or a new
        // window appears moments later, the touch event was meant to be delivered to
        // whatever window happened to be on screen at the time.
        //
        // Generic motion events, such as trackball or joystick events are a little trickier.
        // Like key events, generic motion events are delivered to the focused window.
        // Unlike key events, generic motion events don't tend to transfer focus to other
        // windows and it is not important for them to be serialized.  So we prefer to deliver
        // generic motion events as soon as possible to improve efficiency and reduce lag
        // through batching.
        //
        // The one case where we pause input event delivery is when the wait queue is piling
        // up with lots of events because the application is not responding.
        // This condition ensures that ANRs are detected reliably.
        if (!connection->waitQueue.isEmpty()
                && currentTime >= connection->waitQueue.head->deliveryTime
                        + STREAM_AHEAD_EVENT_TIMEOUT) {
            return StringPrintf("Waiting to send non-key event because the %s window has not "
                    "finished processing certain input events that were delivered to it over "
                    "%0.1fms ago.  Wait queue length: %d.  Wait queue head age: %0.1fms.",
                    targetType, STREAM_AHEAD_EVENT_TIMEOUT * 0.000001f,
                    connection->waitQueue.count(),
                    (currentTime - connection->waitQueue.head->deliveryTime) * 0.000001f);
        }
    }
    return "";
}
int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime){
     // 如果失敗的原因是因為上一個任務未處理完龙宏,則不需要給超時時間重新賦值
     if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
            // 設置InputTargetWaitCause
            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
            //這里的currentTime是指執(zhí)行dispatchOnceInnerLocked方法體的起點
            mInputTargetWaitStartTime = currentTime; 
            // timeout為5s
            mInputTargetWaitTimeoutTime = currentTime + timeout;
            mInputTargetWaitTimeoutExpired = false;
            mInputTargetWaitApplicationHandle.clear();
      }
    //當超時5s棵逊,則進入ANR流程
    if (currentTime >= mInputTargetWaitTimeoutTime) {
        onANRLocked(currentTime, applicationHandle, windowHandle,
                entry->eventTime, mInputTargetWaitStartTime, reason);
        *nextWakeupTime = LONG_LONG_MIN; //強制立刻執(zhí)行輪詢來執(zhí)行ANR策略
        return INPUT_EVENT_INJECTION_PENDING;
    }
}

其他

anr觸發(fā)后通過發(fā)信號的方式觸發(fā)堆棧搜集。

    final void appNotResponding(ProcessRecord app, ActivityRecord activity,
            ActivityRecord parent, boolean aboveSystem, final String annotation) {
                ...
        if (tracesFile == null) {
            // There is no trace file, so dump (only) the alleged culprit's threads to the log
            Process.sendSignal(app.pid, Process.SIGNAL_QUIT);
        }
                ...
    }
Image.png

No Window Focus Timeout银酗,Window Timeout辆影,Window Monitor Timeout徒像。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蛙讥,隨后出現(xiàn)的幾起案子锯蛀,更是在濱河造成了極大的恐慌,老刑警劉巖次慢,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旁涤,死亡現(xiàn)場離奇詭異,居然都是意外死亡迫像,警方通過查閱死者的電腦和手機劈愚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來闻妓,“玉大人菌羽,你說我怎么就攤上這事∮衫拢” “怎么了注祖?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長犁功。 經(jīng)常有香客問我氓轰,道長,這世上最難降的妖魔是什么浸卦? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任署鸡,我火速辦了婚禮,結(jié)果婚禮上限嫌,老公的妹妹穿的比我還像新娘靴庆。我一直安慰自己,他們只是感情好怒医,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布炉抒。 她就那樣靜靜地躺著,像睡著了一般稚叹。 火紅的嫁衣襯著肌膚如雪焰薄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天扒袖,我揣著相機與錄音塞茅,去河邊找鬼。 笑死季率,一個胖子當著我的面吹牛野瘦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鞭光,長吁一口氣:“原來是場噩夢啊……” “哼吏廉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起惰许,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤席覆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后啡省,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體娜睛,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年卦睹,在試婚紗的時候發(fā)現(xiàn)自己被綠了畦戒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡结序,死狀恐怖障斋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情徐鹤,我是刑警寧澤垃环,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站返敬,受9級特大地震影響遂庄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜劲赠,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一涛目、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧凛澎,春花似錦霹肝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至最铁,卻和暖如春讯赏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背冷尉。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工待逞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人网严。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像嗤无,于是被迫代替她去往敵國和親震束。 傳聞我的和親對象是個殘疾皇子怜庸,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

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