cvmachine.com - 申博开户网

查找: 您的方位主页 > 手机频道 > 阅览资讯:Android作业分发机制完全解析,带你从源码的视点完全了解(上)

Android作业分发机制完全解析,带你从源码的视点完全了解(上)

2019-03-29 15:49:24 来历:www.cvmachine.com 【

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/9097463

 

其实我一向预备写一篇关于Android作业分发机制的文章,从我的榜首篇博客开端,就零零散散在许多当地运用到了Android作业分发的常识。也有许多朋友问过我各种问题,比方:onTouch和onTouchEvent有什么区别,又该怎么运用?为什么给ListView引入了一个滑动菜单的功用,ListView就不能翻滚了?为什么图片轮播器里的图片运用Button而不必ImageView?等等……关于这些问题,我并没有给出十分具体的答复,由于我知道假如想要完全搞了解这些问题,把握Android作业分发机制是必不行少的,而Android作业分发机制必定不是片言只语就能说得清的。

 

在我经过较长时刻的预备之后,总算决议开端写这样一篇文章了。现在尽管网上相关的文章也不少,但我觉得没有哪篇写得特别具体的(或许我还没有找到),大都文章仅仅讲了讲理论,然后合作demo运转了一下成果。而我预备带着咱们从源码的视点进行剖析,信任咱们能够愈加深刻地了解Android作业分发机制。

 

阅览源码考究由浅入深,按部就班,因而咱们也从简略的开端,本篇先带咱们探求View的作业分发,下篇再去探求难度更高的ViewGroup的作业分发。

 

那咱们现在就开端吧!比方说你其时有一个十分简略的项目,只需一个Activity,而且Activity中只需一个按钮。你或许现已知道,假如想要给这个按钮注册一个点击作业,只需求调用:

button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Log.d("TAG", "onClick execute");
}
});

这样在onClick办法里边写完成,就能够在按钮被点击的时分履行。你或许也现已知道,假如想给这个按钮再添加一个touch作业,只需求调用:

button.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "onTouch execute, action " + event.getAction());
return false;
}
});

onTouch办法里能做的作业比onClick要多一些,比方判别手指按下、抬起、移动等作业。那么假如我两个作业都注册了,哪一个会先履行呢?咱们来试一下就知道了,运转程序点击按钮,打印成果如下:

 

Android作业分发机制完全解析,带你从源码的视点完全了解(上)

 

能够看到,onTouch是优先于onClick履行的,而且onTouch履行了两次,一次是ACTION_DOWN,一次是ACTION_UP(你还或许会有屡次ACTION_MOVE的履行,假如你手抖了一下)。因而作业传递的次序是先经过onTouch,再传递到onClick。

 

仔细的朋友应该能够留意到,onTouch办法是有回来值的,这儿咱们回来的是false,假如咱们测验把onTouch办法里的回来值改成true,再运转一次,成果如下:

 

Android作业分发机制完全解析,带你从源码的视点完全了解(上)

 

咱们发现,onClick办法不再履行了!为什么会这样呢?你能够先了解成onTouch办法回来true就以为这个作业被onTouch消费掉了,因而不会再持续向下传递。

 

假如到现在为止,以上的一切常识点你都是清楚的,那么阐明你对Android作业传递的根本用法应该是把握了。不过别满足于现状,让咱们从源码的视点剖析一下,呈现上述现象的原理是什么。

 

首要你需求知道一点,只需你接触到了任何一个控件,就必定会调用该控件的dispatchTouchEvent办法。那当咱们去点击按钮的时分,就会去调用Button类里的dispatchTouchEvent办法,但是你会发现Button类里并没有这个办法,那么就到它的父类TextView里去找一找,你会发现TextView里也没有这个办法,那没办法了,只好持续在TextView的父类View里找一找,这个时分你总算在View里找到了这个办法,示意图如下:

 

Android作业分发机制完全解析,带你从源码的视点完全了解(上)

 

然后咱们来看一下View中dispatchTouchEvent办法的源码:

public boolean dispatchTouchEvent(MotionEvent event) {
  if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
      mOnTouchListener.onTouch(this, event)) {
    return true;
  }
  return onTouchEvent(event);
}

这个办法十分的简练,只需短短几行代码!咱们能够看到,在这个办法内,首要是进行了一个判别,假如mOnTouchListener != null,(mViewFlags & ENABLED_MASK) == ENABLED和mOnTouchListener.onTouch(this, event)这三个条件都为真,就回来true,不然就去履行onTouchEvent(event)办法并回来。

 

先看一下榜首个条件,mOnTouchListener这个变量是在哪里赋值的呢?咱们寻觅之后在View里发现了如下办法:

public void setOnTouchListener(OnTouchListener l) {
  mOnTouchListener = l;
}

Bingo!找到了,mOnTouchListener正是在setOnTouchListener办法里赋值的,也便是说只需咱们给控件注册了touch作业,mOnTouchListener就必定被赋值了。

 

第二个条件(mViewFlags & ENABLED_MASK) == ENABLED是判别其时点击的控件是否是enable的,按钮默许都是enable的,因而这个条件恒定为true。

 

