Android按键超时的ANR原理小结

原创 2012年09月22日 16:40:59

1) 按键处理主要的功臣是WindowManagerService. mInputManager

@WindowManagerService.java

mInputManager = new InputManager(context, this);

 

@InputManager.java

    

 public InputManager(Context context, WindowManagerService windowManagerService) {
        this.mContext = context;
        this.mWindowManagerService = windowManagerService;
        this.mCallbacks = new Callbacks();

        Looper looper = windowManagerService.mH.getLooper();

        Slog.i(TAG, "Initializing input manager");
        nativeInit(mContext, mCallbacks, looper.getQueue());

        // Add ourself to the Watchdog monitors.
        Watchdog.getInstance().addMonitor(this);
    }


@com_android_server_InputManager.cpp


static void android_server_InputManager_nativeInit(JNIEnv* env, jclass clazz,
        jobject contextObj, jobject callbacksObj, jobject messageQueueObj) {
    if (gNativeInputManager == NULL) {
        sp<Looper> looper = android_os_MessageQueue_getLooper(env, messageQueueObj);
        gNativeInputManager = new NativeInputManager(contextObj, callbacksObj, looper);
    } else {
        LOGE("Input manager already initialized.");
        jniThrowRuntimeException(env, "Input manager already initialized.");
    }
}


NativeInputManager::NativeInputManager(jobject contextObj,
        jobject callbacksObj, const sp<Looper>& looper) :
        mLooper(looper) {
    JNIEnv* env = jniEnv();

    mContextObj = env->NewGlobalRef(contextObj);
    mCallbacksObj = env->NewGlobalRef(callbacksObj);

    {
        AutoMutex _l(mLock);
        mLocked.displayWidth = -1;
        mLocked.displayHeight = -1;
        mLocked.displayExternalWidth = -1;
        mLocked.displayExternalHeight = -1;
        mLocked.displayOrientation = DISPLAY_ORIENTATION_0;

        mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
        mLocked.pointerSpeed = 0;
        mLocked.pointerGesturesEnabled = true;
        mLocked.showTouches = false;
    }

    sp<EventHub> eventHub = new EventHub();
    mInputManager = new InputManager(eventHub, this, this);
}

@inputManager.cpp

InputManager::InputManager(
        const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    initialize();
}


2) 接下来,重点来了,inputDispatcher负责事件(keytouch)的分发,而事件处理延时的ANR也在它这里。InputReader主要负责读取底层传上来的事件,这里就不介绍了

@inputDispatcher.cpp


// If the currently focused window is still working on previous events then keep waiting.
    if (! isWindowFinishedWithPreviousInputLocked(mFocusedWindowHandle)) {
#if DEBUG_FOCUS
        LOGD("Waiting because focused window still processing previous input.");
#endif
        injectionResult = handleTargetsNotReadyLocked(currentTime, entry,
                mFocusedApplicationHandle, mFocusedWindowHandle, nextWakeupTime);
        goto Unresponsive;
    }

...

int32_t InputDispatcher::handleTargetsNotReadyLocked(nsecs_t currentTime,
        const EventEntry* entry,
        const sp<InputApplicationHandle>& applicationHandle,
        const sp<InputWindowHandle>& windowHandle,
        nsecs_t* nextWakeupTime) {

...

 if (currentTime >= mInputTargetWaitTimeoutTime) {
        onANRLocked(currentTime, applicationHandle, windowHandle,
                entry->eventTime, mInputTargetWaitStartTime);

        // Force poll loop to wake up immediately on next iteration once we get the
        // ANR response back from the policy.
        *nextWakeupTime = LONG_LONG_MIN;
        return INPUT_EVENT_INJECTION_PENDING;
    } else {
        // Force poll loop to wake up when timeout is due.
        if (mInputTargetWaitTimeoutTime < *nextWakeupTime) {
            *nextWakeupTime = mInputTargetWaitTimeoutTime;
        }
        return INPUT_EVENT_INJECTION_PENDING;
    }
}

void InputDispatcher::onANRLocked(
        nsecs_t currentTime, const sp<InputApplicationHandle>& applicationHandle,
        const sp<InputWindowHandle>& windowHandle,
        nsecs_t eventTime, nsecs_t waitStartTime) {
    LOGI("Application is not responding: %s.  "
            "%01.1fms since event, %01.1fms since wait started",
            getApplicationWindowLabelLocked(applicationHandle, windowHandle).string(),
            (currentTime - eventTime) / 1000000.0,
            (currentTime - waitStartTime) / 1000000.0);

    CommandEntry* commandEntry = postCommandLocked(
            & InputDispatcher::doNotifyANRLockedInterruptible);
    commandEntry->inputApplicationHandle = applicationHandle;
    commandEntry->inputWindowHandle = windowHandle;
}

