卡頓監控 - ANR 底層機制源碼分析

ANR 可能是大家平時開發過程中,相對比較少的一類的問題,從考覈過程中就能看出來,很少有同學能夠回答完善。既然我們在開發中會遇到,那麼我們至少需要知道 Framework 層的原理,ANR 怎麼監控?ANR 怎麼分析解決?這三個維度。

ANR 大致分爲這麼兩種:四大組件啓動超時和 Input 響應超時,它們之間的底層實現原理也是有些差別的,四大組件啓動超時 ANR 是一個埋炸彈拆炸彈的過程,而 Input 響應超時 ANR 是一個排雷的過程。四大組件啓動超時的原理我相信大家都能看懂,但 Input 響應超時可能就未必了,因爲它需要一定的 C++ 基礎。

1. 四大組件啓動超時 ANR

我們在 Framework 源碼分析篇中詳細介紹過 Service 啓動流程。其中在 Service 進程 attach 到 system_server 進程的過程中會調用 realStartServiceLocked() 方法來埋下炸彈.

private final void realStartServiceLocked(ServiceRecord r, ProcessRecord app, boolean execInFg) throws RemoteException {
    ...
    // 發送delay消息 (SERVICE_TIMEOUT_MSG) 
    bumpServiceExecutingLocked(r, execInFg, "create");
    try {
        ...
        // 最終創建 Service 並執行服務的 onCreate() 方法
        app.thread.scheduleCreateService(r, r.serviceInfo,
                mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo),
                app.repProcState);
    } catch (DeadObjectException e) {
        mAm.appDiedLocked(app);
        throw e;
    } finally {
        ...
    }
}

private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) {
    ... 
    scheduleServiceTimeoutLocked(r.app);
}

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 消息,則執行 service Timeout 流程
    mAm.mHandler.sendMessageAtTime(msg,
        proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT));
}

該方法的主要工作發送 delay 消息 (SERVICE_TIMEOUT_MSG)。炸彈已埋下, 我們並不希望炸彈被引爆, 那麼就需要在炸彈爆炸之前拆除炸彈。

    private void handleCreateService(CreateServiceData data) {
        ...
        // 反射創建 Service 對象
        java.lang.ClassLoader cl = packageInfo.getClassLoader();
        Service service = (Service) cl.loadClass(data.info.name).newInstance();
        ...

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

    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) {
                //當前服務所在進程中沒有正在執行的service
                mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app);
          ...
        }
      ...
    }

如果整個啓動過程中一切正常那麼就會 removeMessages ,如果不正常則會引爆炸彈

final class MainHandler extends Handler {
    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--) {
            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,則執行appNotResponding
        mAm.appNotResponding(proc, null, null, false, anrMessage);
    }
}

2. Input 響應超時 ANR

在正式開始分析 Input 事件的 ANR 觸發原理以及觸發場景之前,大家需要先回顧一下 Input 事件的分發流程,這個也在 Framework 源碼分析篇中。

Input ANR 時間區間便是指當前這次的事件 dispatch 過程中執行 findFocusedWindowTargetsLocked() 方法到下一次執行 resetANRTimeoutsLocked() 的時間區間。看源碼有 5 個時機會 reset。都位於InputDispatcher.cpp 文件:

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    
    // Ready to start a new event.
    // If we don't already have a pending event, go grab one.
    if (! mPendingEvent) {
        ......
        // Get ready to dispatch the event.
        resetANRTimeoutsLocked();
    }
    ......
    switch (mPendingEvent->type) {
    
    case EventEntry::TYPE_MOTION: {
        ......
        done = dispatchMotionLocked(currentTime, typedEntry,
                &dropReason, nextWakeupTime);
        break;
    }

    ......
}

int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
        const EventEntry* entry, Vector<InputTarget>& inputTargets, nsecs_t* nextWakeupTime) {
    int32_t injectionResult;
    String8 reason;

    // If there is no currently focused window and no focused application
    // then drop the event.
    if (mFocusedWindowHandle == NULL) {
        if (mFocusedApplicationHandle != NULL) {
            injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                    mFocusedApplicationHandle, NULL, nextWakeupTime,
                    "Waiting because no window has focus but there is a "
                    "focused application that may eventually add a window "
                    "when it finishes starting up.");
            goto Unresponsive;
        }
        goto Failed;
    }
    ......
    return injectionResult;
}

int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
        const EventEntry* entry,
        const sp<InputApplicationHandle>& applicationHandle,
        const sp<InputWindowHandle>& windowHandle,
        nsecs_t* nextWakeupTime, const char* reason) {
    if (applicationHandle == NULL && windowHandle == NULL) {
        ...
    } else {
        if (mInputTargetWaitCause != INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY) {
            if (windowHandle != NULL) {
                timeout = windowHandle->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT);
            } else if (applicationHandle != NULL) {
                timeout = applicationHandle->getDispatchingTimeout(
                        DEFAULT_INPUT_DISPATCHING_TIMEOUT);
            } else {
                timeout = DEFAULT_INPUT_DISPATCHING_TIMEOUT;
            }

            mInputTargetWaitCause = INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY;
            mInputTargetWaitStartTime = currentTime;
            // 這個就是超時時間了
            mInputTargetWaitTimeoutTime = currentTime + timeout;
            mInputTargetWaitTimeoutExpired = false;
            mInputTargetWaitApplicationHandle.clear();

            if (windowHandle != NULL) {
                mInputTargetWaitApplicationHandle = windowHandle->inputApplicationHandle;
            }
            if (mInputTargetWaitApplicationHandle == NULL && applicationHandle != NULL) {
                mInputTargetWaitApplicationHandle = applicationHandle;
            }
        }
    }

    if (mInputTargetWaitTimeoutExpired) {
        return INPUT_EVENT_INJECTION_TIMED_OUT;
    }
}

視頻鏈接: https://pan.baidu.com/s/1bYHPvx3IOWmrk4k6DW5HHA
視頻密碼: l5df

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