Input ANR原理

Post on Jan 05, 2023 by Wei Lin

Input dispatching Timedout

Base on: Android 12

Input模块中的InputReader利用EventHub获取数据后生成EventEntry事件,加入到InputDispatcher的消息队列,然后由InputDispatcher负责分发。

InputDispatcher-初始化与开启线程

首先进行InputDispatcher的初始化,然后InputDispatcher::start()开启InputDispatcher线程。

InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy)      : mPolicy(policy),
        mPendingEvent(nullptr),
        mLastDropReason(DropReason::NOT_DROPPED),
        mIdGenerator(IdGenerator::Source::INPUT_DISPATCHER),
        mAppSwitchSawKeyDown(false),
        mAppSwitchDueTime(LONG_LONG_MAX),
        mNextUnblockedEvent(nullptr),
        mDispatchEnabled(false),
        mDispatchFrozen(false),
        mInputFilterEnabled(false),
        // mInTouchMode will be initialized by the WindowManager to the default device config.
        // To avoid leaking stack in case that call never comes, and for tests,
        // initialize it here anyways.
        mInTouchMode(true),
        mFocusedDisplayId(ADISPLAY_ID_DEFAULT) {
    //创建Looper对象
    mLooper = new Looper(false);
    mReporter = createInputReporter();

    mKeyRepeatState.lastKeyEntry = nullptr;
    //获取分发超时参数,保存在mConfig
    policy->getDispatcherConfiguration(&mConfig);
}

​ policy是一个NativeInputManager类的智能指针,mConfig是一个InputDispatcherConfiguration类的指针,包含keyRepeatTimeout(默认500ms)和keyRepeatDelay(默认50ms)两个超时参数。


然后开启InputDispatcher线程:

status_t InputDispatcher::start() {    if (mThread) {
        return ALREADY_EXISTS;
    }
    mThread = std::make_unique<InputThread>(
            "InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); });
    return OK;
}

进入dispatchOnce()代码中。

dispatchOnce()-消息循环

在dispatchOnce()中进行消息循环。通过dispatchOnceInnerLocked()分发事件,每一次消息队列分发完毕后,再用processAnrsLocked()检查是否发生ANR。

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { // acquire lock
        std::scoped_lock _l(mLock);
        //唤醒所有等待线程
        mDispatcherIsAlive.notify_all();

        // haveCommandsLocked()判断消息队列mCommandQueue是否为空
        // 当mCommandQueue不为空时,则派发输入事件
        if (!haveCommandsLocked()) {
            dispatchOnceInnerLocked(&nextWakeupTime);
        }

        // 通过循环处理mCommandQueue中的所有事件.
        // 若所有事件执行完毕,将nextWakeupTime设置得极小(即当有新事件到来时立即唤醒线程).
        if (runCommandsLockedInterruptible()) {
            nextWakeupTime = LONG_LONG_MIN;
        }

        // 检查是否发生了ANR,并返回下次执行的时间
        const nsecs_t nextAnrCheck = processAnrsLocked();
        //选择一个最小的时间,作为下次执行时间
        nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);

        // 若满足条件,进入一个无限长的睡眠,因为没有命令或挂起或排队的事件
        if (nextWakeupTime == LONG_LONG_MAX) {
            mDispatcherEnteredIdle.notify_all();
        }
    } // release lock

    // Wait for callback or timeout or wake.  (make sure we round up, not down)
    nsecs_t currentTime = now();
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    mLooper->pollOnce(timeoutMillis);
}

线程执行Looper->pollOnce,进入epoll_wait等待状态,当发生以下任一情况则退出等待状态:

callback:通过回调方法来唤醒;

timeout:到达nextWakeupTime时间,超时唤醒;

wake: 主动调用Looper的wake()方法;

dispatchOnceInnerLocked()-事件类型分发

