从Android 6.0源码的角度剖析View的事件分发机制

从Android 6.0源码的角度剖析UI界面架构一文中,我们了解到Activity是Android的可视化界面,是用户与Android系统交互的窗口,也就是说每个Activity都对应着一个窗体,但窗体是一个抽象的概念,它的具体表现形式是视图。在Android中,窗体对应着Window类,视图对应着View类。Window是一个抽象类,它的具体实现是PhoneWindow类,该类将DecorView作为窗体的顶层视图并封装了相关操作窗体的方法,而这个DecorView就是View的子类,它将被作为整个窗体的最顶层视图,后续所有我们希望展示的界面(比如setContentView()操作)将被加载到DecorView中,准确来说,是DecorView的内容区域。

 在Android系统中,一个事件用MotionEvent来表示,事件的传递分为分发拦截处理三个过程,它们分别对应于方法dispatchTouchEventonInterceptTouchEventonTouchEvent。根据从Android 6.0源码的角度剖析UI界面架构所述,当一个点击事件到来时,事件的传递总是会遵循以下顺序:Activity->Window->DecorView,即事件首先会被传递到Activity,由Activity的dispatchTouchEvent进行派发事件,而具体的工作则由Activity内部的Window来完成,Window会将事件传递DecorView。DecorView是窗体的最顶层视图,它继承于FrameLayout,是setContentView的底层容器。换句话说,一个事件从传递到Activity开始,会经过最顶层视图DecorView,直到具体的View,当然,期间肯定会经历过多个ViewGroup,但这里我们不考虑,因为多个ViewGroup的传递过程跟在DecorView中是一样的,毕竟DecorView就是一个ViewGroup的子类。Activity视图结构如下:

Activity视图结构

1. Activity对事件的分发过程

 Activity是Android的可视化界面,是用户与Android系统交互的窗口,当用户操作Android应用产生的事件时,该事件首先会传递到Activity,并由Activity的dispatchTouchEvent进行事件分发,该方法源码如下:

public boolean dispatchTouchEvent(MotionEvent ev) {
    // (1)判断down事件
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    // (2)调用PhoneWindow的superDispatchTouchEvent继续传递事件
    //  public Window getWindow() {
    //     return mWindow;
    //  }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    // (3)由Activity消耗事件
    return onTouchEvent(ev);
}

 从上述源码可知,dispatchTouchEvent()方法首先会判断事件是否是down事件,如果是则调用Activity的onUserInteraction()方法,这个方法是个空方法,在实际开发中,我们却可以通过重写该方法获知是否用户与设备进行交互;然后,调用Window的superDispatchTouchEvent()方法将事件传递到顶层视图DecorView,具体的传递过程我们下一小节详细剖析,这里只需要知道从这里开始事件将会被逐级向下传递,如果没有任何一个View或者ViewGroup消耗该事件,getWindow().superDispatchTouchEvent(ev)将返回false,那么事件将会直接交给Activty来处理,即调用Activity的onTouchEvent消耗事件。onTouchEvent()源码如下:

public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }

    return false;
}

 接下来,我们具体分析Window是如何将事件传递给ViewGroup的,即最顶层视图DecorView。通过之前的分析可知,Window是一个抽象类,它的具体实现是PhoneWindow,为此,我们找到PhoneWindow的superDispatchTouchEvent,该方法将直接调用DecorView的superDispatchTouchEvent方法,并在这个方法中将事件交给DecorView的dispatchTouchEvent,切确的说是ViewGroup的dispatchTouchEvent。具体源码如下:

// PhoneWindow.DecorView,窗体顶层视图
private DecorView mDecor;

// PhoneWindow.superDispatchTouchEvent()
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

// PhoneWindow.DecorView.superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
    // surper即ViewGroup
    return super.dispatchTouchEvent(event);
}