void InputDispatcher::doNotifyANRLockedInterruptible(
        CommandEntry* commandEntry) {
    mLock.unlock();

    nsecs_t newTimeout = mPolicy->notifyANR(
            commandEntry->inputApplicationHandle, commandEntry->inputWindowHandle);

    mLock.lock();

    resumeAfterTargetsNotReadyTimeoutLocked(newTimeout,
            commandEntry->inputWindowHandle != NULL
                    ? commandEntry->inputWindowHandle->getInputChannel() : NULL);
}

这里的mPlicy是在构造的时候初始化的。追溯前面的inputManager.cpp,com_android_server_InputManager.cpp,很快

发现mPlicy其实就是NativeInputManager。

InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy) :
    mPolicy(policy),

@ com_android_server_InputManager.cpp


nsecs_t NativeInputManager::notifyANR(const sp<InputApplicationHandle>& inputApplicationHandle,
        const sp<InputWindowHandle>& inputWindowHandle) {
#if DEBUG_INPUT_DISPATCHER_POLICY
    LOGD("notifyANR");
#endif

    JNIEnv* env = jniEnv();

    jobject inputApplicationHandleObj =
            getInputApplicationHandleObjLocalRef(env, inputApplicationHandle);
    jobject inputWindowHandleObj =
            getInputWindowHandleObjLocalRef(env, inputWindowHandle);

    jlong newTimeout = env->CallLongMethod(mCallbacksObj,
                gCallbacksClassInfo.notifyANR, inputApplicationHandleObj, inputWindowHandleObj);
    if (checkAndClearExceptionFromCallback(env, "notifyANR")) {
        newTimeout = 0; // abort dispatch
    } else {
        assert(newTimeout >= 0);
    }

    env->DeleteLocalRef(inputWindowHandleObj);
    env->DeleteLocalRef(inputApplicationHandleObj);
    return newTimeout;
}

3) 仔细回忆下前面从java层到native层的inputManger和inputDispatcher的构造,不难发现mPlicy的notifyANR()最终回到inputManager.java中对应Callbacks的notifyANR()。(JNI:不仅java层访问本地方法,同样在本地方法中也可以访问java层方法)

@inputManager.java


public long notifyANR(InputApplicationHandle inputApplicationHandle,
                InputWindowHandle inputWindowHandle) {
            return mWindowManagerService.mInputMonitor.notifyANR(
                    inputApplicationHandle, inputWindowHandle);
        }

@WindowManagerService.java 


final InputMonitor mInputMonitor = new InputMonitor(this);

@InputMonitor.java


public long notifyANR(InputApplicationHandle inputApplicationHandle,
            InputWindowHandle inputWindowHandle) {
        AppWindowToken appWindowToken = null;
        if (inputWindowHandle != null) {
            synchronized (mService.mWindowMap) {
                WindowState windowState = (WindowState) inputWindowHandle.windowState;
                if (windowState != null) {
                    Slog.i(WindowManagerService.TAG, "Input event dispatching timed out sending to "
                            + windowState.mAttrs.getTitle());
                    appWindowToken = windowState.mAppToken;
                }
            }
        }
        
        if (appWindowToken == null && inputApplicationHandle != null) {
            appWindowToken = inputApplicationHandle.appWindowToken;
            if (appWindowToken != null) {
                Slog.i(WindowManagerService.TAG,
                        "Input event dispatching timed out sending to application "
                                + appWindowToken.stringName);
            }
        }

        if (appWindowToken != null && appWindowToken.appToken != null) {
            try {
                // Notify the activity manager about the timeout and let it decide whether
                // to abort dispatching or keep waiting.
                boolean abort = appWindowToken.appToken.keyDispatchingTimedOut();
                if (! abort) {
                    // The activity manager declined to abort dispatching.
                    // Wait a bit longer and timeout again later.
                    return appWindowToken.inputDispatchingTimeoutNanos;
                }
            } catch (RemoteException ex) {
            }
        }
        return 0; // abort dispatching
    }

