美文网首页
dialog.window.token为空导致的屏幕无法操作

dialog.window.token为空导致的屏幕无法操作

作者: 丸子不爱吃丸子 | 来源:发表于2019-10-08 10:06 被阅读0次

 之前针对手表开发遇到的一个问题,当app弹出dialog后锁屏,然后屏幕就无法操作了,由于是一个全屏的dialog,直接导致测试同事认为屏幕失效.

  • 问题表现:dialog弹出后锁屏,然后亮屏,屏幕无法操作,back键无效.

  • 追踪log
    发现有如下log频繁打印:

Line 20605: 01-09 03:36:00.820  3584  3584 W ViewRootImpl[MainActivity]: Dropping event due to no window focus: MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=96.223854, y[0]=491.9438, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=160615, downTime=159442, deviceId=1, source=0x5002 }
    Line 20648: 01-09 03:36:00.845  3584  3584 W ViewRootImpl[MainActivity]: Dropping event due to no window focus: MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=90.276855, y[0]=496.7355, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=3, eventTime=160638, downTime=159442, deviceId=1, source=0x5002 }
    Line 20691: 01-09 03:36:00.868  3584  3584 W ViewRootImpl[MainActivity]: Dropping event due to no window focus: MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=89.533676, y[0]=499.87527, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=160661, downTime=159442, deviceId=1, source=0x5002 }
    Line 20734: 01-09 03:36:00.891  3584  3584 W ViewRootImpl[MainActivity]: Dropping event due to no window focus: MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=99.987785, y[0]=503.17758, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=160684, downTime=159442, deviceId=1, source=0x5002 }
    Line 20777: 01-09 03:36:00.914  3584  3584 W ViewRootImpl[MainActivity]: Dropping event due to no window focus: MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=113.40473, y[0]=502.47736, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=160707, downTime=159442, deviceId=1, source=0x5002 }
    Line 20820: 01-09 03:36:00.936  3584  3584 W ViewRootImpl[MainActivity]: Dropping event due to no window focus: MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=123.53931, y[0]=498.88895, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=3, eventTime=160730, downTime=159442, deviceId=1, source=0x5002 }
    Line 20863: 01-09 03:36:00.959  3584  3584 W ViewRootImpl[MainActivity]: Dropping event due to no window focus: MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=126.928474, y[0]=495.8939, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=160754, downTime=159442, deviceId=1, source=0x5002 }
    Line 20906: 01-09 03:36:00.983  3584  3584 W ViewRootImpl[MainActivity]: Dropping event due to no window focus: MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=126.46416, y[0]=492.6097, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=2, eventTime=160777, downTime=159442, deviceId=1, source=0x5002 }
    Line 20949: 01-09 03:36:01.006  3584  3584 W ViewRootImpl[MainActivity]: Dropping event due to no window focus: MotionEvent { action=ACTION_MOVE, actionButton=0, id[0]=0, x[0]=123.98795, y[0]=490.30612, toolType[0]=TOOL_TYPE_FINGER, buttonState=0, metaState=0, flags=0x0, edgeFlags=0x0, pointerCount=1, historySize=3, eventTime=160800, downTime=159442, deviceId=1, source=0x5002 }

分析log发现是框架主动drop事件导致.

查找打印log源代码:

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)
            && !isAutofillUiShowing()) || mStopped
            || (mIsAmbientMode && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_BUTTON))
            || (mPausedForTransition && !isBack(q.mEvent))) {
         分析必然是这里的条件成立,只需要打印每一个条件,即可继续定位问题
        // This is a focus event and the window doesn't currently have input focus or
        // has stopped. This could be an event that came back from the previous stage
        // but the window has lost focus or stopped in the meantime.
        if (isTerminalInputEvent(q.mEvent)) {
            // Don't drop terminal input events, however mark them as canceled.
            q.mEvent.cancel();
            Slog.w(mTag, "Cancelling event due to no window focus: " + q.mEvent);
            return false;
        }

        // Drop non-terminal input events.
        Slog.w(mTag, "Dropping event due to no window focus: " + q.mEvent);
        return true;
    }
    return false;
}

据此分析必然是 else if 条件成立才导致.由于是必先的bug,直接在框架中加入log打印每一个条件