2. DecorView对事件的分发过程

 在上一节说到,当事件由Window传递到顶层视图DecorView时,实际上是将事件传递给了ViewGroup,因为DecorView本身继承于FrameLayout,而FrameLayout又是ViewGroup的子类。因此,分析DecorView中事件的分发处理过程,就需要从ViewGroup的dispatchTouchEvent方法入手。ViewGroup.dispatchTouchEvent方法源码如下,考虑到该部分逻辑较为复杂,下面我们将分段剖析。

  • 首先,判断ViewGroup是否需要拦截事件。
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;

// Handle an initial down.
// 1. 初始化状态,当为ACTION_DOWN事件时
// 清除之前的所有状态,开始一次新的触摸事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
    cancelAndClearTouchTargets(ev);
    resetTouchState();
}

// 2. 检查是否需要拦截事件
// 只针对ACTION_DOWN事件,或mFirstTouchTarget!=null情况
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
    final boolean disallowIntercept = (mGroupFlags & 
                                       FLAG_DISALLOW_INTERCEPT) != 0;
    // 当disallowIntercept为false时,调用onInterceptTouchEvent
    if (!disallowIntercept) {
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); 
    } else {
        intercepted = false;
    }
} else {
    // 如果down事件被拦截,同一事件序列的其他事件也会被ViewGroup拦截
    intercepted = true;
}

 从上述代码可知,ViewGroup的dispatchTouchEvent首先会去判断当前触摸事件是否为down事件(ACTION_DOWN),如果是则清理之前的所有状态,并重置FLAG_DISALLOW_INTERCEPT标志,以便开始新的触摸事件序列。然后,通过判断down事件或mFirstTouchTarget != null决定是否对事件进行拦截,即调用ViewGroup的onInterceptTouchEvent方法,从该方法的源码可知它默认返回false,也就是说,ViewGroup默认是不对任何事件进行拦截。对于mFirstTouchTarget对象,后面我们会谈到,这里只需要知道当ViewGroup不拦截事件并交给子元素处理时mFirstTouchTarget!=null,如果拦截则mFirstTouchTarget=null。由此可知,假如ViewGroup在ACTION_DOWN到来时,对事件进行了拦截,那么与down事件的同一事件序列中的其他事件,比如move事件、up事件,都会被ViewGroup拦截且onInterceptTouchEvent也不会再被调用,即intercepted = true。当然,假如ViewGroup没有拦截down事件,那么同一事件序列中的其他事件是否会被拦截,将由FLAG_DISALLOW_INTERCEPT标志决定,该标志由ViewGroup的requestDisallowInterceptTouchEvent方法控制,通常用于子View中,一旦这个标志被设置,即mGroupFlags&FLAG_DISALLOW_INTERCEPTViewGroup!=0,ViewGroup将无法拦截除ACTION_DOWN的其他事件。为什么是除ACTION_DOWN情况,那是因为down事件到来的时候会直接重置FLAG_DISALLOW_INTERCEPT标志。

// ViewGroup.resetTouchState()方法
private void resetTouchState() {
    clearTouchTargets();
    resetCancelNextUpFlag(this);
    // 使mGroupFlags&FLAG_DISALLOW_INTERCEPT=0
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    mNestedScrollAxes = SCROLL_AXIS_NONE;
} 

// ViewGroup.onInterceptTouchEvent()方法
public boolean onInterceptTouchEvent(MotionEvent ev) {
    return false;
}
// ViewGroup.requestDisallowInterceptTouchEvent()方法
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
        // We're already in this state, assume our ancestors are too
        return;
    }
    // mGroupFlags&FLAG_DISALLOW_INTERCEPT!=0为真
    if (disallowIntercept) {
        mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
    } else {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }

    // Pass it up to our parent
    if (mParent != null) {
        mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
}

同一个事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程中所产生的一系列事件,这个事件序列以down事件开始,中间含有数量不定的move事件,最终以up事件结束。

  • 其次,假如ViewGroup没有拦截事件,则将事件进行向下(子元素)分发。
