View 解析

一.View和Activity,Window之间的关系:

在构造Activity的时候呢,会初始化一个Window .(PhoneWindow),而PhoneWindow设置DecorView,作为窗口的根视图(Viewroot),分为TitleView和 ContextView。在OnCreated()方法中,setContextView()用来设置上面的方法,而findVIewbyid则是至上而 下的遍历这棵控件树(我们都知道findViewbyid这个方法是十分消耗内存的,对于性能的优化可以从这里入手考虑)。Viewroot通过 addview方法加入将Viwe添加进来(Textview,button,ect.), 而这些View的事件监听,是由WindowManagerService来接受消息,并且回调Activity函数。

二.View的更新

Android更新view有两组方法,invalidate在UI线程中使用,postInvalidate在非线程中使用。
postInvalidate源码:

public void postInvalidate() {
    postInvalidateDelayed(0);
}

public void postInvalidateDelayed(long delayMilliseconds) {
    // We try only with the AttachInfo because there's no point in invalidating
    // if we are not attached to our window
    if (mAttachInfo != null) {
        Message msg = Message.obtain();
        msg.what = AttachInfo.INVALIDATE_MSG;
        msg.obj = this;
        mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds);
    }
}

可见,还是用过handle.sendMessageDelayed,最终交给了主线程UI处理的。
这个时候在说一下checkThread()方法:

void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

可见如果mThread !=Thread.currentThread就会抛出异常。而mThread是在构造方法中实例化的,为主线程,而Thread.currentThread()是当前UI。该代码出自 framework/base/core/java/android/view/ViewRootImpl.java
还需要一提的是invalidatechilid()和invalidatechildparent()方法:

@Override
public void invalidateChild(View child, Rect dirty) {
        invalidateChildInParent(null, dirty);
    }
@Override
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
                  ...
        if (dirty == null) {
            invalidate();
            return null;
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }
                  ...
    }

方法中调用了checkThread(),还有关于ui的更新。

既然是单线程模型,就要先找到这个UI线程实现类ActivityThread,看里面哪里 addview 了。没错,是在onResume里面,对应ActivityThread就是handleResumeActivity这个方法:

<pre>final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { // If we are getting ready to gc after going to the background, well // we are back active so skip it.
 unscheduleGcIdler();
        mSomeActivitiesChanged = true; // TODO Push resumeArgs into the activity for consideration
        ActivityClientRecord r = performResumeActivity(token, clearHide);
        ...... if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit; if (a.mVisibleFromClient) {
                a.mWindowAdded = true;
                wm.addView(decor, l);
            } // If the window has already been added, but during resume // we started another activity, then don't yet make the // window visible.
        } else if (!willBeVisible) { if (localLOGV) Slog.v(
                TAG, "Launch " + r + " mStartedActivity set");
            r.hideForNow = true;
        }
    ......
}

所以,如果在onCreate中通过子线程直接更新UI,并不会抛CalledFromWrongThreadException异常。但是一般情况下,我们不会在onCreate中做这样的事情。

  • 这就是Android为我们设计的单线程模型,核心就是一句话:Android UI操作并不是线程安全的,并且这些操作必须在UI线程执行。

三.事件拦截:

上文提到过view的创建流程,android的事件拦截主要是三个方法:
1.dispatchtouchevent():进行事件分发,只要事件传递给当前view,此方法就一定会被调用。
2.onInterceptTouchEvent( ): 标记是否拦截Touch事件,如果拦截则自己处理,否则交给子View处理(可以是ViewGroup,也可以是View)。
3.onTouchEvent(): 在拦截之后进行事件的处理。
上一段开发艺术中的伪代码帮助理解:

public void ondispatchTouchevent(Motionevent ev){
  boolean consume = false;
  if(onInterceptTouchEvent(ev)){
    consume = onTouchEvent(ev);
  }else{
    consume = child.dispatchTouchEvent(ev);
  }
  return consume;
}

这里提一下mFirstTouchTarget:即为事件处理ViewmFirstTouchTarget != null的情况下才会调用onInterceptTouchEvent(),所以当事件如果为Move并且mFirstTouchTarget == null 的情况下(ViewGroup没有处理当前事件的子View)就直接intercepted = true,表明自己拦截。
通过判断x,y来确定子view是否包含,如果包含就添加到mFirstTouchTarget中,否则继续遍历。
在拿一段view dispatchTouchEvent( )的关键部分:

if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            //onTouchEvent()
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容