第三个条件就比较要害了,mOnTouchListener.onTouch(this, event),其实也便是去回调控件注册touch作业时的onTouch办法。也便是说假如咱们在onTouch办法里回来true,就会让这三个条件悉数建立,然后整个办法直接回来true。假如咱们在onTouch办法里回来false,就会再去履行onTouchEvent(event)办法。

 

现在咱们能够结合前面的比如来剖析一下了,首要在dispatchTouchEvent中最早履行的便是onTouch办法,因而onTouch必定是要优先于onClick履行的,也是印证了刚刚的打印成果。而假如在onTouch办法里回来了true,就会让dispatchTouchEvent办法直接回来true,不会再持续往下履行。而打印成果也证明了假如onTouch回来true,onClick就不会再履行了。

 

依据以上源码的剖析,从原理上解说了咱们前面比如的运转成果。而上面的剖析还透漏出了一个重要的信息,那便是onClick的调用必定是在onTouchEvent(event)办法中的!那咱们马上来看下onTouchEvent的源码,如下所示:

public boolean onTouchEvent(MotionEvent event) {
  final int viewFlags = mViewFlags;
  if ((viewFlags & ENABLED_MASK) == DISABLED) {
    // A disabled view that is clickable still consumes the touch
    // events, it just doesn't respond to them.
    return (((viewFlags & CLICKABLE) == CLICKABLE ||
        (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
  }
  if (mTouchDelegate != null) {
    if (mTouchDelegate.onTouchEvent(event)) {
      return true;
    }
  }
  if (((viewFlags & CLICKABLE) == CLICKABLE ||
      (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
    switch (event.getAction()) {
      case MotionEvent.ACTION_UP:
        boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
        if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
          // take focus if we don't have it already and we should in
          // touch mode.
          boolean focusTaken = false;
          if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
            focusTaken = requestFocus();
          }
          if (!mHasPerformedLongPress) {
            // 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();
              }
            }
          }
          if (mUnsetPressedState == null) {
            mUnsetPressedState = new UnsetPressedState();
          }
          if (prepressed) {
            mPrivateFlags |= PRESSED;
            refreshDrawableState();
            postDelayed(mUnsetPressedState,
                ViewConfiguration.getPressedStateDuration());
          } else if (!post(mUnsetPressedState)) {
            // If the post failed, unpress right now
            mUnsetPressedState.run();
          }
          removeTapCallback();
        }
        break;
      case MotionEvent.ACTION_DOWN:
        if (mPendingCheckForTap == null) {
          mPendingCheckForTap = new CheckForTap();
        }
        mPrivateFlags |= PREPRESSED;
        mHasPerformedLongPress = false;
        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
        break;
      case MotionEvent.ACTION_CANCEL:
        mPrivateFlags &= ~PRESSED;
        refreshDrawableState();
        removeTapCallback();
        break;
      case MotionEvent.ACTION_MOVE:
        final int x = (int) event.getX();
        final int y = (int) event.getY();
        // Be lenient about moving outside of buttons
        int slop = mTouchSlop;
        if ((x < 0 - slop) || (x >= getWidth() + slop) ||
            (y < 0 - slop) || (y >= getHeight() + slop)) {
          // Outside button
          removeTapCallback();
          if ((mPrivateFlags & PRESSED) != 0) {
            // Remove any future long press/tap checks
            removeLongPressCallback();
            // Need to switch from pressed to not pressed
            mPrivateFlags &= ~PRESSED;
            refreshDrawableState();
          }
        }
        break;
    }
    return true;
  }
  return false;
}

相较于方才的dispatchTouchEvent办法,onTouchEvent办法杂乱了许多,不过不要紧,咱们只挑要点看就能够了。

 

首要在第14行咱们能够看出,假如该控件是能够点击的就会进入到第16行的switch判别中去,而假如其时的作业是抬起手指,则会进入到MotionEvent.ACTION_UP这个case傍边。在经过种种判别之后,会履行到第38行的performClick()办法,那咱们进入到这个办法里瞧一瞧:

public boolean performClick() {
  sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
  if (mOnClickListener != null) {
    playSoundEffect(SoundEffectConstants.CLICK);
    mOnClickListener.onClick(this);
    return true;
  }
  return false;
}

能够看到,只需mOnClickListener不是null,就会去调用它的onClick办法,那mOnClickListener又是在哪里赋值的呢?经过寻觅后找到如下办法:

public void setOnClickListener(OnClickListener l) {
  if (!isClickable()) {
    setClickable(true);
  }
  mOnClickListener = l;
}

一切都是那么清楚了!当咱们经过调用setOnClickListener办法来给控件注册一个点击作业时,就会给mOnClickListener赋值。然后每逢控件被点击时,都会在performClick()办法里回调被点击控件的onClick办法。

 

这样View的整个作业分发的流程就让咱们搞清楚了!不过别快乐的太早,现在还没完毕,还有一个很重要的常识点需求阐明,便是touch作业的层级传递。咱们都知道假如给一个控件注册了touch作业,每次点击它的时分都会触发一系列的ACTION_DOWN,ACTION_MOVE,ACTION_UP等作业。这儿需求留意,假如你在履行ACTION_DOWN的时分回来了false,后边一系列其它的action就不会再得到履行了。简略的说,便是当dispatchTouchEvent在进行作业分发的时分,只需前一个action回来true,才会触发后一个action。

 

说到这儿,许多的朋友必定要有巨大的疑问了。这不是在自相矛盾吗?前面的比如中,分明在onTouch作业里边回来了false,ACTION_DOWN和ACTION_UP不是都得到履行了吗?其实你仅仅被假象所利诱了,让咱们仔细剖析一下,在前面的比如傍边,咱们究竟回来的是什么。

参考着咱们前面剖析的源码,首要在onTouch作业里回来了false,就必定会进入到onTouchEvent办法中,然后咱们来看一下onTouchEvent办法的细节。由于咱们点击了按钮,就会进入到第14行这个if判别的内部,然后你会发现,不论其时的action是什么,终究都必定会走到第89行,回来一个true。

 

是不是有一种被诈骗的感觉?分明在onTouch作业里回来了false,体系仍是在onTouchEvent办法中帮你回来了true。就由于这个原因,才使得前面的比如中ACTION_UP能够得到履行。

 

那咱们能够换一个控件,将按钮替换成ImageView,然后给它也注册一个touch作业,并回来false。如下所示:

imageView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.d("TAG", "onTouch execute, action " + event.getAction());
return false;
}
});