if (!canceled && !intercepted) {
    View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
        ? findChildWithAccessibilityFocus() : null;

    if (actionMasked == MotionEvent.ACTION_DOWN
        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
        final int actionIndex = ev.getActionIndex(); // always 0 for down
        final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
            : TouchTarget.ALL_POINTER_IDS;
        removePointersFromTouchTargets(idBitsToAssign);

        final int childrenCount = mChildrenCount;
        if (newTouchTarget == null && childrenCount != 0) {
            // 获取触摸事件相对于父容器坐标
            final float x = ev.getX(actionIndex);
            final float y = ev.getY(actionIndex);
            // 遍历所有child,找到能够接收当前触摸事件event的child
            final ArrayList<View> preorderedList = buildOrderedChildList();
            final boolean customOrder = preorderedList == null
                && isChildrenDrawingOrderEnabled();
            final View[] children = mChildren;
            for (int i = childrenCount - 1; i >= 0; i--) {
                final int childIndex = customOrder
                    ? getChildDrawingOrder(childrenCount, i) : i;
                // 获取一个child,即子view
                final View child = (preorderedList == null)
                    ? children[childIndex] : preorderedList.get(childIndex);
				// 判断子view是否拥有焦点
                if (childWithAccessibilityFocus != null) {
                    if (childWithAccessibilityFocus != child) {
                        continue;
                    }
                    childWithAccessibilityFocus = null;
                    i = childrenCount - 1;
                }
				// (1)判断子view是否能够接收点击事件
                // 点击事件的坐标是否处于子view区域内
                if (!canViewReceivePointerEvents(child)
                    || !isTransformedTouchPointInView(x, y, child, null)) {
                    ev.setTargetAccessibilityFocus(false);
                    continue;
                }

                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);
                // (2)进入下一级事件分发
                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();
                    // (3)mFirstTouchTarget赋值
                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                    alreadyDispatchedToNewTouchTarget = true;
                    break;
                }
                ev.setTargetAccessibilityFocus(false);
            }
            if (preorderedList != null) preorderedList.clear();
        }
    }
}

 从上述代码可知,假如ViewGroup没有拦截当前事件且canceled不为真,那么ViewGroup的dispatchTouchEvent接下来会去遍历ViewGroup的所有子View,以寻找哪个子View能够接收该事件。那么如何找到这个子View呢?

 首先,当前遍历到的子View是否具有焦点和是否能够接收点击事件,其中,后者主要根据子View是否正在播放动画点击事件的坐标是否处于子View区域内来确定,如果不满足这两个条件,则进入下一个子View判断。相关源码如下:

扫描二维码关注公众号,回复: 9060935 查看本文章
// ViewGroup.canViewReceivePointerEvents()方法
private static boolean canViewReceivePointerEvents(View child) {
    // View处于可见状态或正在播放动画
    return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
        || child.getAnimation() != null;
}
// ViewGroup.isTransformedTouchPointInView()方法
protected boolean isTransformedTouchPointInView(float x, float y, View child,
                                                PointF outLocalPoint) {
    final float[] point = getTempPoint();
    point[0] = x;
    point[1] = y;
    transformPointToViewLocal(point, child);
    // 坐标x,y是否位于子view区域
    final boolean isInView = child.pointInView(point[0], point[1]);
    if (isInView && outLocalPoint != null) {
        outLocalPoint.set(point[0], point[1]);
    }
    return isInView;
}

 接下来,调用ViewGroup的dispatchTransformedTouchEvent方法处理事件分发。该方法会判断child是否为空,当然走到这里child肯定不为null,因此最终会调用child的dispatchTouchEvent()方法,这样事件就交给子元素(子view)来处理了,从而完成了一轮事件分发。假如这个子view本身是一个ViewGroup,那么接下来的事件分发过程与上述DecorView的一致;假如这个子View是一个View,那么接下来当前事件会交给View的dispatchTouchEvent来处理,这部分我们下一节详讲。当然,如果child.dispatchTouchEvent()返回false,那么将进入下一个子view的判断,直至找到那个能够接收该事件的子view。

