在IMS初始化流程分析开头我们提到过几个问题:
- input的分发流程是怎样的?
- input事件是通过什么方式把事件传递到应用进程?
- ims里面是怎么去找到当前的焦点app以及window?
- ims里面ANR超时5s是在哪里定义的?
- ims里面ANR的发生是怎么去处理的?
- WindowInputEventReceiver中的onInputEvent方法,是运行在主线程还是子线程?
ims初始化流程、ims启动流程、input事件分发流程这几个流程我们也分析完了,现在我们就针对在ims初始化流程中提到的这几个问题来总结一下。
1. input的分发流程是怎样的?
直接看input事件分发流程时序图也可以,调用流程以及一些总结很清晰。
2. input事件是通过什么方式把事件传递到应用进程?
本质是通过socket通信,应用的Looper
通过epoll方式监听sockcet的fd, 当应用的socket变为可读时(例如,inputDispatcher向socket中写入数据),Looper
将回调handleEvent
。 此时,应用应读取已进入套接字的事件。 只要socket中有未读事件,函数 handleEvent 就会继续触发。
大致流程:
创建一对InputChannel,并且会开启一对相互连接的socket作为事件传递的媒介。
server端的InputChannel会注册到InputDispatcher中去以完成事件的分发,并且会将其fd添加到looper中,而client端的InputChannel会在InputEventReceiver初始化时也会将其fd添加到looper中。
在InputDispatcher线程负责处理分发事件的时候,最后调用到InputChannel的sendMessage函数,然后调用send函数将msg循环写入socket,而应用端在调用ViewRootImpl.setView方法的时候会将InputChannel传入InputEventReceiver,创建app端监听,即WindowInputEventReceiver 作为事件的接收端,在NativeInputEventReceiver初始化时,会将inputChannel的文件描述符fd添加到looper中去,并且添加了looper callback为NativeInputEventReceiver实例自身,所以,当server端写入事件消息时,就会触发callback,于是便调用到NativeInputEventReceiver的handleEvent方法,最终客户端进程取出消息还是通过InputChannel.receiveMessage读取消息 。
3. ims里面是怎么去找到当前的焦点app以及window?
首先我们需要理解为什么需要去找到当前的焦点app以及焦点window?
那当然是因为当前有输入事件,输入事件需要传递给当前获得焦点的App的窗口啦。
焦点app:
设置焦点app类图:
焦点window:
焦点窗口更新以及inputwindow更新时序图:
更新inputwindow信息在Android R之后的版本是有变化的,之前的方法是通过wms再由InputMonitor通过JNI调用的方式间接调用InputDispatcher::setInputWindows,以实现同步可见窗口信息给InputDispatcher。Android R上交由SurfaceFlinger进程来处理这个事情,SurfaceFlinger会在每一帧合成任务SurfaceFlinger::OnMessageInvalidate中搜集每个可见窗口Layer的信息通过Binder调用InputDispatcher::setInputWindows同步可见窗口信息给InputDispatcher。关于这样做的好处有两个:
- 触控事件的分发其实只关心所有可见窗口的信息,而SurfaceFlinger其实最清楚所有可见窗口的Layer的触控区域、透明度、图层Z_Order顺序等信息。
- 这样WindowManagerService只需要统一负责将窗口信息同步给SurfaceFlinger即可,不再同时维护将可见窗口信息同步给InputDispatcher的逻辑,整体职责更加清晰。
寻找焦点window重要方法分析:
首先是去更新焦点窗口,这部分内容大致可以看看上面的时序图,主要内容就是遍历DisplayContent,并在每个DisplayContent中进行更新,然后将更新的结果返回给DisplayContent#mCurrentFocus变量,该变量表示全局的焦点窗口。同时更新mTopFocusedDisplayId变量,表示当前焦点屏(即焦点窗口所在的屏)。
findFocusedWindow方法会遍历所有WindowState,然后将寻找到的WindowState赋值给mTmpWindow,并返回给WMS。
mFindFocusedWindow函数接口将依次根据如下条件获得焦点窗口:
- 如果WindowState不能接收Input事件,则不能作为焦点窗口。
- 如果没有前台Activity,则当前WindowState作为焦点窗口返回。
- 如果前台Activity是不可获焦状态,则当前WindowState作为焦点窗口返回。
- 如果当前WindowState由ActivityRecord管理,且该WindowState不是Staring Window类型,那么当前台Activity在当前WindowState所属Activity之上时,不存在焦点窗口。
- 如果以上条件都不满足,则当前WindowState作为焦点窗口返回。
/frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
WindowState findFocusedWindow() {
mTmpWindow = null;
// 遍历WindowState
forAllWindows(mFindFocusedWindow, true /* traverseTopToBottom */);
if (mTmpWindow == null) {
return null;
}
return mTmpWindow;
}
private final ToBooleanFunction<WindowState> mFindFocusedWindow = w -> {
// 当前处于前台的ActivityRecord
final ActivityRecord focusedApp = mFocusedApp;
// 如果窗口无法接收key事件,则不能作为焦点窗口,返回false
if (!w.canReceiveKeys()) {
return false;
}
final ActivityRecord activity = w.mActivityRecord;
// 如果前台没有Activity,则此次WindowState将作为焦点窗口返回
if (focusedApp == null) {
mTmpWindow = w;
return true;
}
// 如果前台Activity是不可获焦的,则此次WindowState将作为焦点窗口返回
if (!focusedApp.windowsAreFocusable()) {
mTmpWindow = w;
return true;
}
// 如果当前WindowState由ActivityRecord管理,且非StartingWindow,则当
if (activity != null && w.mAttrs.type != TYPE_APPLICATION_STARTING) {
if (focusedApp.compareTo(activity) > 0) {
mTmpWindow = null;
return true;
}
}
// 不满足以上条件,则此次WindowState将作为焦点窗口返回
mTmpWindow = w;
return true;
};
inputWindow更新:
updateInputWindows方法,这个方法属于InputMonitor内部类UpdateInputForAllWindowsConsumer中的方法,在该方法中,首先会确认是否存在几类特殊的InputConsumer。InputConsumer用于读取事件,每个窗口对应的客户端都会通过InputConsumer来读取和消费事件,一般情况下,ViewRootImpl在添加窗口过程中,会在注册InputEventReceiver时自动创建InputConsumer对象。此处的四类特殊InputConsumer则是对一些系统UI显式地进行了创建,然后,将发起所有WindowState的遍历,mUpdateInputForAllWindowsConsumer本身是一个Consumer接口对象,因此会回调accept方法对每个WindowState进行处理:
/frameworks/base/services/core/java/com/android/server/wm/InputMonitor.java
private void updateInputWindows(boolean inDrag) {
// 显式创建的特殊InputConsumer对象
// 用于处理Nav相关input事件
mNavInputConsumer = getInputConsumer(INPUT_CONSUMER_NAVIGATION);
// 用于处理Pip相关input事件
mPipInputConsumer = getInputConsumer(INPUT_CONSUMER_PIP);
// 用于处理壁纸相关input事件
mWallpaperInputConsumer = getInputConsumer(INPUT_CONSUMER_WALLPAPER);
// 用于处理最近任务相关input事件
mRecentsAnimationInputConsumer = getInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
....
mWallpaperController = mDisplayContent.mWallpaperController;
// 重置mInputTransaction
resetInputConsumers(mInputTransaction);
// 遍历
mDisplayContent.forAllWindows(this,
true /* traverseTopToBottom */);
// 将mInputTransaction合并到mPendingTransaction上进行提交
if (!mUpdateInputWindowsImmediately) {
mDisplayContent.getPendingTransaction().merge(mInputTransaction);
mDisplayContent.scheduleAnimation();
}
}
@Override
public void accept(WindowState w) {
// 获取WindowState的InputChannel对象
final InputChannel inputChannel = w.mInputChannel;
// 获取WindowState的InputWindowHandle对象
final InputWindowHandle inputWindowHandle = w.mInputWindowHandle;
// 最近任务是否存在
final RecentsAnimationController recentsAnimationController =
mService.getRecentsAnimationController();
final boolean shouldApplyRecentsInputConsumer = recentsAnimationController != null
&& recentsAnimationController.shouldApplyInputConsumer(w.mActivityRecord);
final int type = w.mAttrs.type;
......
final int flags = w.mAttrs.flags;
final int privateFlags = w.mAttrs.privateFlags;
// 是否是焦点窗口
final boolean hasFocus = w.isFocused();
// mRecentsAnimationInputConsumer处理最近任务相关input事件
if (mAddRecentsAnimationInputConsumerHandle && shouldApplyRecentsInputConsumer) {
if (recentsAnimationController.updateInputConsumerForApp(
mRecentsAnimationInputConsumer.mWindowHandle, hasFocus)) {
mRecentsAnimationInputConsumer.show(mInputTransaction, w);
mAddRecentsAnimationInputConsumerHandle = false;
}
}
// 处理处于PIP模式时的input事件
if (w.inPinnedWindowingMode()) {
}
// mNavInputConsumer处理Nav相关input事件
if (mAddNavInputConsumerHandle) {
mNavInputConsumer.show(mInputTransaction, w);
mAddNavInputConsumerHandle = false;
}
// mWallpaperInputConsumer处理壁纸input事件
if (mAddWallpaperInputConsumerHandle) {
if (w.mAttrs.type == TYPE_WALLPAPER && w.isVisibleLw()) {
mWallpaperInputConsumer.show(mInputTransaction, w);
mAddWallpaperInputConsumerHandle = false;
}
}
// 是否壁纸不接收input事件
if ((privateFlags & PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS) != 0) {
mDisableWallpaperTouchEvents = true;
}
final boolean hasWallpaper = mWallpaperController.isWallpaperTarget(w)
&& !mService.mPolicy.isKeyguardShowing()
&& !mDisableWallpaperTouchEvents;
// 是否处于拖拽过程中
if (mInDrag && isVisible && w.getDisplayContent().isDefaultDisplay) {
mService.mDragDropController.sendDragStartedIfNeededLocked(w);
}
// 填充InputWindowHandle,填充完毕InputWindowHandle后,会将InputWindowHandle设
// 置给mInputTransaction对象
populateInputWindowHandle(
inputWindowHandle, w, flags, type, isVisible, hasFocus, hasWallpaper);
// 提交inputWindowHandle
if (w.mWinAnimator.hasSurface()) {
mInputTransaction.setInputWindowInfo(
w.mWinAnimator.mSurfaceController.getClientViewRootSurface(),
inputWindowHandle);
}
}
updateInputWindows方法与accept方法作用:
- 首先会对几类特殊InputConsumer进行单独处理。
- 然后填充InputWindowHandle对象。
- 最后将InputWindowHandle对象设置给Transaction对象,并在事物提交后,由SurfaceFlinger设置给InputDispatcher中。
InputWindowHandle代表了WindowState,传给了InputDispatcher中用于派发事件,当InputDispatcher中进行事件的派发时,将以InputWindowHandle确定需要派发给哪些窗口。
InputDispatcher寻找焦点app以及焦点window
关于焦点app以及焦点window的寻找在InputDispatcher中,在findFocusedWindowTargetsLocked函数中有此体现:
/frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
int32_t InputDispatcher::findFocusedWindowTargetsLocked(nsecs_t currentTime,
const EventEntry& entry,
std::vector<InputTarget>& inputTargets,
nsecs_t* nextWakeupTime) {
std::string reason;
int32_t displayId = getTargetDisplayId(entry);
sp<InputWindowHandle> focusedWindowHandle =
getValueByKey(mFocusedWindowHandlesByDisplay, displayId);
sp<InputApplicationHandle> focusedApplicationHandle =
getValueByKey(mFocusedApplicationHandlesByDisplay, displayId);
// If there is no currently focused window and no focused application
// then drop the event.
// 如果没有当前聚焦的窗口,也没有聚焦的app,那么删除该事件。
if (focusedWindowHandle == nullptr && focusedApplicationHandle == nullptr) {
ALOGI("Dropping %s event because there is no focused window or focused application in "
"display %" PRId32 ".",
EventEntry::typeToString(entry.type), displayId);
return INPUT_EVENT_INJECTION_FAILED;
}
............................
// Success! Output targets.
// 找到正确的InputChannel,加入到InputTargets中
addWindowTargetLocked(focusedWindowHandle,
InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS,
BitSet32(0), inputTargets);
// Done.
return INPUT_EVENT_INJECTION_SUCCEEDED;
}
现在我们来看看在触摸事件分发的条件下焦点window是怎么去寻找的:
sp<InputWindowHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x,
int32_t y, TouchState* touchState,
bool addOutsideTargets,
bool addPortalWindows,
bool ignoreDragWindow) {
if ((addPortalWindows || addOutsideTargets) && touchState == nullptr) {
LOG_ALWAYS_FATAL(
"Must provide a valid touch state if adding portal windows or outside targets");
}
// Traverse windows from front to back to find touched window.
// 从前到后,遍历窗口
const std::vector<sp<InputWindowHandle>>& windowHandles = getWindowHandlesLocked(displayId);
.................省略大量代码................
return nullptr;
}
std::vector<sp<InputWindowHandle>> InputDispatcher::getWindowHandlesLocked(
int32_t displayId) const {
// 从集合mWindowHandlesByDisplay中根据传入的displayId取出对应的可见窗口集合
return getValueByKey(mWindowHandlesByDisplay, displayId);
}
void InputDispatcher::updateWindowHandlesForDisplayLocked(
const std::vector<sp<InputWindowHandle>>& inputWindowHandles, int32_t displayId) {
...
// Insert or replace
// 给mWindowHandlesByDisplay集合赋值的地方
mWindowHandlesByDisplay[displayId] = newHandles;
}
void InputDispatcher::setInputWindowsLocked(
const std::vector<sp<InputWindowHandle>>& inputWindowHandles, int32_t displayId) {
...
updateWindowHandlesForDisplayLocked(inputWindowHandles, displayId);
...
}
void InputDispatcher::setInputWindows(
const std::unordered_map<int32_t, std::vector<sp<InputWindowHandle>>>& handlesPerDisplay) {
{ // acquire lock
std::scoped_lock _l(mLock);
for (auto const& i : handlesPerDisplay) {
setInputWindowsLocked(i.second, i.first);
}
}
// Wake up poll loop since it may need to make new input dispatching choices.
mLooper->wake();
}
上面的流程就非常清楚了,从外部调用setInputWindows再到找到焦点window,从而进行相应的input事件分发。
4. ims里面ANR超时5s是在哪里定义的?
/frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
在InputDispatcher::startDispatchCycleLocked函数中通过getDispatchingTimeoutLocked 获取到超时时间。
constexpr std::chrono::nanoseconds DEFAULT_INPUT_DISPATCHING_TIMEOUT = 5s;
nsecs_t InputDispatcher::getDispatchingTimeoutLocked(const sp<IBinder>& token) {
sp<InputWindowHandle> window = getWindowHandleLocked(token);
// focus window不为空
if (window != nullptr) {
return window->getDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT).count();
}
// input事件超时默认值5s
return DEFAULT_INPUT_DISPATCHING_TIMEOUT.count();
}
void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
const sp<Connection>& connection) {
...............
while (connection->status == Connection::Status::NORMAL && !connection->outboundQueue.empty()) {
..........
const nsecs_t timeout =
getDispatchingTimeoutLocked(connection->inputChannel->getConnectionToken());
// 设置超时时间
dispatchEntry->timeoutTime = currentTime + timeout.count();
...................
}
}
}
设置的超时时间是当前时间+超时时间,所以我们获取的这个timeout时间就是currentTime + timeout.count()
5. ims里面ANR的发生是怎么去处理的?
首先我们需要知道3个queue,input事件存储在这3个queue里面:
InputDispatcher的mInboundQueue:存储的是从InputReader 送来的输入事件。
Connection的outboundQueue:该队列是存储即将要发送给应用的输入事件。
Connection的waitQueue:队列存储的是已经发给应用的事件,但是应用还未处理完成的。
知晓了这3个队列之后我们来看看正常的事件流程:
先将事件加入到outBoundQueue,然后publishXxxEvent到App Main线程
然后立马将A事件从outboundQueue中剥离,加入到waitQueue中
App线程会调用nativeFinishInputEvent,进一步调用 sendFinishedSignal 向 SystemServer发送哪个按键事件已经被finish, 最后从waitQueue中移出掉事件
接下来我们看看异常情况(这里结合两种情况来说,一种是view设置了onClick回调;一种是view设置了onTouch回调):
如果app线程在处理A事件(ACTION_DOWN), 没有返回:
这时候来了一个B事件(ACTION_UP):
/frameworks/base/core/java/android/view/View.java
public boolean onTouchEvent(MotionEvent event) {
....
switch (action) {
case MotionEvent.ACTION_UP:
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
// 通过post延迟 触发点击事件
if (!post(mPerformClick)) {
performClickInternal();
}
.....
}
这时候App侧通知onClick的时候经历过一次handler转发,但是事件已经处理完成,此时waitQueue就是空的,所以这一次不会发生anr。
但是又来了一次C事件,这时候就会发生anr了,因为app线程正被阻塞住,waitQueue 这个时候会存放两个事件action_down和action_up,等到第二次input事件超时后,通过looper再次调用到dispatchOnce()方法,再检测是否发生anr,waitQueue的头节点是大于5s的,通过mAnrTracker取出超时时间。
nsecs_t InputDispatcher::processAnrsLocked() {
...
// Check if any connection ANRs are due
nextAnrCheck = std::min(nextAnrCheck, mAnrTracker.firstTimeout());
// // 这个时候当前时间大于nextAnrCheck
if (currentTime < nextAnrCheck) { // most likely scenario
return nextAnrCheck; // everything is normal. Let's check again at nextAnrCheck
}
...........
// 触发anr
onAnrLocked(connection);
return LONG_LONG_MIN;
}
上面的这种情况是view设置了onClick回调 ,第一次点击不会发生anr但是第二次就会了。
如果是view设置了onTouch回调,第一次点击就会报anr,在View.dispatchTouchEvent中如果Activity中的控件触摸回调设置了onTouch,所以就直接给它处理了。
下面我们来看看anr的触发逻辑:
anr的触发逻辑
/frameworks/native/services/inputflinger/dispatcher/InputDispatcher.cpp
在dispatchOnce(),会调用processAnrsLocked 方法来决定是否需要触发anr:
void InputDispatcher::dispatchOnce() {
...
// 检查应用程序是否正处于anr
const nsecs_t nextAnrCheck = processAnrsLocked();
....
}
processAnrsLocked
nsecs_t InputDispatcher::processAnrsLocked() {
const nsecs_t currentTime = now();
// 下一次检查anr的时间
nsecs_t nextAnrCheck = LONG_LONG_MAX;
// Check if we are waiting for a focused window to appear. Raise ANR if waited too long
// 检查我们是否正在等待一个聚焦窗口出现。如果等待时间过长就报 ANR
if (mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr) {
if (currentTime >= *mNoFocusedWindowTimeoutTime) {
// 场景1:找不到focus window,有focus app
onAnrLocked(mAwaitedFocusedApplication);
mAwaitedFocusedApplication.clear();
return LONG_LONG_MIN;
} else {
// Keep waiting
const nsecs_t millisRemaining = ns2ms(*mNoFocusedWindowTimeoutTime - currentTime);
ALOGW("Still no focused window. Will drop the event in %" PRId64 "ms", millisRemaining);
nextAnrCheck = *mNoFocusedWindowTimeoutTime;
}
}
// Check if any connection ANRs are due
// 检查是否有任何连接 ANR 到期,mAnrTracker 中保存所有已分发事件(未被确认消费的事件)的超时时间
nextAnrCheck = std::min(nextAnrCheck, mAnrTracker.firstTimeout());
if (currentTime < nextAnrCheck) { // most likely scenario
// 一切正常,在 nextAnrCheck 再检查一次
return nextAnrCheck; // everything is normal. Let's check again at nextAnrCheck
}
// If we reached here, we have an unresponsive connection.
sp<Connection> connection = getConnectionLocked(mAnrTracker.firstToken());
if (connection == nullptr) {
ALOGE("Could not find connection for entry %" PRId64, mAnrTracker.firstTimeout());
return nextAnrCheck;
}
connection->responsive = false;
// Stop waking up for this unresponsive connection
// 停止为此无响应的连接唤醒
mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
// 场景2;找得到focus window
onAnrLocked(connection);
return LONG_LONG_MIN;
}
mAnrTracker 存储已经成功分发给应用的事件,在https://blog.csdn.net/m0_61097519/article/details/134188988 3.13小结startDispatchCycleLocked函数也有说明,mNoFocusedWindowTimeoutTime 是在findFocusedWindowTargetsLocked() 方法中赋值的。
从processAnrsLocked函数可以看出有两个场景ANR的条件:
- 有等待获取焦点的应用:当前时间超过Timeout。
- 存在window:当前时间超过事件响应的超时时间。调用onAnrLocked() 进一步确认。
场景1:找不到focus window,有focus app
void InputDispatcher::onAnrLocked(const sp<InputApplicationHandle>& application) {
std::string reason = android::base::StringPrintf("%s does not have a focused window",
application->getName().c_str());
// 收集anr的window、reason信息
updateLastAnrStateLocked(application, reason);
// 调用doNotifyAnrLockedInterruptible函数指针走anr提示框流程
std::unique_ptr<CommandEntry> commandEntry =
std::make_unique<CommandEntry>(&InputDispatcher::doNotifyAnrLockedInterruptible);
commandEntry->inputApplicationHandle = application;
commandEntry->inputChannel = nullptr;
commandEntry->reason = std::move(reason);
// 将anr的命令添加到 mCommandQueue 中
postCommandLocked(std::move(commandEntry));
}
收集anr信息会调用到dumpDispatchStateLocked函数,主要会打印当前window和事件队列信息,执行dumpsys input命令dumpDispatchStateLocked函数输出内容入下:
Input Dispatcher State:
....
PendingEvent: <none> // 当前正在调度转储事件。
InboundQueue: <empty> // Inbound 队列
ReplacedKeys: <empty>
Connections:
317: channelName='cf1eda9 com..../com....MainActivity (server)', windowName='cf1eda9 com.../com....MainActivity (server)', status=NORMAL, monitor=false, responsive=true
OutboundQueue: <empty>
WaitQueue: length=4
MotionEvent(deviceId=9, source=0x00001002, displayId=0, action=DOWN, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, classification=NONE, edgeFlags=0x00000000, xPrecision=22.8, yPrecision=10.8, xCursorPosition=nan, yCursorPosition=nan, pointers=[0: (700.0, 1633.9)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=0, age=4129ms, wait=4128ms
MotionEvent(deviceId=9, source=0x00001002, displayId=0, action=UP, actionButton=0x00000000, flags=0x00000000, metaState=0x00000000, buttonState=0x00000000, classification=NONE, edgeFlags=0x00000000, xPrecision=22.8, yPrecision=10.8, xCursorPosition=nan, yCursorPosition=nan, pointers=[0: (700.0, 1633.9)]), policyFlags=0x62000000, targetFlags=0x00000105, resolvedAction=1, age=4011ms, wait=4010ms
....
从上面可以看到InboundQueue,OutboundQueue,WaitQueue 3个Queue的状态。其中WaitQueue的size为4,即两对点击事件在等待com...
消费。
从onAnrLocked(application),我们还可以看出会调用到doNotifyAnrLockedInterruptible函数指针去走anr提示框流程:
void InputDispatcher::doNotifyAnrLockedInterruptible(CommandEntry* commandEntry) {
sp<IBinder> token =
commandEntry->inputChannel ? commandEntry->inputChannel->getConnectionToken() : nullptr;
mLock.unlock();
// mPolicy是InputDispatcherPolicyInterface 接口的实例
// NativeInputManager 类实现了InputDispatcherPolicyInterface接口
// 所以这里会调用到NativeInputManager下的notifyAnr函数,也就是通过jni通知到java层
const nsecs_t timeoutExtension =
mPolicy->notifyAnr(commandEntry->inputApplicationHandle, token, commandEntry->reason);
....................
}
场景2:找得到focus window
void InputDispatcher::onAnrLocked(const sp<Connection>& connection) {
// Since we are allowing the policy to extend the timeout, maybe the waitQueue
// is already healthy again. Don't raise ANR in this situation
// 由于我们允许策略延长超时,因此 waitQueue 可能已经再次正常运行。在这种情况下不要触发 ANR
if (connection->waitQueue.empty()) {
ALOGI("Not raising ANR because the connection %s has recovered",
connection->inputChannel->getName().c_str());
return;
}
/**
* “最旧的条目”是首次发送到应用程序的条目。但是,该条目可能不是导致超时发生的条目。
* 一种可能性是窗口超时已更改。这可能会导致较新的条目在已分派的条目之前超时。
* 在这种情况下,最新条目会导致 ANR。但很有可能,该应用程序会线性处理事件。
* 因此,提供有关最早条目的信息似乎是最有用的。
*/
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());
// 收集anr的window、reason信息
updateLastAnrStateLocked(getWindowHandleLocked(connection->inputChannel->getConnectionToken()),
reason);
// 调用doNotifyAnrLockedInterruptible函数指针走anr提示框流程
std::unique_ptr<CommandEntry> commandEntry =
std::make_unique<CommandEntry>(&InputDispatcher::doNotifyAnrLockedInterruptible);
commandEntry->inputApplicationHandle = nullptr;
commandEntry->inputChannel = connection->inputChannel;
commandEntry->reason = std::move(reason);
// 将anr的命令添加到 mCommandQueue 中
postCommandLocked(std::move(commandEntry));
}
下面我们来看看anr的窗口弹出流程
anr窗口弹出流程
这里就展示一个时序图了,大致流程就是这样:
6. WindowInputEventReceiver中的onInputEvent方法,是运行在主线程还是子线程?
先回答问题,主线程。
觉得是子线程的可能认为主线程不会一直阻塞等待来自系统侧的消息,但是我们看看https://blog.csdn.net/m0_61097519/article/details/134188988 4.1小结的内容inputChannel的创建与初始化过程中,在ViewRootImpl.setView里面,这里会注册一个基于looper的FD监听:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
...........
// 将InputChannel传入InputEventReceiver
// 创建app端监听,即WindowInputEventReceiver 作为事件的接收端
mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
Looper.myLooper());
}
// ...
}
}
}
这个时候基于looper的epoll机制,如果这个FD收到传递过来的消息,就会自动唤醒主线程,然后在主线程就会通过其逻辑读取这个输入的信号。
扩展延伸
input事件分发为什么使用socket通信而不是binder?
Android input子系统使用Socket通信而不是Binder来进行输入事件分发的原因主要有以下几点:
1. 独立性和稳定性:使用Socket通信可以将输入事件分发的逻辑与客户端应用程序隔离开,确保系统的稳定性。即使某个客户端应用程序崩溃或出现问题,不会对整个系统产生负面影响。
2. 跨进程通信:Socket通信是一种跨进程通信方式,允许InputDispatcher与多个客户端应用程序同时通信,而不受Binder的限制。这对于同时处理多个应用程序的输入事件非常重要。
3. 独立的通信通道:每个客户端应用程序都与InputDispatcher建立了独立的Socket通信通道,这意味着它们之间的通信是相互隔离的,不会相互干扰。
4. 简化的通信模型:Socket通信提供了一种相对简单的通信模型,易于实现和维护。它不涉及Binder的复杂性,如Binder Stub和Binder Proxy。
5. 灵活性:Socket通信在不同设备和平台上都具有良好的兼容性,因此它更具灵活性,适用于多种Android设备和架构。
总的来说,Socket通信在输入事件分发方面提供了稳定性、独立性和跨进程通信的优势,使其成为Android input子系统中的首选通信方式。尽管Binder在其他方面具有强大的功能,但在这个特定的应用领域,Socket通信更适合实现输入事件的高效分发。
ANR发生后会立马弹出ANR框吗?
不会,触发了ANR并且采集完相关线程日志后,会延时5S在弹出ANR的框。
主线程卡顿一定会导致ANR吗?
不会,一般来说,主线程有耗时操作会导致卡顿,卡顿超过阈值,触发ANR。其次,一定要满足某个场景才可以。比如说在onCreate,onResume生命周期执行耗时超过5s的任务,期间不进行点击,这种就不会发生anr,anr的条件是waitQueue 不为空,在activity启动过程,没有触发input事件的分发,也就自然不会调用anr检查流程。
比如ActivityA跳转ActivityB之后,延时1S后主线程休眠20S,就不会ANR,因为输入事件已经得到响应,并不满足任何触发ANR的场景。