喜马拉雅启动后显示用户协议相关 Dialog。这时不响应 touch event。复现后,回到 Home,重新进入也不恢复。
概率性发生的。重启后概率性恢复。
相关 log:
2713 2713 W ViewRootImpl[MainActivity]: Dropping event due to no window focus: MotionEvent { action=ACTION_DOWN, actionButton=0, id[0]=0, x[0]=934.4269, y[0]=460.23578, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=0, eventTime=89426937, downTime=89426937, deviceId=4, source=0x1002 }
这是跑 monkey 的时候复现的。手动复现步骤如下:
直接原因是 ViewRootImpl 对象的 mStopped 字段为 true。
// frameworks/base/core/java/android/view/ViewRootImpl.java
protected boolean shouldDropInputEvent(QueuedInputEvent q) {
if (mView == null || !mAdded) {
Slog.w(mTag, "Dropping event due to root view being removed: " + q.mEvent);
return true;
} else if ((!mAttachInfo.mHasWindowFocus
&& !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
|| mStopped
|| (mIsAmbientMode && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_BUTTON))
|| (mPausedForTransition && !isBack(q.mEvent))) {
// Drop non-terminal input events.
Slog.w(mTag, "Dropping event due to no window focus: " + q.mEvent);
return true;
}
return false;
}
mStopped 表示相关 Window 的状态,如果是 true 则表明 inactive,所以会 drop event。而 mStopped 就是在 Activity 的 performRestart()
和 performStop()
方法中改变的。
// frameworks/base/core/java/android/app/Activity.java
final void performRestart() {
if (mToken != null && mParent == null) {
// No need to check mStopped, the roots will check if they were actually stopped.
WindowManagerGlobal.getInstance().setStoppedState(mToken, false /* stopped */);
}}
final void performStop(boolean preserveWindow) {
// If we're preserving the window, don't setStoppedState to true, since we
// need the window started immediately again. Stopping the window will
// destroys hardware resources and causes flicker.
if (!preserveWindow && mToken != null && mParent == null) {
WindowManagerGlobal.getInstance().setStoppedState(mToken, true);
}}
// frameworks/base/core/java/android/view/WindowManagerGlobal.java
public void setStoppedState(IBinder token, boolean stopped) {
synchronized (mLock) {
int count = mViews.size();
for (int i = 0; i < count; i++) {
if (token == null || mParams.get(i).token == token) {
ViewRootImpl root = mRoots.get(i);
root.setWindowStopped(stopped);
}}}}
// frameworks/base/core/java/android/view/ViewRootImpl.java
// Set to true if the owner of this window is in the stopped state,
// so the window should no longer be active.
boolean mStopped = false;
void setWindowStopped(boolean stopped) {
if (mStopped != stopped) {
mStopped = stopped;
...
}}
注意 setStoppedState()
方法的第一个参数是 token,这是 WindowManager.LayoutParams 类的 token 字段。用 token 来表示改变 mStopped 的是哪一个 Window。这里会遍历所有 ViewRootImpl,如果 token 相同则调用 setWindowStopped()
方法。
接下来看一下,这个 token 是怎么来的。对于一个 dialog window,它的 token 是在 addView()
时赋值的,其实就是启动该 dialog 的 activity window 的 mAppToken。最终是在 adjustLayoutParamsForSubWindow()
方法中赋值的。
// frameworks/base/core/java/android/view/WindowManagerImpl.java
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
// frameworks/base/core/java/android/view/WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
ViewRootImpl root;
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView);
}
// frameworks/base/core/java/android/view/Window.java
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
if (wp.token == null) {
wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
}}