01-09 03:36:01.005  3584  3584 W ViewRootImpl[MainActivity]: ====================ggplog dropevent start=============================
01-09 03:36:01.005  3584  3584 W ViewRootImpl[MainActivity]: ggplog mAttachInfo.mHasWindowFocus: true
01-09 03:36:01.005  3584  3584 W ViewRootImpl[MainActivity]: ggplog q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER): true
01-09 03:36:01.005  3584  3584 W ViewRootImpl[MainActivity]: ggplog mStopped: true
01-09 03:36:01.005  3584  3584 W ViewRootImpl[MainActivity]: ggplog this....: android.view.ViewRootImpl$EarlyPostImeInputStage@30f5af0
01-09 03:36:01.006  3584  3584 W ViewRootImpl[MainActivity]: ggplog mIsAmbientMode: false
01-09 03:36:01.006  3584  3584 W ViewRootImpl[MainActivity]: ggplog q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_BUTTON): false
01-09 03:36:01.006  3584  3584 W ViewRootImpl[MainActivity]: ggplog mPausedForTransition: false
01-09 03:36:01.006  3584  3584 W ViewRootImpl[MainActivity]: ggplog isBack(q.mEvent): false
01-09 03:36:01.006  3584  3584 W ViewRootImpl[MainActivity]: ====================ggplog dropevent end=============================

很明显这里mStopped == true 然后进入if条件成立,事件被dropping
继续分析为什么mStopped会为true
必然是框架哪里流程出现问题才导致stopped赋值出错.
字面意思很容易理解,当窗口在前台的时候必然是false状态.当窗口退到后台则必然是true.处于停止状态,这里大家可以直接dumpsys window查看验证一下.
查找设置mStopped值代码,发现只有一个函数在设置值

ViewRootImpl.java
void setWindowStopped(boolean stopped) {
    if (mStopped != stopped) {
        mStopped = stopped;
        final ThreadedRenderer renderer = mAttachInfo.mThreadedRenderer;
        if (renderer != null) {
            if (DEBUG_DRAW) Log.d(mTag, "WindowStopped on " + getTitle() + " set to " + mStopped);
            renderer.setStopped(mStopped);
        }
        if (!mStopped) {
            scheduleTraversals();
        } else {
            if (renderer != null) {
                renderer.destroyHardwareResources(mView);
            }
        }

        for (int i = 0; i < mWindowStoppedCallbacks.size(); i++) {
            mWindowStoppedCallbacks.get(i).windowStopped(stopped);
        }

        if (mStopped) {
            mSurface.release();
        }
    }
}

分析到这里接下来就容易多了,直接打印调用栈

01-09 03:35:30.149  3584  3584 W ViewRootImpl[MainActivity]: java.lang.Throwable
01-09 03:35:30.149  3584  3584 W ViewRootImpl[MainActivity]:    at android.view.ViewRootImpl.setWindowStopped(ViewRootImpl.java:1141)
01-09 03:35:30.149  3584  3584 W ViewRootImpl[MainActivity]:    at android.view.WindowManagerGlobal.setStoppedState(WindowManagerGlobal.java:618)
01-09 03:35:30.149  3584  3584 W ViewRootImpl[MainActivity]:    at android.app.Activity.performRestart(Activity.java:6779)
01-09 03:35:30.149  3584  3584 W ViewRootImpl[MainActivity]:    at android.app.Activity.performResume(Activity.java:6816)
01-09 03:35:30.149  3584  3584 W ViewRootImpl[MainActivity]:    at android.app.ActivityThread.performResumeActivity(ActivityThread.java:3406)
01-09 03:35:30.149  3584  3584 W ViewRootImpl[MainActivity]:    at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:3469)
01-09 03:35:30.149  3584  3584 W ViewRootImpl[MainActivity]:    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1527)
01-09 03:35:30.149  3584  3584 W ViewRootImpl[MainActivity]:    at android.os.Handler.dispatchMessage(Handler.java:102)

查看WindowManagerGlobal代码:

WindowManagerGlobal.java
public void setStoppedState(IBinder token, boolean stopped) {
    synchronized (mLock) {
        int count = mViews.size();
        for (int i = count - 1; i >= 0; i--) {
            if (token == null || mParams.get(i).token == token) {
                ViewRootImpl root = mRoots.get(i);
                // Client might remove the view by "stopped" event.
                root.setWindowStopped(stopped);
                // Recursively forward stopped state to View's attached
                // to this Window rather than the root application token,
                // e.g. PopupWindow's.
                setStoppedState(root.mAttachInfo.mWindowToken, stopped);
            }
        }
    }
}