//ViewGroup.dispatchTransformedTouchEvent()方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                                              View child, int desiredPointerIdBits) {
    final boolean handled;
    //...
    // 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());
        }
        // 调用child的dispatchTouchEvent分发事件
        // 如果child是ViewGroup,则分发过程跟上面一样
        // 如果child是View,则进入View的dispatchTouchEvent
        handled = child.dispatchTouchEvent(transformedEvent);
    }

    // Done.
    transformedEvent.recycle();
    return handled;
}

 最后,如果ViewGroup.dispatchTransformedTouchEvent()方法返回true,说明事件已经被分发至子View,从而会跳出循环,mFirstTouchTarget将会被赋值。从ViewGroup的addTouchTarget源码可知,mFirstTouchTarget实际上是一个单链表,它能否被赋值将影响ViewGroup对事件拦截的策略,即当mFirstTouchTarget=null时,ViewGroup将拦截同一事件序列的所有点击事件。addTouchTarget()源码如下:

// ViewGroup.addTouchTarget()方法
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
    TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}
  • 最后,判断是否需要ViewGroup处理点击事件。
if (mFirstTouchTarget == null) {
    // No touch targets so treat this as an ordinary view.
    handled = dispatchTransformedTouchEvent(ev, canceled, null,
                                            TouchTarget.ALL_POINTER_IDS);
}

 由前面的分析可知,当mFirstTouchTarget=null的时候,if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign))返回的是false,可能说明两种情况:(1)当前ViewGroup没有子元素(2)所有的子元素处理了当前点击事件,但是dispatchTouchEvent返回false,子元素的这个方法返回false通常是其onTouchEvent返回false,即子元素没有消耗事件。这两种情况,我们从调用dispatchTransformedTouchEvent时,传入child=null可以看出。既然没有任何一个子元素消耗点击事件,那么就只有ViewGroup自身来消耗了,从ViewGroup的dispatchTransformedTouchEvent方法可知,当child=null时,最终会调用View的dispatchTouchEvent方法。为什么是View?因为,ViewGroup本身也是继承于View,View是视图基类。

//ViewGroup.dispatchTransformedTouchEvent()方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                                              View child, int desiredPointerIdBits) {
    final boolean handled;
    //...
    // Perform any necessary transformations and dispatch.
    // 调用view的dispatchTouchEvent方法
    // ViewGroup继承于View
    if (child == null) {
        handled = super.dispatchTouchEvent(transformedEvent);
    } 
    return handled;
}

3. View对事件的分发过程

 View对点击事件的分发过程较为简单,即当前View是否需要点击事件,如果消耗,则返回true,否则,返回false。View的dispatchTouchEvent方法主要处理二件事情:
(1) 判断View是否能够处理当前点击事件的能力;
(2) 判断View是否注册了触摸事件监听器(setOnTouchEventListener)且是否可点击,如果两个条件均满足,则会触发onTouch方法,同时View.dispatchTouchEvent()返回true,而点击事件即被当前View消耗掉。否则,就会触发onTouchEvent()方法来消耗点击事件。

// View.dispatchTouchEvent()部分源码
if (li != null && li.mOnTouchListener != null
		&& (mViewFlags & ENABLED_MASK) == ENABLED
		&& li.mOnTouchListener.onTouch(this, event)) {
	result = true;
}
if (!result && onTouchEvent(event)) {
	result = true;
}