根据事件的类型进行分发,又不同的函数处理不同的事件。按键输入使用 dispatchKeyLocked() 进行处理,触屏输入使用dispatchMotionLocked()进行处理。

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    //......
    switch (mPendingEvent->type) {
        case EventEntry::Type::CONFIGURATION_CHANGED: {  //配置改变
            //......
            break;
        }
        case EventEntry::Type::DEVICE_RESET: {  //设备重置
            //......
            break;
        }
        case EventEntry::Type::FOCUS: {  
            //焦点变化
            break;
        }
        case EventEntry::Type::KEY: {  //按键输入
            KeyEntry* typedEntry = static_cast<KeyEntry*>(mPendingEvent);
            //......
            done = dispatchKeyLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
            break;
        }
        case EventEntry::Type::MOTION: {  //触屏输入
            MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
            //......
            done = dispatchMotionLocked(currentTime, typedEntry, &dropReason, nextWakeupTime);
            break;
        }
}
//......
}

processAnrsLocked()-检查ANR

在processAnrsLocked()中,执行ANR检查,即判断是否出现了ANR,以下两种情况会触发ANR:

  • 应用有焦点,但窗口无焦点(即系统在等待出现焦点窗口),且超过ANR等待事件,则进入ANR处理流程;
  • 检查第一个connection的状态,若其ANR触发时间到了,则表示发生了ANR。

返回值表示下次检查ANR的时间。

nsecs_t InputDispatcher::processAnrsLocked() {
    const nsecs_t currentTime = now();
    nsecs_t nextAnrCheck = LONG_LONG_MAX;
    // 窗口无焦点,但应用有焦点
    if (mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr) {
        if (currentTime >= *mNoFocusedWindowTimeoutTime) {  //如果等待时间超过ANR时间
            onAnrLocked(mAwaitedFocusedApplication);  //执行ANR代码
            mAwaitedFocusedApplication.clear();
            return LONG_LONG_MIN;
        } else {   // 未超时,继续等待焦点窗口出现
            const nsecs_t millisRemaining = ns2ms(*mNoFocusedWindowTimeoutTime - currentTime);
            ALOGW("Still no focused window. Will drop the event in %" PRId64 "ms", millisRemaining);
            nextAnrCheck = *mNoFocusedWindowTimeoutTime;
        }
    }

    // 检查是否有connection中的ANR触发时间到了
    nextAnrCheck = std::min(nextAnrCheck, mAnrTracker.firstTimeout());
    if (currentTime < nextAnrCheck) {  //还没到检查时间,即还没发生ANR
        return nextAnrCheck;
    }

    // 执行到此处,已经发生了ANR
    sp<Connection> connection = getConnectionLocked(mAnrTracker.firstToken());
    if (connection == nullptr) {
        ALOGE("Could not find connection for entry %" PRId64, mAnrTracker.firstTimeout());
        return nextAnrCheck;
    }
    connection->responsive = false;  //置connection状态为未响应
    // Stop waking up for this unresponsive connection
    mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
    onAnrLocked(connection);  //处理ANR
    return LONG_LONG_MIN;
}

onANRLocked()-发生ANR

onANRLocked()有两个重载函数,分别对应上述的两种ANR情况,一个是窗口无法得到焦点,另一个是connection状态异常。

// connection的ANR触发时间到产生的ANR
void InputDispatcher::onAnrLocked(const sp<Connection>& connection) {
    if (connection->waitQueue.empty()) {
        ALOGI("Not raising ANR because the connection %s has recovered",
              connection->inputChannel->getName().c_str());
        return;
    }
    DispatchEntry* oldestEntry = *connection->waitQueue.begin();
    const nsecs_t currentWait = now() - oldestEntry->deliveryTime;
    std::string reason =
            android::base::StringPrintf("%s is not responding. Waited %" PRId64 "ms for %s",
                                        connection->inputChannel->getName().c_str(),
                                        ns2ms(currentWait),
                                        oldestEntry->eventEntry->getDescription().c_str());

   updateLastAnrStateLocked(getWindowHandleLocked(connection->inputChannel->getConnectionToken()),reason);

    std::unique_ptr<CommandEntry> commandEntry =
            std::make_unique<CommandEntry>(&InputDispatcher::doNotifyAnrLockedInterruptible);
    commandEntry->inputApplicationHandle = nullptr;
    commandEntry->inputChannel = connection->inputChannel;
    commandEntry->reason = std::move(reason);
    postCommandLocked(std::move(commandEntry));
}