在这里添加log,复现一次bug,锁屏解锁,分别打印log,由于我们是activity弹出dialog,很容易理解这里有两个window, count 值 为:2-1

  • 锁屏
01-09 03:35:19.882  3584  3584 V ggplog  : stopped...: true
01-09 03:35:19.882  3584  3584 V ggplog  : count...: 2
第一次
01-09 03:35:19.882  3584  3584 V ggplog  : mParams ( 0). token : android.os.BinderProxy@34056ff
01-09 03:35:19.882  3584  3584 V ggplog  : token: android.os.BinderProxy@34056ff
01-09 03:35:19.882  3584  3584 V ggplog  : mParams.get(i).token == token
01-09 03:35:19.882  3584  3584 V ggplog  : root....: android.view.ViewRootImpl@e1b55b6
第二次
01-09 03:35:19.886  3584  3584 V ggplog  : mParams ( 1). token : android.os.BinderProxy@34056ff
01-09 03:35:19.886  3584  3584 V ggplog  : token: android.os.BinderProxy@34056ff
01-09 03:35:19.887  3584  3584 V ggplog  : mParams.get(i).token == token
01-09 03:35:19.887  3584  3584 V ggplog  : root....: android.view.ViewRootImpl@f11104a
  • 解锁
01-09 03:35:30.144  3584  3584 V ggplog  : stopped...: false
//有两个window
01-09 03:35:30.145  3584  3584 V ggplog  : count...: 2
//第一次循环
01-09 03:35:30.145  3584  3584 V ggplog  : mParams ( 0). token : android.os.BinderProxy@34056ff
01-09 03:35:30.145  3584  3584 V ggplog  : token: android.os.BinderProxy@34056ff
01-09 03:35:30.145  3584  3584 V ggplog  : mParams.get(i).token == token
01-09 03:35:30.145  3584  3584 V ggplog  : root....: android.view.ViewRootImpl@e1b55b6
01-09 03:35:30.149  3584  3584 W ViewRootImpl[MainActivity]: ggplog this....: android.view.ViewRootImpl@e1b55b6
//第二次循环
//这里mParams ( 1). token : null
01-09 03:35:30.151  3584  3584 V ggplog  : mParams ( 1). token : null
01-09 03:35:30.151  3584  3584 V ggplog  : token: android.os.BinderProxy@34056ff

从log发现问题,在设置window stop的时候设置了两次,但是解锁以后应该将window设置stop = false却只有一次,第二次token == null,流程无法继续.

  • 到这里我们已经有了初步结论,由于token == null,导致setStoppedState流程出错,进而导致window stop状态异常,导致ViewRootImpl判断window是stop状态,然后直接drop事件.

  • 接下来我们应该调查为什么token为空?
    关于window token大家可以参考这篇博客,讲的还是很清晰明确的.
    https://blog.csdn.net/hohohong/article/details/54616053
    我们来直接看看Dialog初始化的流程:

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
    if (createContextThemeWrapper) {
        if (themeResId == ResourceId.ID_NULL) {
            final TypedValue outValue = new TypedValue();
            context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
            themeResId = outValue.resourceId;
        }
        mContext = new ContextThemeWrapper(context, themeResId);
    } else {
        mContext = context;
    }

    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

    final Window w = new PhoneWindow(mContext);
    mWindow = w;
    w.setCallback(this);
    w.setOnWindowDismissedCallback(this);
    w.setOnWindowSwipeDismissedCallback(() -> {
        if (mCancelable) {
            cancel();
        }
    });
    w.setWindowManager(mWindowManager, null, null);
    w.setGravity(Gravity.CENTER);

    mListenersHandler = new ListenersHandler(this);
}

重点在于 Window w = new PhoneWindow()
phoneWindow继承了window,window 创建的时候同时new 了成员变量mWindowAttributes

private final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams()

创建dialog的时候创建了phoneWindow,phoneWindow中new了内部成员变量mWindowAttributes,而attibutes中就包含了这个token,此时new出来之后token还是空值.(可以打log验证)

dialog show的过程中有token赋值过程,跟踪源代码发现token赋值过程
Dailog.show() -> windowmanagerImpl.addview()->windowManagerGlobal.addview()
关于token初始复制过程不在详细分析,如果有兴趣可以看一下addWindow全过程.