// View.onTouchEvent()方法
public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();
    // 即使view被disable,仍然能够消耗事件
    // 只是没有对点击事件进行反馈而已
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (action == MotionEvent.ACTION_UP && 
            (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        return (((viewFlags & CLICKABLE) == CLICKABLE
                 || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
    }
    // 检测clickable或long_clickable标志
    // 调用OnClickLisenter的onClick方法
    if (((viewFlags & CLICKABLE) == CLICKABLE ||
         (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
        (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    // 请求获取焦点
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }

                    if (prepressed) {
                        setPressed(true, x, y);
                    }

                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        // This is a tap, so remove the longpress check
                        removeLongPressCallback();

                        // Only perform take click 
                        // actions if we were in the pressed state
                        if (!focusTaken) {
                            // Use a Runnable and post this rather than calling
                            // performClick directly. This lets other visual state
                            // of the view update before click actions start.
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }
                    break;
                }

                return true;
        }
        return false;
    }

 在View的onTouchEvent方法中,我们可以看到它首先会判断当前View是否被disable了,如果被disable了,将不再继续下面的逻辑,当然,如果View的clickable或long_clickable是有效的,它照样能够消耗点击事件,只是没有对点击事件进行反馈而已。如果View是enable状态,则先会去检测clickable或long_clickable标志,如果没有被禁用,当前View就会去处理当前事件,onTouchEvent返回true,表示已经消耗了该事件。当点击事件是up事件的时候,说明已经完成了一次同一事件序列,就会触发onClickListener的onClick方法,当然,前提是我们有为View注册点击事件监听器(OnClickListenter)。performClick()的源码如下:

public boolean performClick() {
    final boolean result;
    final ListenerInfo li = mListenerInfo;
    // 判断是否注册了点击事件监听器
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        // 调用onClick方法
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }

    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    return result;
}

 至此,通过对View的onTouchEvent()方法的研究,我们知道View的onTouchEvent是最终用来决定是否处理点击事件的,并且受View的clickable和long_clickable状态的影响,如果它们被禁用,则onTouchEvent会返回false,View不会消耗该点击事件。另外,如果我们注册了触摸事件(OnTouchListener)和点击事件(OnClickListener)监听器的话,消耗事件的顺序是:onTouch->onTouchEvent->onClick。,其中,如果onTouch被回调,onTouchEvent将永远不会被触发,自然onClick也永远不会被回调。View.dispatchTouchEvent()源码如下:

public boolean dispatchTouchEvent(MotionEvent event) {
    // If the event should be handled by accessibility focus first.
    // (1)判断是否具有处理当前点击事件的能力
    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)) {
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        // (2) 判断是否需要onTouch消耗事件
        if (li != null && li.mOnTouchListener != null
            && (mViewFlags & ENABLED_MASK) == ENABLED
            && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
		// (3) 判断是否需要消耗onTouchEvent事件
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
	//....
    return result;
}

(1) View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEnvent方法就会被调用。

(2)View的onTouchEvent默认都会消耗点击事件(返回true),除非它是不可点击的,即clickable和longClickable同时返回false。

(3)View的enbale属性不影响onTouchEvent的默认返回值,即使是disable状态,只要它的clickable或longClickable返回true,那么onTouchEvent就会返回true,即表示消耗该点击事件。

 文章的最后,我们再对View事件分发机制作个小结:在Android中,事件的传递总是会遵循以下顺序:`Activity->Window->DecorView,其中,DecorView是窗体最顶层的实体,我们设计的界面就“装载”在该容器中。事件的分发总是自顶向下,事件的处理消耗总是自低向上,对于同一事件序列来说,当其中的一个事件被拦截了,这个事件序列的剩余事件将不会再继续向下传递给其他视图,都会被拦截的视图来处理。

 对于第二点,我记得某本书上有这么一个例子,就是领导分配任务问题。假设你是最底层码农,你上面有一个项目组长,一个项目经理,一个老总,某天老总安排了一个任务给项目经理(为什么不是直接给你,因为你太渺小了,认不得啊~),那么通常情况项目经理觉得太简单了会转给项目组长,然后项目组长不想做最终转给你,由你来处理这个任务,你做完任务后(直接返回true),再报给项目组长,项目组长报给项目经理,项目经理就交给老总,这样,一次任务的分发和处理就完成了,当然所有功劳都是领导的。假如当你接到任务时,本来就觉得领导总是不认可自己,就直接跟项目组长说,做不了(返回false),那么任务就会又转回给项目组长,项目组长没办法就自己处理了(返回true)。此时,项目组长心里也肯定不爽,所以,当下次有相同的任务派下来时,项目组长就不会再派给你了,要么自己处理(返回true),要么继续向项目经理汇报做不了(返回false),那么一次任务的分发和处理也完成了。

发布了83 篇原创文章 · 获赞 293 · 访问量 30万+

猜你喜欢

转载自blog.csdn.net/AndrExpert/article/details/99647082