最近在看各种Android多线程开发的各种知识,网上看到有关线程和View的东西,其中View的两种更新方法invalidate()和postInvalidate()。记得当时在深圳去面试其中有个题目就是说明这两个的区别。今天突然又看到就需要把不懂得总结出来。
首先不管这两个的使用问题,就从源码看。
先来到android.view.View这个最重要的类。Ctr+F搜索invalidate
这个关键词会有大量的检索到的信息,一直向下,会发现标记到的有注释说明和方法内部调用。如果你留心的话就会发现很多关键信息。
比如:
setEnabled
改变View可选状态的方法,它内部操作其实就是改变内部Flags标志位状态,刷新内部Drawable显示状态DrawableState,然后就调用invalidate(true),所以invalidate肯定是会导致视图重绘的方法。代码如下。 @RemotableViewMethod
public void setEnabled(boolean enabled) {
if (enabled == isEnabled()) return;
setFlags(enabled ? ENABLED : DISABLED, ENABLED_MASK);
/*
* The View most likely has to change its appearance, so refresh
* the drawable state.
*/
refreshDrawableState();
// Invalidate too, since the default behavior for views is to be
// be drawn at 50% alpha rather than to change the drawable.
invalidate(true);
if (!enabled) {
cancelPendingInputEvents();
}
}
同样的还有onFocusChanged()
、setAlpha
、setScrollIndicators()
等也在内部有类似的调用过程,先不管它们真正的操作,光看名字就知道都是改变视图状态的方法。
最后来到这里invalidate(ture)真正的方法。
/**
* This is where the invalidate() work actually happens. A full invalidate()
* causes the drawing cache to be invalidated, but this function can be
* called with invalidateCache set to false to skip that invalidation step
* for cases that do not need it (for example, a component that remains at
* the same dimensions with the same content).
*
* @param invalidateCache Whether the drawing cache for this view should be
* invalidated as well. This is usually true for a full
* invalidate, but may be set to false if the View's contents or
* dimensions have not changed.
*/
void invalidate(boolean invalidateCache) {
invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
先看注释,大概意思就是会使得绘画的缓存(drawing cache)失效,现在没有前后文理解也搞不清楚具体的意思。再看它的参数mRight - mLeft, mBottom - mTop
这就是显示范围的宽高。也是和视图显示相关的。
(坐标原点在屏幕左上角,横为左右值,竖为顶底值)
右-左=当前View的宽显示范围。
底-高=当前View的高显示范围。
总的来说:invalidate会直接在方法内改变View的显示效果与线程没有明显的关系。
引用Android官网API的参考文档
Invalidate the whole view. If the view is visible, onDraw(android.graphics.Canvas) will be called at some point in the future.This must be called from a UI thread. To call from a non-UI thread, call postInvalidate().
大概翻译:如果当前视图是可见的,onDraw(真正的绘制方法)会未来的某个时刻调用,这个方法必须在UI主线程调用,如果不是主线程需要调用postInvalidate方法。
postInvalidate()方法相对就在View源码中就比较少,
我检索到的两个方法
//不带参数
public void postInvalidate() {
postInvalidateDelayed(0);
}
//带视图范围参数
public void postInvalidate(int left, int top, int right, int bottom) {
postInvalidateDelayed(0, left, top, right, bottom);
}
上面两个方法区别只在于是否带视图信息的参数。最后都的逻辑都是一样的。通过绑定的ViewRootImpl对象发送出消息,参数this就是把自己View对象给发送了出去,这很关键。
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
ViewRootImpl就是当我们视图在子线程更新UI向系统发出异常的类,它的工具就是视图顶级管理类负责管理所有的View对象。
异常长这样:android.view.ViewRootImpl$calledfromwrongthreadexception only
the original thread
点进去看ViewRootImpl的源码,已经没什么好说的了,就是一个Handler的发送消息方法。把传过来的View对象和int类型的标志位MSG_INVALIDATE
打包成Message消息对象,发送延时消息。
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
用MSG_INVALIDATE
这个what标志去找,跳转到ViewRootHandler类
//省略很多代码
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break;
//省略很多代码
真相大白!
ViewRootHandler的handleMessage处理消息方法就是把Message消息打包的Object强转回View对象再调用invalidate方法。
转了一大圈postInvalidate最后还是调用了invalidate方法,只是不是在随意线程调用,而是通过ViewRootImpl这个顶级视图检查管理类(The top of a view hierarchy)去负责分发轮询处理,然后在主线程调用View方法,实现View控件的线程安全。
引用Android官网API的参考文档
Cause an invalidate to happen on a subsequent cycle through the event loop. Use this to invalidate the View from a non-UI thread.
This method can be invoked from outside of the UI thread only when this View is attached to a window.
大概翻译:通过周期性的轮询使得能够在非UI线程调用得到invalidate方法。