// 事件下发,但系统在等待出现焦点窗口
void InputDispatcher::onAnrLocked(const sp<InputApplicationHandle>& application) {
    std::string reason = android::base::StringPrintf("%s does not have a focused window",application->getName().c_str());

    updateLastAnrStateLocked(application, reason);

    std::unique_ptr<CommandEntry> commandEntry =
            std::make_unique<CommandEntry>(&InputDispatcher::doNotifyAnrLockedInterruptible);
    commandEntry->inputApplicationHandle = application;
    commandEntry->inputChannel = nullptr;
    commandEntry->reason = std::move(reason);
    postCommandLocked(std::move(commandEntry));
}

调用到Framework

调用 onANRLocked() 来捕获 ANR 的相关信息。之后调用到Framework层,大致的调用流程为:

InputDispatcher::onANRLocked() -> 
InputDispatcher::doNotifyANRLockedInterruptible() -> 
InputDispatcher::notifyANR() ->
InputManagerService.notifyANR() -> 
InputMonitor.notifyANR() -> 
ActivityManagerService.inputDispatchingTimedOut() -> 
AnrHelper.appNotResponding()

最后和其它类型的ANR一样,调用到了AnrHelper.appNotResponding()。

总结

和其它超时不同的是,输入事件的超时是在C++代码InputDispatcher.cpp中进行判断的,在C++中判定超时后通知AMS,AMS再调用appNotResponding处理ANR。

发生Input dispatching Timedout的情况较为复杂,通常是下发到窗口时,窗口出现问题,窗口在Activity执行onResume后才会开始绘制,所以常见的问题有:

  • Activity的生命周期耗时:又有很多情况,如生命周期方法中有太多操作、IO耗时、等锁、binder服务端问题等。
  • window绘制问题:mDrawState=NO_SURFACE(未绘制)或mDrawState=DRAW_PENDING(正在绘制)。
  • 其它。

常见ANR问题

APP的生命周期耗时

APP的onCreate、onStart、onRestart、onStop等方法耗时,通常是APP的问题。

日志示例:

11-03 06:08:33.653 22199 22199 I wm_on_create_called: [0,com.android.settings.MainSettings,performCreate,40582]
说明:onCreate耗时约40s。

11-03 23:15:54.100  3589  3589 I wm_on_start_called: [0,com.android.settings.MainSettings,handleStartActivity,22224]
说明:onStart耗时约22s。

wm_*到wm_*调用延时

看打印wm_*日志的所在线程是否有等锁。

# AppReviewsActivity执行onDestroy过程中,wm_destroy_activity打印慢
05-22 03:51:29.734 1813 6406 I wm_add_to_stopping: [0,133348335,com.xiaomi.market/.h52native.detail.AppReviewsActivity,completeFinishing]
05-22 03:51:38.767 1813 1916 I wm_destroy_activity: [0,133348335,3596,com.xiaomi.market/.h52native.detail.AppReviewsActivity,finish-imm:idle]

# 可以在日志中查看wm_destroy_activity日志所在线程1916等哪个锁
05-22 03:51:36.662 1813 1916 I dvm_lock_sample: [system_server,1,android.display,6920,WindowManagerService.java,5371,void com.android.server.wm.WindowManagerService.reportFocusChanged(android.os.IBinder, android.os.IBinder),ActivityTaskManagerService.java,7055,boolean com.android.server.wm.ActivityTaskManagerService.isInSplitScreenWindowingMode(),6406]