可以确认的是token初始值是有的,但是在一次锁屏解锁过程之后就变为空了,那么问题必然就出在update过程中.直接查找ViewRootImpl.updateViewLayout()

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }

    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

    view.setLayoutParams(wparams);

    synchronized (mLock) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots.get(index);
        mParams.remove(index);
        mParams.add(index, wparams);
        root.setLayoutParams(wparams, false);
    }
}

这里先是拿到params 然后调用了view.setLayoutParams(),最后将wparams add到list列表中。注意我们回到之前看到在setStop的时候就是取了mParams中的params.token.
直接在updateViewLayout()中添加log并且打印trace查看调用关系。

01-17 03:35:46.064  3512  3512 W WindowManager: java.lang.Throwable
01-17 03:35:46.064  3512  3512 W WindowManager:     at android.view.WindowManagerGlobal.updateViewLayout(WindowManagerGlobal.java:368)
01-17 03:35:46.064  3512  3512 W WindowManager:     at android.view.WindowManagerImpl.updateViewLayout(WindowManagerImpl.java:101)
01-17 03:35:46.064  3512  3512 W WindowManager:     at android.app.Dialog.onWindowAttributesChanged(Dialog.java:747)
01-17 03:35:46.064  3512  3512 W WindowManager:     at android.view.Window.dispatchWindowAttributesChanged(Window.java:1111)
01-17 03:35:46.064  3512  3512 W WindowManager:     at com.android.internal.policy.PhoneWindow.dispatchWindowAttributesChanged(PhoneWindow.java:2954)
01-17 03:35:46.064  3512  3512 W WindowManager:     at android.view.Window.setFlags(Window.java:1088)
01-17 03:35:46.064  3512  3512 W WindowManager:     at com.android.internal.policy.PhoneWindow$3.onSwipeCancelled(PhoneWindow.java:3039)
01-17 03:35:46.064  3512  3512 W WindowManager:     at com.android.internal.widget.SwipeDismissLayout.cancel(SwipeDismissLayout.java:282)
01-17 03:35:46.064  3512  3512 W WindowManager:     at com.android.internal.widget.SwipeDismissLayout$2$1.run(SwipeDismissLayout.java:98)
01-17 03:35:46.064  3512  3512 W WindowManager:     at android.os.Handler.handleCallback(Handler.java:751)
01-17 03:35:46.064  3512  3512 W WindowManager:     at android.os.Handler.dispatchMessage(Handler.java:95)
01-17 03:35:46.064  3512  3512 W WindowManager:     at android.os.Looper.loop(Looper.java:154)
01-17 03:35:46.064  3512  3512 W WindowManager:     at android.app.ActivityThread.main(ActivityThread.java:6167)
01-17 03:35:46.064  3512  3512 W WindowManager:     at java.lang.reflect.Method.invoke(Native Method)
01-17 03:35:46.064  3512  3512 W WindowManager:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
01-17 03:35:46.064  3512  3512 W WindowManager:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)

这里我们发现了一个比较有意思的东西SwipeDismissLayout,直接百度

SwipeDismissFrameLayout是一个右滑退出的布局。联想到我们手表所有view都可以右滑退出,那么我们可以想象一下一定是右滑的时候更新了dialog的参数。锁屏的时候也可能对当前top window做了右滑的操作,不过这已经不重要了。我们现在重要的是看看SwipeDismissFrameLayout到底怎么更新dialog对应window参数的

    swipeDismiss.setOnSwipeProgressChangedListener(
            new SwipeDismissLayout.OnSwipeProgressChangedListener() {
                @Override
                public void onSwipeProgressChanged(
                        SwipeDismissLayout layout, float alpha, float translate) {
                    WindowManager.LayoutParams newParams = getAttributes();
                    newParams.x = (int) translate;
                    newParams.alpha = alpha;
                    setAttributes(newParams);

                    int flags = 0;
                    if (newParams.x == 0) {
                        flags = FLAG_FULLSCREEN;
                    } else {
                        flags = FLAG_LAYOUT_NO_LIMITS;
                    }
                    setFlags(flags, FLAG_FULLSCREEN | FLAG_LAYOUT_NO_LIMITS);
                }

                @Override
                public void onSwipeCancelled(SwipeDismissLayout layout) {
                    WindowManager.LayoutParams newParams = getAttributes();
                    // Swipe changes only affect the x-translation and alpha, check to see if
                    // those values have changed first before resetting them.
                    if (newParams.x != 0 || newParams.alpha != 1) {
                        newParams.x = 0;
                        newParams.alpha = 1;
                        setAttributes(newParams);
                        setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN | FLAG_LAYOUT_NO_LIMITS);
                    }
                }
            });
}

