Android View 绘制刷新流程分析
Android中对View的更新有很多种方式,使用时要区分不同的应用场合。
1.不使用多线程和双缓冲
这种情况最简单,一般只是希望在View发生改变时对UI进行重绘。你只需显式地调用View对象中的invalidate(){关于invalidate的解释:当调用线程处于空闲状态时,会调用onDraw,刷新界面,也就是说,该函数仅是标记当前界面过期,并不直接负责刷新界面;}方法即可。系统会自动调用View的onDraw()方法。
2.使用多线程但不使用双缓冲
这种情况需要开启新的线程,新开的线程就不好访问View对象了。强行访问的话会报:android.view.ViewRoot$CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views.
这时候你需要创建一个继承了android.os.Handler的子类,并重写handleMessage(Message msg)方法。android.os.Handler是能发送和处理消息的,你需要在Activity中发出更新UI的消息,然后在Handler(可以使用匿名内部类)中处理消息(因为匿名内部类可以访问父类变量, 你可以直接调用View对象中的invalidate()方法 )。也就是说:在新线程创建并发送一个Message,然后再主线程中捕获、处理该消息。
3.使用多线程和双缓冲
Android中SurfaceView是View的子类,她同时也实现了双缓冲。可以定义一个她的子类并实现SurfaceHolder.Callback接口。由于实现SurfaceHolder.Callback接口,新线程就不需要android.os.Handler帮忙了。SurfaceHolder中lockCanvas()方法可以锁定画布,绘制完新的图像后调用unlockCanvasAndPost(canvas)解锁(显示)
先看看源代码对SurfaceHolder接口的描述
/**
* 允许你控制surface view的大小、样式,编辑像素或监视surface的改变,典型的运用于SurfaceView中,需要注意
* lockCanvas方法和Callback.surfaceCreated方法
*/
再看SurfaceHolder.Callback的描述
/**
* A client may implement this interface to receive information about
* changes to the surface. When used with a {@link SurfaceView}, the
* Surface being held is only available between calls to
* {@link #surfaceCreated(SurfaceHolder)} and
* {@link #surfaceDestroyed(SurfaceHolder)}. The Callback is set with
* {@link SurfaceHolder#addCallback SurfaceHolder.addCallback} method.
*/
下面是一个继承自SurfaceView并实现SurfaceHolder.Callback接口的类
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder holder; public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
} public MySurfaceView(Context context) {
super(context);
holder = this.getHolder();
holder.addCallback(this);
this.setLongClickable(true);// 不设置将无法捕捉onFling()事件
setFocusable(true);// 设置键盘焦点
setFocusableInTouchMode(true);// 设置触摸屏焦点
} protected void paintView(Canvas canvas) { // 自定义方法,类似于onDraw
}public void rePaint() { // 自定义类似于invalidate方法,调用此方法刷新View
Canvas c = null;
try {
c = holder.lockCanvas();
paintView(c);
} finally {
if (c != null) {
holder.unlockCanvasAndPost(c);
}
}
} @Override
public void surfaceCreated(SurfaceHolder holder) {
Canvas canvas = holder.lockCanvas(null);// 获取画布
canvas.drawColor(Color.WHITE);// 设置画布背景
holder.unlockCanvasAndPost(canvas);// 解锁画布,提交画好的图像
} @Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
} @Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
}
------------------------------------------------------View的绘制流程-----------------------------------------------------
View的绘制绘制流程:父View负责刷新、布局、显示子View;而当子View需要刷新时,则是通知父View来完成。下面通过查看原代码来验证
1.子类调用invalidate方法()
/**
* 使当前View无效. 如果View可见,onDraw方法将会在之后某个时间点被调用,这个方法的调用必须在UI线程中,如果在非UI线程中调用需要使用postInvalidate()方法*/
public void invalidate() {
invalidate(true);
} /**
* invalidate实际上是调用这个方法.drawing的缓存被设置为无效之后一个完整的invalidate将会发生.但是这个功能可以通过设置invalidateCachefalse来跳过无效的步骤当并不需要重新绘制View的时候(例如,一个组件保持着同样的尺寸和内容)
* @param invalidateCache 这个View的缓存是否应该被设置为无效,通常是false表示要进行全部绘制,但是可能设置为true当View的Content和dimension都没有改变时.
*/
void invalidate(boolean invalidateCache) {
if (skipInvalidate()) {
return;
}
if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) ||
(invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) ||
(mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED || isOpaque() != mLastIsOpaque) {
// ......final AttachInfo ai = mAttachInfo; // 获取匹配
final ViewParent p = mParent; // 获取父类对象
// noinspection PointlessBooleanExpression,ConstantConditions
if (!HardwareRenderer.RENDER_DIRTY_REGIONS) {
if (p != null && ai != null && ai.mHardwareAccelerated) {
p.invalidateChild(this, null);
return;
}
} if (p != null && ai != null) {
final Rect r = ai.mTmpInvalRect;
r.set(0, 0, mRight - mLeft, mBottom - mTop); // 设置View的尺寸
p.invalidateChild(this, r); // 调用parent对象让parent对象重绘制child
}
}
}
>>2.child View调用invalidate时,首先找到自己父View(View的成员变量mParent记录自己的父View),然后将AttachInfo中保存的信息告诉父View刷新自己,父View调用invalidateChild函数刷新child View
下面查看ViewGroup中的invalidateChild方法的实现
/**
* 不要调用或重写此方法,这个方法是用于实现View的绘制层次
*/
public final void invalidateChild(View child, final Rect dirty) {
ViewParent parent = this; final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
// 如果child view绘制的是动画,我们希望child的mPrivateFlags拷贝到ViewGroup之上
// 并且让parent确保无效的请求通过
final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION)
== PFLAG_DRAW_ANIMATION; // ...final int[] location = attachInfo.mInvalidateChildLocation;
location[CHILD_LEFT_INDEX] = child.mLeft;
location[CHILD_TOP_INDEX] = child.mTop;
// ...do {
View view = null;
if (parent instanceof View) {
view = (View) parent;
} if (drawAnimation) {
if (view != null) {
view.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
} else if (parent instanceof ViewRootImpl) {
((ViewRootImpl) parent).mIsAnimating = true;
}
} // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
// flag coming from the child that initiated the invalidate
if (view != null) {
if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
view.getSolidColor() == 0) {
opaqueFlag = PFLAG_DIRTY;
}
if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
}
} parent = parent.invalidateChildInParent(location, dirty); // 转到第三步,调用此方法层层刷新View
if (view != null) {
// Account for transform on current parent
Matrix m = view.getMatrix();
if (!m.isIdentity()) {
RectF boundingRect = attachInfo.mTmpTransformRect;
boundingRect.set(dirty);
m.mapRect(boundingRect);
dirty.set((int) (boundingRect.left - 0.5f),
(int) (boundingRect.top - 0.5f),
(int) (boundingRect.right + 0.5f),
(int) (boundingRect.bottom + 0.5f));
}
}
} while (parent != null);
}
}
3>>.调用invalidateChildInParent函数依次层层刷新
/**
* 这个方法返回null如果ViewGroup已经没有父View了,
* 或者如果这个ViewGrop已经全部被设置为无效,或者当前View的需要刷新的rectangle区域与ViewGroup不相交
*/
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
if ((mPrivateFlags & PFLAG_DRAWN) == PFLAG_DRAWN ||
(mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) {
if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE)) !=
FLAG_OPTIMIZE_INVALIDATE) {
dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
location[CHILD_TOP_INDEX] - mScrollY); // 根据父View的位置,偏移刷新区域 final int left = mLeft;
final int top = mTop; if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) { // 计算实际可刷新区域
dirty.setEmpty();
}
}
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; location[CHILD_LEFT_INDEX] = left;
location[CHILD_TOP_INDEX] = top; if (mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
mLocalDirtyRect.union(dirty);
} return mParent; } else {
mPrivateFlags &= ~PFLAG_DRAWN & ~PFLAG_DRAWING_CACHE_VALID; location[CHILD_LEFT_INDEX] = mLeft;
location[CHILD_TOP_INDEX] = mTop;
if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
} else {
// in case the dirty rect extends outside the bounds of this container
dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
} if (mLayerType != LAYER_TYPE_NONE) {
mPrivateFlags |= PFLAG_INVALIDATED;
mLocalDirtyRect.union(dirty);
}
return mParent;
}
}
return null;
}
Android View 绘制刷新流程分析的更多相关文章
- 简单研究Android View绘制三 布局过程
2015-07-28 17:29:19 这一篇主要看看布局过程 一.布局过程肯定要不可避免的涉及到layout()和onLayout()方法,这两个方法都是定义在View.java中,源码如下: /* ...
- 简单研究Android View绘制一 测量过程
2015-07-27 16:52:58 一.如何通过继承ViewGroup来实现自定义View?首先得搞清楚Android时如何绘制View的,参考Android官方文档:How Android Dr ...
- QtWebkit里RenderLayer树的绘制具体流程分析
更新:RenderLayer树的绘制对RenderObject的绘制.同一时候补足绘制阶段的描写叙述. QtWebkit里,QWebView,QWebPage和QWebFr ...
- Gradle之Android Gradle Plugin 主要流程分析(二)
[Android 修炼手册]Gradle 篇 -- Android Gradle Plugin 主要流程分析 预备知识 理解 gradle 的基本开发 了解 gradle task 和 plugin ...
- Android View绘制流程
框架分析 在之前的下拉刷新中,小结过触屏消息先到WindowManagerService(Wms)然后顺次传递给ViewRoot(派生自Handler),经decor view到Activity再传递 ...
- Android View绘制原理分析
推荐两篇分析view绘制原理比较好的文章,感谢作者的分享. <Android应用层View绘制流程与源码分析> <View 绘制流程>
- Android View 绘制流程(Draw) 完全解析
前言 前几篇文章,笔者分别讲述了DecorView,measure,layout流程等,接下来将详细分析三大工作流程的最后一个流程——绘制流程.测量流程决定了View的大小,布局流程决定了View的位 ...
- Android View 绘制流程
Android 中 Activity 是作为应用程序的载体存在,代表着一个完整的用户界面,提供了一个窗口来绘制各种视图,当 Activity 启动时,我们会通过 setContentView 方法来设 ...
- Android View绘制过程
Android的View绘制是从根节点(Activity是DecorView)开始,他是一个自上而下的过程.View的绘制经历三个过程:Measure.Layout.Draw.基本流程如下图: per ...
随机推荐
- HI-2110的657sp3版本应用笔记之TUP
1. TUP是什么? TUP是华为的搞的一套封装了标准Coap的函数,底层是Coap,上层是华为封装的一层收发函数,用来简化Coap的收发流程,最终只用6个函数搞定,不用懂Coap就可以的. 2. T ...
- tensorflow学习一
1.用图(graph)来表示计算任务 2.用op(opreation)来表示图中的计算节点,图有默认的计算节点,构建图的过程就是在其基础上加节点. 3.用tensor表示每个op的输入输出数据,可以使 ...
- Web应用服务器性能压力测试
压力测试需要关注三个方面:如何正确产生压力.如何定位瓶颈.如何预估系统的承载能力 产生压力的方法 通常可以写脚本产生压力机器人对服务器进行发包和收包操作,也可以使用现有的工具(像jmeter.Load ...
- unable to access android sdk add-on list and SDK 更新镜像设置
前记 国内的网络呀,真是操蛋!!!!!! unable to access android sdk add-on list 在 Android Studio 安装目录 bin/idea.propert ...
- 【转】Bootstrap FileInput中文API整理
Bootstrap FileInput中文API整理 这段时间做项目用到bootstrap fileinput插件上传文件,在用的过程中,网上能查到的api都不是很全,所以想着整理一份比较详细的文档, ...
- 树莓派怎么连接无线网wifi?
没有显示器的同学,想要连接无线网,一定非常苦恼,前面教会了大家远程登录图形界面,下面我将教会大家:在没有图形界面的情况下,怎么连接树莓派WiFi.同样还是利用putty远程访问软件登录,但这次不需要登 ...
- Python基础 之 tuple类-元组 和 dict类-字典
tuple 元组 一.tuple 类的基本属性 1.元组,有序:元素不可被修改,不能被增加或者删除tuple类 tu = (111,22,33,44) 一般写元组的时候,推荐在最后加入,和类方法进行区 ...
- Sharepoint 2013与Sharepoint 2016的功能对比
开发人员功能 SharePoint Foundation 2013 SharePoint Server 2013 Standard CAL SharePoint Server 2013 Enterpr ...
- STM32F4 编程手册学习1_编程模型
STM32F4 programming manual_1 1. 处理器模式与特权等级 处理器模式分为以下两种: 线程模式: 用来执行应用软件: 处理器从reset出来时,进入线程模式: CONTROL ...
- bootstrapValidator.js,最好用的bootstrap表单验证插件 简单实用方法
实用方法 1.引入 在有jquery和bootstrap的页面里引入bootstrapValidator.js和bootstrapValidator.css文件 2. 按照bootstrap的表单组件 ...