wm_*_*到wm_on_*_called调用延时

(1) 是Slow Binder或等锁(dvm_lock_sample日志)导致的延时。

(2) APP的生命周期耗时

wm_on_*_called日志的打印要APP的生命周期方法onXXXX完成后才打印,若APP的生命周期耗时,则打印时间也有延迟。

APP无wm_on_xxx调用

日志中只有system_server端wm_xxx_activity的调用信息,但没有wm_on_xxx_called,APP端没有调用Activity的生命周期方法。

(1) Binder问题

可能是binder方面的问题,通知Activity启动的消息未发送到应用。如APP端Binder线程池已满。


(2) 等锁导致

system端代码等锁时间太长,以至于无法调用到APP端。

生命周期正常,窗口绘制问题

Activity的生命周期正常,但window绘制较慢,导致焦点无法进入。

若一段时间后焦点仍无法进入窗口,则会发生ANR(Input dispatching timed out)。

设置的时间到达时窗口仍没有绘制完成。通常为WMS的问题。若有dumpsys log,可以通过关键字”WindowStateAnimator{.*Activity名}”查看,如:

WindowStateAnimator{19ca2f0 com.android.email/com.kingsoft.email2.ui.MailActivityEmail}:
  mDrawState=NO_SURFACE       mLastHidden=true
  mEnterAnimationPending=false      mSystemDecorRect=[0,0][919,1653] mLastClipRect=[0,0][919,1653]
  mGlobalScale=0.70185184 mDsDx=0.70185184 mDtDx=0.0 mDtDy=0.0 mDsDy=0.70185184

mDrawState=NO_SURFACE表示还没有绘制。

QueuedWork.processPendingWork()

使用SharedPreferences修改配置文件后,调用apply()提交。apply()首先写入内存,然后将写入到磁盘(落盘)的任务加入到队列中,通过异步线程做落盘的操作。

QueuedWork工作在queued-work-looper线程中,主要是用来执行和跟踪一些进程全局的工作,就是由它调度的SP相关的异步任务,通过调用调用QueuedWork.queue()将SP文件变更操作的任务发送到QueuedWork类中执行。

而APP会在handlePauseActivity()和handleStopActivity()方法中(在onPause和onStop执行之后)调用QueuedWork.waitToFinish(),保证这些SP任务都已经完成,如果写入比较慢,主线程就会卡顿,甚至ANR。常见于以下情况:

  • 将较大的String写入到SP中;
  • 系统资源较为紧张,CPU或内存占用大;
  • 在monkey压测场景中,IO比较频繁。
SharedPreferences.Editor editor = getSharedPreferences("fileName",MODEPRIVATE).edit();
editor.putString("Key", "Value");
editor.apply();

发生ANR的堆栈:

......
at android.app.SharedPreferencesImpl.writeToFile(SharedPreferencesImpl.java:738)
at android.app.SharedPreferencesImpl.access$900(SharedPreferencesImpl.java:59)
at android.app.SharedPreferencesImpl$2.run(SharedPreferencesImpl.java:672)
locked <0x0ebcf9c3> (a java.lang.Object)
at android.app.QueuedWork.processPendingWork(QueuedWork.java:299)
locked <0x0a61f240> (a java.lang.Object)
at android.app.QueuedWork.waitToFinish(QueuedWork.java:190)
at android.app.ActivityThread.handleStopActivity(ActivityThread.java:5014)
at android.app.servertransaction.StopActivityItem.execute(StopActivityItem.java:40)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2116)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:236)
at android.app.ActivityThread.main(ActivityThread.java:7904)
at java.lang.reflect.Method.invoke(Native method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:656)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:967)

无Focus entering

Focus request后没有Focus enter,导致ANR。日志中显示原因为:Input dispatching timed out (Application does not have a focused window)。