事件分发拦截消费源码解读

来源:互联网 发布:网络飞机票怎么取登机 编辑:程序博客网 时间:2024/06/06 06:24

事件源头

touch事件(用MotionEvent表示)发生时,事件最先传递给当前的Activity, 由Activity的dispatchTouchEvent进行分发:
android7.0.0_r4\androidn\frameworks\base\core\java\android\app\Activity.java

    /**     * Called to process touch screen events.  You can override this to     * intercept all touch screen events before they are dispatched to the     * window.  Be sure to call this implementation for touch screen events     * that should be handled normally.     *     * @param ev The touch screen event.     *     * @return boolean Return true if this event was consumed.     */    public boolean dispatchTouchEvent(MotionEvent ev) {        if (ev.getAction() == MotionEvent.ACTION_DOWN) {            onUserInteraction(); // 空实现        }        if (getWindow().superDispatchTouchEvent(ev)) { //getWindow()返回的是PhoneWindow对象             return true;        }        return onTouchEvent(ev); // 如果没有View处理则回调Activity.onTouchEvent();    }

android7.0.0_r4\androidn\frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java

    @Override    public boolean superDispatchTouchEvent(MotionEvent event) {        // mDecor(DecorView类型对象)is the top-level view        // of the window, containing the window decor.        // mDecor.superDispatchTouchEvent(event)实际调用的        // 是super.dispatchTouchEvent(event);        return mDecor.superDispatchTouchEvent(event);            }

根据上面类图,mDecor.superDispatchTouchEvent(event)最终调用的是ViewGrop.dispatchTouchEvent(event)
android7.0.0_r4\androidn\frameworks\base\core\java\android\view\ViewGroup.java

    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        ....        boolean handled = false;        // onFilterTouchEventForSecurity 应用安全策略过滤touch event,比如窗口模糊或被遮掩(此时        // 返回false),正常返回true;        if (onFilterTouchEventForSecurity(ev)) {            final int action = ev.getAction();            // MotionEvent.ACTION_MASK = 0xff            final int actionMasked = action & MotionEvent.ACTION_MASK;            // stage 1 ---> Handle an initial down.            if (actionMasked == MotionEvent.ACTION_DOWN) {                // Throw away all previous state when starting a new touch gesture.                // The framework may have dropped the up or cancel event for the previous gesture                // due to an app switch, ANR, or some other state change.                cancelAndClearTouchTargets(ev);  // 注意回调mFirstTouchTarget = null;                resetTouchState(); // 注意回调mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;                        }            // stage 2 ---> Check for interception.            final boolean intercepted;            if (actionMasked == MotionEvent.ACTION_DOWN                    || mFirstTouchTarget != null) { // DOWN事件或者DOWN事件被它的child view处理                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; // 调用requestDisableInterceptTouchEvent方法后则该boolean为true                if (!disallowIntercept) {                    // onInterceptTouchEvent 返回true则该MotionEvent不传给child view,                     // 而被这个ViewGrop onTouchEvent消费                    intercepted = onInterceptTouchEvent(ev);                    ev.setAction(action); // restore action in case it was changed                } else {                    intercepted = false;                }            } else { // 不是DOWN事件并且DOWN事件没被它的child view拦截消费,继续拦截该事件                // There are no touch targets and this action is not an initial down                // so this view group continues to intercept touches.                intercepted = true;            }            // If intercepted, start normal event dispatch. Also if there is already            // a view that is handling the gesture, do normal event dispatch.            if (intercepted || mFirstTouchTarget != null) {                ev.setTargetAccessibilityFocus(false);            }            // stage 3 ---> Check for cancelation.            final boolean canceled = resetCancelNextUpFlag(this)                    || actionMasked == MotionEvent.ACTION_CANCEL;            // stage 4 ---> Update list of touch targets for pointer down, if needed.            final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;  // split常为true            TouchTarget newTouchTarget = null;            boolean alreadyDispatchedToNewTouchTarget = false;            if (!canceled && !intercepted) {                // If the event is targeting accessiiblity focus we give it to the                // view that has accessibility focus and if it does not handle it                // we clear the flag and dispatch the event to all children as usual.                // We are looking up the accessibility focused host to avoid keeping                // state since these events are very rare.                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()                        ? findChildWithAccessibilityFocus() : null;                if (actionMasked == MotionEvent.ACTION_DOWN                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { // 只处理down和hover_move(值7,move值2)事件                    final int actionIndex = ev.getActionIndex(); // always 0 for down                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)                            : TouchTarget.ALL_POINTER_IDS; // idBitsToAssign常为1                    // Clean up earlier touch targets for this pointer id in case they                    // have become out of sync.                    removePointersFromTouchTargets(idBitsToAssign);                    final int childrenCount = mChildrenCount; // 当前ViewGroup的child view个数                    if (newTouchTarget == null && childrenCount != 0) {                        final float x = ev.getX(actionIndex);                        final float y = ev.getY(actionIndex);                        // Find a child that can receive the event.                        // Scan children from front to back.                        // 下面这句根据该ViewGroup的child views的Z序是否为0,为0返回空(最顶层的child view                        // 的Z序不为0),不为0返回child views list;                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();/                        final boolean customOrder = preorderedList == null                                && isChildrenDrawingOrderEnabled();                        final View[] children = mChildren;                        for (int i = childrenCount - 1; i >= 0; i--) {                            final int childIndex = getAndVerifyPreorderedIndex(                                    childrenCount, i, customOrder); // customOrder为false返回i                            final View child = getAndVerifyPreorderedView(                                    preorderedList, children, childIndex);// preorderedList不为null返回它list里面的子项,否则返回mChildren[]数组的子项                            // If there is a view that has accessibility focus we want it                            // to get the event first and if not handled we will perform a                            // normal dispatch. We may do a double iteration but this is                            // safer given the timeframe.                            if (childWithAccessibilityFocus != null) {// childWithAccessibilityFocus常null                                if (childWithAccessibilityFocus != child) {                                    continue;                                }                                childWithAccessibilityFocus = null;                                i = childrenCount - 1;                            }                            // 检查child控件是否正在动画或者点击区域是否在child控件区域内                            if (!canViewReceivePointerEvents(child)                                    || !isTransformedTouchPointInView(x, y, child, null)) {                                ev.setTargetAccessibilityFocus(false);                                continue;                            }                            // 从mFirstTouchTarget及其链表中找TouchTarget对象的view成员是否和child对象一致,找到则返回该TouchTarget,否则返回null                            newTouchTarget = getTouchTarget(child);                             if (newTouchTarget != null) {                                // Child is already receiving touch within its bounds.                                // Give it the new pointer in addition to the ones it is handling.                                newTouchTarget.pointerIdBits |= idBitsToAssign;                                break;                            }                            resetCancelNextUpFlag(child);                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                                // Child wants to receive touch within its bounds.                                mLastTouchDownTime = ev.getDownTime();                                if (preorderedList != null) {                                    // childIndex points into presorted list, find original index                                    for (int j = 0; j < childrenCount; j++) {                                        if (children[childIndex] == mChildren[j]) {                                            mLastTouchDownIndex = j;                                            break;                                        }                                    }                                } else {                                    mLastTouchDownIndex = childIndex;                                }                                mLastTouchDownX = ev.getX();                                mLastTouchDownY = ev.getY();                                newTouchTarget = addTouchTarget(child, idBitsToAssign);// mFirstTouchTarget = newTouchTarget;                                alreadyDispatchedToNewTouchTarget = true;                                break;                            }                            // The accessibility focus didn't handle the event, so clear                            // the flag and do a normal dispatch to all children.                            ev.setTargetAccessibilityFocus(false);                        } // end of for (int i = childrenCount - 1; i >= 0; i--) {                        if (preorderedList != null) preorderedList.clear();                    } // end of if (newTouchTarget == null && childrenCount != 0) {                    if (newTouchTarget == null && mFirstTouchTarget != null) {                        // Did not find a child to receive the event.                        // Assign the pointer to the least recently added target.                        newTouchTarget = mFirstTouchTarget;                        while (newTouchTarget.next != null) {                            newTouchTarget = newTouchTarget.next;                        }                        newTouchTarget.pointerIdBits |= idBitsToAssign;                    }                } // end of if (actionMasked == MotionEvent.ACTION_DOWN....)            } // end of if (!canceled && !intercepted)            // stage 5 ---> Dispatch to touch targets.            if (mFirstTouchTarget == null) {                // No touch targets so treat this as an ordinary view.                handled = dispatchTransformedTouchEvent(ev, canceled, null,                        TouchTarget.ALL_POINTER_IDS);            } else {                // Dispatch to touch targets, excluding the new touch target if we already                // dispatched to it.  Cancel touch targets if necessary.                TouchTarget predecessor = null;                TouchTarget target = mFirstTouchTarget;                while (target != null) {                    final TouchTarget next = target.next;                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {                        handled = true;                    } else {                        final boolean cancelChild = resetCancelNextUpFlag(target.child)                                || intercepted;                        if (dispatchTransformedTouchEvent(ev, cancelChild,                                target.child, target.pointerIdBits)) {                            handled = true;                        }                        if (cancelChild) {                            if (predecessor == null) {                                mFirstTouchTarget = next;                            } else {                                predecessor.next = next;                            }                            target.recycle();                            target = next;                            continue;                        }                    }                    predecessor = target;                    target = next;                }            }            // stage 6 Update list of touch targets for pointer up or cancel, if needed.            if (canceled                    || actionMasked == MotionEvent.ACTION_UP                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {                resetTouchState();            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {                final int actionIndex = ev.getActionIndex();                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);                removePointersFromTouchTargets(idBitsToRemove);            }        } // end of onFilterTouchEventForSecurity        if (!handled && mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);        }        return handled;    }

这个函数的代码太长了,没关系,我们从头理起就更好理解了,这个头是什么呢?我们知道touch事件最先是down事件,后面可能跟着一丢move,然后是up,所以我们的头就是down事件,我们带着下面的问题看代码:
问题1: ViewGroup拦截down事件,down事件怎么分发消费?
问题2:ViewGroup没有拦截down事件,down事件又怎么分发消费?


问题1 ViewGroup拦截down事件

stage 1: mFirstTouchTarget将置null,mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
stage 2: disallowIntercept返回false, boolean intercepted的值取决于onInterceptTouchEvent函数的返回值,假如该函数返回true,也即拦截down事件;

  • stage 3: boolean canceled将返回false;
  • stage 4: 由于intercepted为true导致if (!canceled && !intercepted)返回false,所以stage 4不走;
  • stage 5: 由于mFirstTouchTarget在stage 1时被置null,所以执行handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS),从下面的代码看最终会回调handled = super.dispatchTouchEvent(event)这句代码,由于ViewGroup继承View类,所以最终调用View.dispatchTouchEvent, 看代码如果该root viewGroup属性enable并且通过setOnTouchListener设置了Touch Listener, 会回调该Listener的onTouch()接口,如果onTouch返回false才会调用onTouchEvent方法,否则不执行onTouchEvent方法。
    /**     * Transforms a motion event into the coordinate space of a particular child view,     * filters out irrelevant pointer ids, and overrides its action if necessary.     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.     */    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { // 这个函数很重要        final boolean handled;        // Canceling motions is a special case.  We don't need to perform any transformations        // or filtering.  The important part is the action, not the contents.        final int oldAction = event.getAction();        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {            event.setAction(MotionEvent.ACTION_CANCEL);            if (child == null) {                handled = super.dispatchTouchEvent(event);            } else {                handled = child.dispatchTouchEvent(event);            }            event.setAction(oldAction);            return handled;        }        // Calculate the number of pointers to deliver.        final int oldPointerIdBits = event.getPointerIdBits();        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;        // If for some reason we ended up in an inconsistent state where it looks like we        // might produce a motion event with no pointers in it, then drop the event.        if (newPointerIdBits == 0) {            return false;        }        // If the number of pointers is the same and we don't need to perform any fancy        // irreversible transformations, then we can reuse the motion event for this        // dispatch as long as we are careful to revert any changes we make.        // Otherwise we need to make a copy.        final MotionEvent transformedEvent;        if (newPointerIdBits == oldPointerIdBits) { // 常相等            if (child == null || child.hasIdentityMatrix()) { // child不为null时hasIdentityMatrix()常为true                if (child == null) {                    handled = super.dispatchTouchEvent(event); //                 } else {                    final float offsetX = mScrollX - child.mLeft;                    final float offsetY = mScrollY - child.mTop;                    event.offsetLocation(offsetX, offsetY);                    handled = child.dispatchTouchEvent(event); // 分发看child是否拦截和消费(注意View.java没有拦截接口)                    event.offsetLocation(-offsetX, -offsetY);                }                return handled;            }            transformedEvent = MotionEvent.obtain(event);        } else {            transformedEvent = event.split(newPointerIdBits);        }        // Perform any necessary transformations and dispatch.        if (child == null) {            handled = super.dispatchTouchEvent(transformedEvent);        } else {            final float offsetX = mScrollX - child.mLeft;            final float offsetY = mScrollY - child.mTop;            transformedEvent.offsetLocation(offsetX, offsetY);            if (! child.hasIdentityMatrix()) {                transformedEvent.transform(child.getInverseMatrix());            }            handled = child.dispatchTouchEvent(transformedEvent);        }        // Done.        transformedEvent.recycle();        return handled;    }

android7.0.0_r4\androidn\frameworks\base\core\java\android\view\View.java

    /**     * Pass the touch screen motion event down to the target view, or this     * view if it is the target.     *     * @param event The motion event to be dispatched.     * @return True if the event was handled by the view, false otherwise.     */    public boolean dispatchTouchEvent(MotionEvent event) {        // If the event should be handled by accessibility focus first.        if (event.isTargetAccessibilityFocus()) {            // We don't have focus or no virtual descendant has it, do not handle the event.            if (!isAccessibilityFocusedViewOrHost()) {                return false;            }            // We have focus and got the event, then use normal event dispatch.            event.setTargetAccessibilityFocus(false);        }        boolean result = false;        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(event, 0);        }        final int actionMasked = event.getActionMasked();        if (actionMasked == MotionEvent.ACTION_DOWN) {            // Defensive cleanup for new gesture            stopNestedScroll();        }        if (onFilterTouchEventForSecurity(event)) {            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {                result = true;            }            //noinspection SimplifiableIfStatement            ListenerInfo li = mListenerInfo;            // 属性ENABLE并且设置了OnTouchListener接口,执行该接口onTouch回调,            // 并根据该回调返回值决定是否走onTouchEvent方法            if (li != null && li.mOnTouchListener != null                    && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) { // 注意onTouch返回值对result的影响                result = true;            }            if (!result && onTouchEvent(event)) { // 注意onTouchEvent返回值对result的影响                result = true;            }        }        if (!result && mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);        }        // Clean up after nested scrolls if this is the end of a gesture;        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest        // of the gesture.        if (actionMasked == MotionEvent.ACTION_UP ||                actionMasked == MotionEvent.ACTION_CANCEL ||                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {            stopNestedScroll();        }        return result; // 如果onTouch和onTouchEvent返回false,则result为false    }

总结问题1:ViewGroup拦截down事件,down事件不会传给它的child view,而是被自己消费,如果该ViewGroup拦截down事件前设置了OnTouchListener监听接口则调用监听接口的onTouch回调,如果onTouch回调未消费该down事件(返回false)则down事件传给OnTouchEvent方法消费,如果onTouchEvent还未消费(返回false),则传给该ViewGroup的父ViewGroup消费。


问题2 ViewGroup不拦截down事件

stage 1: 将mFirstTouchTarget置null,mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
stage 2: disallowIntercept返回false, boolean intercepted的值取决于onInterceptTouchEvent函数的返回值,假如该函数返回false,也即不拦截down事件;

  • stage 3: boolean canceled将返回false;
  • stage 4: 由于intercepted为false导致if (!canceled && !intercepted)为true,所以进入stage 4,其伪代码如下图;
  • stage 5: 如果stage 4没有消费down事件,则mFirstTouchTarget为null,该ViewGroup自己消费down事件。

总结问题2:ViewGroup拦截down事件不拦截down事件,down事件传给它的child view,如果child view没有拦截和消费down事件,则由该ViewGroup消费。


后续move/up事件

理清了问题1和问题2,也即理清了down事件(action值0)后面的move(action值2)和up(action值1)怎么分发和消费也可根据代码理清了,下面只针对问题3进行说明(备注cancel事件action值是3):
问题3: 如果down事件被ViewGroup的child view消费了,而后该ViewGroup拦截了后续的move/up事件,消费了down事件的child view会消费move/up事件吗?
为了说明这个问题,写了个demo,布局和日志代码以及日志结果截图如下:

<?xml version="1.0" encoding="utf-8"?><com.hjq.view.EventBusParentLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:id="@+id/linearlayout_parenteventbus"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="vertical">    <com.hjq.view.EventBusLinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"        android:id="@+id/linearlayout_eventbus"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:orientation="vertical">        <android.support.design.widget.AppBarLayout            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:theme="@style/AppTheme.AppBarOverlay">            <android.support.v7.widget.Toolbar                android:id="@+id/toolbar"                android:layout_width="match_parent"                android:layout_height="?attr/actionBarSize"                android:background="?attr/colorPrimary"                app:popupTheme="@style/AppTheme.PopupOverlay" />        </android.support.design.widget.AppBarLayout>        <com.hjq.view.EventBusButton            android:id="@+id/btn_eventbus"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:text="Test"></com.hjq.view.EventBusButton>    </com.hjq.view.EventBusLinearLayout></com.hjq.view.EventBusParentLinearLayout>
public class EventBusLinearLayout extends LinearLayout {    private static final String TAG = "EventBusLinearLayout";    .....    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        boolean result = super.dispatchTouchEvent(ev);        Log.i(TAG, "dispatchTouchEvent(" + ev.getAction() + "): " + result);        return result;    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        // boolean result = super.onInterceptTouchEvent(ev);        boolean result = false;        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN:                result = super.onInterceptTouchEvent(ev);                break;            case MotionEvent.ACTION_MOVE:                result = true;                break;        }        Log.i(TAG, "onInterceptTouchEvent(" + ev.getAction() + "): " + result);        return result;    }    @Override    public boolean onTouchEvent(MotionEvent event) {        // boolean result = super.onTouchEvent(event);        boolean result = false;        Log.i(TAG, "onTouchEvent(" + event.getAction() + "): " + result);        return result;    }}
public class EventBusButton extends Button {    private static final String TAG = "EventBusButton";    ....    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        boolean result = super.dispatchTouchEvent(ev);        Log.i(TAG, "dispatchTouchEvent(" + ev.getAction() + "): " + result);        return result;    }    @Override    public boolean onTouchEvent(MotionEvent event) {        boolean result = super.onTouchEvent(event);        // boolean result = false;        Log.i(TAG, "onTouchEvent(" + event.getAction() + "): " + result);        return result;    }}
07-12 17:38:41.861 7331-7331/com.hjq.contentproviderclient I/EventBusLinearLayout: onInterceptTouchEvent(0): false07-12 17:38:41.863 7331-7331/com.hjq.contentproviderclient I/EventBusButton: onTouchEvent(0): true07-12 17:38:41.863 7331-7331/com.hjq.contentproviderclient I/EventBusButton: dispatchTouchEvent(0): true07-12 17:38:41.863 7331-7331/com.hjq.contentproviderclient I/EventBusLinearLayout: dispatchTouchEvent(0): true07-12 17:38:41.863 7331-7331/com.hjq.contentproviderclient W/EventBusDemoActivity: dispatchTouchEvent(0): true07-12 17:38:41.880 7331-7331/com.hjq.contentproviderclient I/EventBusLinearLayout: onInterceptTouchEvent(2): true07-12 17:38:41.884 7331-7331/com.hjq.contentproviderclient I/EventBusButton: onTouchEvent(3): true07-12 17:38:41.884 7331-7331/com.hjq.contentproviderclient I/EventBusButton: dispatchTouchEvent(3): true07-12 17:38:41.884 7331-7331/com.hjq.contentproviderclient I/EventBusLinearLayout: dispatchTouchEvent(2): true07-12 17:38:41.884 7331-7331/com.hjq.contentproviderclient W/EventBusDemoActivity: dispatchTouchEvent(2): true07-12 17:38:41.897 7331-7331/com.hjq.contentproviderclient I/EventBusLinearLayout: onTouchEvent(2): false07-12 17:38:41.897 7331-7331/com.hjq.contentproviderclient I/EventBusLinearLayout: dispatchTouchEvent(2): false07-12 17:38:41.897 7331-7331/com.hjq.contentproviderclient W/EventBusDemoActivity: dispatchTouchEvent(2): false07-12 17:38:41.898 7331-7331/com.hjq.contentproviderclient I/EventBusLinearLayout: onTouchEvent(1): false07-12 17:38:41.898 7331-7331/com.hjq.contentproviderclient I/EventBusLinearLayout: dispatchTouchEvent(1): false07-12 17:38:41.898 7331-7331/com.hjq.contentproviderclient W/EventBusDemoActivity: dispatchTouchEvent(1): false

总结问题3:move/up事件不会分发给消费了down事件的child view,该child view会消费cancel事件,对于拦截move事件的ViewGroup会处理后续的move/up事件。

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 中兴手机充电的地方坏了怎么办? 小米手机与电脑蓝牙传输失败怎么办 捡个华为收机没有账号密码怎么办 华为手机p9激活码忘了怎么办 华为畅享7plus声音小怎么办 华为畅享7plus忘记密码怎么办 华为畅享8plus卡顿怎么办 华为畅享7plus卡机怎么办 华为畅享8plus图标字小怎么办 华为畅享6反应慢发热怎么办 华为畅享5S反应迟钝怎么办 华为畅玩5x玩王者荣耀卡怎么办 不小心把手机里的照片删了怎么办 u盘文件在手机上删了怎么办 荒野行动透视挂功能加载失败怎么办 白色t桖衫被奶茶弄脏了该怎么办 游戏文件不小心解压到c盘了怎么办 装系统时c盘0mb怎么办 电脑设置的开机密码忘了怎么办 电脑开机密码忘了怎么办xp系统 我的电脑在开机时忘了密码怎么办? xp桌面我的电脑图标不见了怎么办 游戏全屏时卡了无法退到界面怎么办 u盘插电脑上提示有病毒怎么办 三星手机文件怎么删除不掉怎么办 用夜神模拟器玩第五人格太卡怎么办 雷电模拟器玩刺激战场太卡了怎么办 绝地求生刺激战场模拟器太卡怎么办 ddj sb2打碟功能没了怎么办 驼背怎么办 要能快速矫正的方法 苹果7中间的home键坏了怎么办 苹果6p的home键不管用怎么办 华为获取数据失败请检查网络怎么办 三星手机未解锁刷机变砖怎么办 手机显示充电但是充不进去怎么办 手机拔出显示无法连接移动网怎么办 手机上的音乐老是显示网络忙怎么办 华为手机账号换手机忘记密码怎么办 墨墨背单词的注册邮箱忘了怎么办 华为手机华为账号密码忘记了怎么办 手机玩游戏降频特别厉害怎么办