class AppWindowToken extends WindowToken {
    // Non-null only for application tokens.
    final IApplicationToken appToken;

其实这里的appToken实质上是一个ActivityRecord的“代理”。(需了解binder)

@ActivityRecord.java


final class ActivityRecord {
    final ActivityManagerService service; // owner
    final ActivityStack stack; // owner
    final IApplicationToken.Stub appToken; // window manager token

public boolean keyDispatchingTimedOut() {
        ActivityRecord r;
        ProcessRecord anrApp = null;
        synchronized(service) {
            r = getWaitingHistoryRecordLocked();
            if (r != null && r.app != null) {
                if (r.app.debugging) {
                    return false;
                }
                
                if (service.mDidDexOpt) {
                    // Give more time since we were dexopting.
                    service.mDidDexOpt = false;
                    return false;
                }
                
                if (r.app.instrumentationClass == null) { 
                    anrApp = r.app;
                } else {
                    Bundle info = new Bundle();
                    info.putString("shortMsg", "keyDispatchingTimedOut");
                    info.putString("longMsg", "Timed out while dispatching key event");
                    service.finishInstrumentationLocked(
                            r.app, Activity.RESULT_CANCELED, info);
                }
            }
        }
        
        if (anrApp != null) {
            service.appNotResponding(anrApp, r, this,
                    "keyDispatchingTimedOut");
        }
        
        return true;
    }

接下去就是ActivityManagerService的appNotResponding,我们平常所看到的ANR对话框正出自这里。

电子词典中鼠标取词的原理

 -- ※ 来源:·BBS 水木清华站 bbs.net.tsinghua.edu.cn·[FROM: 202.117.20.17] 发信人: Dreammy (George), 信区: Program...
  • ghj1976
  • ghj1976
  • 2000-11-27 09:19:00
  • 1868

android input命令 模拟按键

我们可以在手机adb shell中,使用input来模拟按键,和之前的sm类似,input也是一个进程,在framework/base/cmds目录下。 一、Input源码 下面我们先看下inpu...
  • q1183345443
  • q1183345443
  • 2017-02-08 15:34:12
  • 553

android 防止用户粗暴点击(快速点击)引起的无响应问题。

1、问题分析: 在做应用的时候经常会遇到一些奇葩用户,或者测试员,对着一个按钮或者某个控件进行粗暴的点击,这时很有可能出现的情况就是系统来不及响应到最后的ANR。 2、解决方案: 我的解决方案就是用...
  • lisineng
  • lisineng
  • 2015-01-26 09:35:57
  • 1034

Android客户端与服务器交互中的token

学习TokenToken是什么? Token是服务端生成的一串字符串,以作客户端进行请求的一个令牌,当第一次登录后,服务器生成一个Token便将此Token返回给客户端,以后客户端只需带上这个To...
  • watermusicyes
  • watermusicyes
  • 2015-07-23 07:45:31
  • 55326

ANR超时时间的定义

ANR超时时间在ActivityManagerService.java文件中进行了定义     1.broadcast超时时间为10秒 // How long we allow a receiver...
  • ameyume
  • ameyume
  • 2011-12-03 22:10:35
  • 10471

Android ANR 起因的探究

一直以来只知道Android上ANR是yinw
  • louyong0571
  • louyong0571
  • 2014-09-25 18:46:57
  • 1180

Android Input系统的启动以及Input场景下的ANR

为了分析Input场景下ANR发生的原因,特意找了对Input系统全面介绍的一篇文章,如果系统对于Input Event超过预定时间(5s)没有响应,则会弹出ANR提示用户继续等待或者选择FC。通过下...
  • wxlinwzl
  • wxlinwzl
  • 2017-05-27 13:11:35
  • 744

android ANR源码分析 --- 之三

4, inputDispatching Timeout 当input事件处理得慢就会触发ANR. ANR时间区别便是指当前这次的事件dispatch过程中执行findFocusedWindowTa...
  • u012439416
  • u012439416
  • 2017-10-31 20:18:10
  • 297

appNotResponding的代码处理流程

1. 可能导致 anr的情景:activity, service, provice, broadcast都可能产生anr 具体产生的策略这里没有关注,这里只关注发生 anr后的代码流程,也就是关注a...
  • u011279649
  • u011279649
  • 2016-12-28 15:32:29
  • 905

appNotResponding()分析

ActivityManagerService.appNotResponding()在程序无响应、ANR时被调用,分析这个函数有有助于更好地理解日志中打印出的信息。 final void app...
  • guoqifa29
  • guoqifa29
  • 2015-01-16 20:30:03
  • 4328
收藏助手
不良信息举报
您举报文章:Android按键超时的ANR原理小结
举报原因:
原因补充:

(最多只允许输入30个字)