看到这里先是getAttibutes()修改一些参数之后又调用了setAttributes()把参数set回去。
我们打印log查看getAttibutes中的token,看第一次调用的时候token值是什么?

通过我们打印log get 和set 对应的token都是空的,那么也就是从一开始token就是空的,不怪SwipeDismissFrameLayout,这里并没有什么错误。
所以setAttributes()之后自然就是空了。然后又update了params参数。
那么问题又来了,最初dialog的token还赋值了这会怎么就空了?
既然一开始token是有值的,但是后来get的时候是空的,那么有两个方向可以怀疑:
1.中间某个流程设置为null
2.初始化有错误,get了一个错误地址.

回到dialog.show()看看源代码:

public void show() {
   
     .....

    WindowManager.LayoutParams l = mWindow.getAttributes();
    if ((l.softInputMode
            & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
        WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
        nl.copyFrom(l);
        nl.softInputMode |=
                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
        l = nl;
    }

    mWindowManager.addView(mDecor, l);
    mShowing = true;

    sendShowMessage();
}

我们分析一下源代码:
WindowManager.LayoutParams l = mWindow.getAttributes();

WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
....
l = nl
mWindowManager.addView(mDecor, l)
我们知道java除了基本类型是值引用,其他都是地址引用.
这里后期不管怎么给 l 赋值,最后都给到了nl
这是一个很傻逼的写法,我们知道后期需要get window参数的时候肯定是从mWindow.getAttributes()中拿到.
这就导致L中所有参数几乎都没有值.

也就是说一开始token是没有值的,在addwindow的时候将 token复制给了nl,但是这个token并没有给到l,等待再次需要的时候又从l中获取参数.当然就是空了.

*总结
到此,我们已经明白了token为空的原因,那么怎么修复这个bug呢?
由于我们源代码是Android7.0 ,所以我到Android9.0中查看了一下dialog源代码.


dialog.png dialog1.png

看到google在9.0中已经修复了这个错误代码.

相关文章

  • dialog.window.token为空导致的屏幕无法操作

     之前针对手表开发遇到的一个问题,当app弹出dialog后锁屏,然后屏幕就无法操作了,由于是一个全屏的dialo...

  • laravel5.5 500错误

    laravel5.5 500错误 通常是数据库操作错误 比如:设置的字段属性为非空,在操作时为空,导致错误

  • MyBatis更新数据库int类型处理

    尽量使用Integer,否则可能导致数据库不更新,因为int类型无法判断是否为空

  • iOS常用宏定义

    //字符串是否为空 //数组是否为空 //字典是否为空 //是否是空对象 //获取屏幕宽度与高度 //一些缩写 /...

  • SpringBoot前后分离项目实现自定义登录拦截

    通常的 shiro 登录拦截对于 /login 操作可设置为 authc 模式,但前后分离的项目直接设置会导致无法...

  • 流式布局

    1、简介即屏幕分辨率变化时,页面里元素的大小会变化而但布局不变。【这就导致如果屏幕太大或者太小都会导致元素无法正常...

  • fstab误操作导致无法开机

    1,fstab文件被错误修改,导致在开机启动linux时候出现错误,提示让你恢复系统设置。1)开机时出现错误提示 ...

  • iOS常用宏定义

    字符串是否为空 数组是否为空 字典是否为空 是否是空对象 获取屏幕宽度与高度 ( " \ ":连接行标志,连接上下...

  • 空状态设计

    之前关于空状态看了一些文章,整理如下: 导致空状态的几个原因: 初次使用(用户未操作) 清空,完成等状态(用户操作...

  • Unity ios系统手势延迟设置 ScreenEdgesDef

    当IphoneX游戏全屏后会因为误触屏幕边缘, 响应了系统手势导致与游戏内冲突. 解决方案: 延迟屏幕边缘手势操作...

网友评论

      本文标题:dialog.window.token为空导致的屏幕无法操作

      本文链接:https://www.haomeiwen.com/subject/xyhssctx.html