运转一下程序,点击ImageView,你会发现成果如下:

 

Android作业分发机制完全解析,带你从源码的视点完全了解(上)

 

在ACTION_DOWN履行完后,后边的一系列action都不会得到履行了。这又是为什么呢?由于ImageView和按钮不同,它是默许不行点击的,因而在onTouchEvent的第14行判别时无法进入到if的内部,直接跳到第91行回来了false,也就导致后边其它的action都无法履行了。

 

好了,关于View的作业分发,我想讲的东西全都在这儿了。现在咱们再来回忆一下开篇时说到的那三个问题,信任每个人都会有更深一层的了解。

 

1. onTouch和onTouchEvent有什么区别,又该怎么运用?

从源码中能够看出,这两个办法都是在View的dispatchTouchEvent中调用的,onTouch优先于onTouchEvent履行。假如在onTouch办法中经过回来true将作业消费掉,onTouchEvent将不会再履行。

 

别的需求留意的是,onTouch能够得到履行需求两个前提条件,榜首mOnTouchListener的值不能为空,第二其时点击的控件有必要是enable的。因而假如你有一个控件对错enable的,那么给它注册onTouch作业将永久得不到履行。关于这一类控件,假如咱们想要监听它的touch作业,就有必要经过在该控件中重写onTouchEvent办法来完成。

 

2. 为什么给ListView引入了一个滑动菜单的功用,ListView就不能翻滚了?

假如你阅览了Android滑动结构完全解析,教你怎么一分钟完成滑动菜单特效这篇文章,你应该会知道滑动菜单的功用是经过给ListView注册了一个touch作业来完成的。假如你在onTouch办法里处理完了滑动逻辑后回来true,那么ListView自身的翻滚作业就被屏蔽了,天然也就无法滑动(原理同前面比如中按钮不能点击),因而解决办法便是在onTouch办法里回来false。

 

3. 为什么图片轮播器里的图片运用Button而不必ImageView?

提这个问题的朋友是看过了Android完成图片翻滚控件,含页签功用,让你的运用像淘宝相同炫起来 这篇文章。其时我在图片轮播器里运用Button,首要便是由于Button是可点击的,而ImageView是不行点击的。假如想要运用ImageView,能够有两种改法。榜首,在ImageView的onTouch办法里回来true,这样能够确保ACTION_DOWN之后的其它action都能得到履行,才干完成图片翻滚的作用。第二,在布局文件里边给ImageView添加一个android:clickable="true"的特点,这样ImageView变成可点击的之后,即便在onTouch里回来了false,ACTION_DOWN之后的其它action也是能够得到履行的。

 

今日的解说就到这儿了,信任咱们现在对Android作业分发机制又有了进一步的知道,在后边的文章中我会再带咱们一同探求Android中ViewGroup的作业分发机制,感兴趣的朋友请持续阅览 Android作业分发机制完全解析,带你从源码的视点完全了解(下)

 

重视我的技能大众号,每天都有优质技能文章推送。重视我的文娱大众号,作业、学习累了的时分放松一下自己。

微信扫一扫下方二维码即可重视:

Android作业分发机制完全解析,带你从源码的视点完全了解(上) Android作业分发机制完全解析,带你从源码的视点完全了解(上)

 
 

本文地址:http://www.cvmachine.com/a/luyou/99888.html
Tags: Android 机制 作业
修改:申博开户网
关于咱们 | 联络咱们 | 友情链接 | 网站地图 | Sitemap | App | 回来顶部