- // Sets the flag as early as possible to allow draw() implementations
- // to call invalidate() successfully when doing animations
- child.mPrivateFlags |= DRAWN;
- ......
- child.computeScroll();
- final int sx = child.mScrollX;
- final int sy = child.mScrollY;
- boolean scalingRequired = false;
- Bitmap cache = null;
- if ((flags & FLAG_CHILDREN_DRAWN_WITH_CACHE) == FLAG_CHILDREN_DRAWN_WITH_CACHE ||
- (flags & FLAG_ALWAYS_DRAWN_WITH_CACHE) == FLAG_ALWAYS_DRAWN_WITH_CACHE) {
- cache = child.getDrawingCache(true);
- if (mAttachInfo != null) scalingRequired = mAttachInfo.mScalingRequired;
- }
- final boolean hasNoCache = cache == null;
1. 将子视图child的标志值mPrivateFlags的DRAWN设置为1,因为接下来它就会被绘制了。
2. 计算子视图child的滚动位置,这是通过调用子视图child的成员函数computeScroll来实现的。计算好子视图child的滚动位置之后,我们就可以通过它的成员变量mScrollX和mScrollY来获得它在X轴和Y轴上的偏移了。这两个偏移值保存在变量sx和sy中。
3. 检查变量flags的FLAG_CHILDREN_DRAWN_WITH_CACHE位或者FLAG_ALWAYS_DRAWN_WITH_CACHE位是否等于1。如果其中的一个等于1的话,那么就说明子视图是使用缓冲方式来绘制,即它的UI来缓冲在一个Bitmap里面,通过调用子视图child的成员函数getDrawingCache就可以获得这个Bitmap,并且保存在变量cache中。同时,通过ViewGroup类的成员变量mAttachInfo所指向的一个AttachInfo对象的成员变量mScalingRequired还可以知道子视图是否要进行缩放。如果需要缩放,那么变量scalingRequired的值就会等于true。另外,如果子视图child有一个缓冲的Bitmap,那么变量hasNoCache的值就会等于false。
我们继续往下阅读代码:
- final int restoreTo = canvas.save();
- if (hasNoCache) {
- canvas.translate(cl - sx, ct - sy);
- } else {
- canvas.translate(cl, ct);
- if (scalingRequired) {
- // mAttachInfo cannot be null, otherwise scalingRequired == false
- final float scale = 1.0f / mAttachInfo.mApplicationScale;
- canvas.scale(scale, scale);
- }
- }
- float alpha = 1.0f;
- if (transformToApply != null) {
- ......
- alpha = transformToApply.getAlpha();
- ......
- }
- ......
- if ((flags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
- if (hasNoCache) {
- canvas.clipRect(sx, sy, sx + (cr - cl), sy + (cb - ct));
- } else {
- if (!scalingRequired) {
- canvas.clipRect(0, 0, cr - cl, cb - ct);
- } else {
- canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
- }
- }
- }
1. 如果子视图child不是以缓冲的方式来绘制的,那么它的偏移值就需要同时考虑它在X轴和Y轴上的起始位置(cl, ct)以及滚动值sx、sy;如果子视图child是以缓冲的方式来绘制的,那么就不需要考虑它在X轴和Y轴上的滚动位置sx和sy,而只需要考虑它在X轴和Y轴上的起始位置(cl, ct),这是因为它所缓冲的Bitmap已经包含了滚动信息。注意,在子视图child是以缓冲的方式来绘制的情况中,如果变量scalingRequired的值等于true,那么这段代码同时还需要为子视图child设置一个缩放因子,以便接下来可以同步缓冲的Bitmap的所表示的大小。
2. 如果子视图child此时被设置了一个变换矩阵transformToApply,那么一般它就会有一个Alpha值。例如,前面提到,当子视图child还处理动画显示的状态时,它就会有一个变换矩阵,而这个动画一般会有透明的效果,因此,就会有一个Alpha值。通过调用变量transformToApply所指向的一个Transformation对象的成员函数getAlpha就可以获得子视图child的Alpha值,保存在变量alpha中。如果子视图child此时没有被设置变换矩阵的话,那么它的Alpha值alpha就会等于1.0,表示不是透明的。
3. 如果变量flags的FLAG_CLIP_CHILDREN位等于1,那么就说明需要为子视图child设置一个剪裁区域。在设置子视图child的剪裁区域时,同样是需要考虑子视图child是否使用缓冲方式来绘制。如果不使用缓冲方式来会绘制,那么子视图child的剪裁区域就需要同时考虑它在X轴和Y轴上的起始位置(cl, ct)以及滚动值sx、sy;如果不是使用缓冲方式来会绘制,那么就不需要考虑它在X轴和Y轴上的滚动位置sx和sy。注意,在子视图child是以缓冲的方式来绘制的情况中,如果变量scalingRequired的值等于false,那么它的剪裁区域的宽度和高度就分别为(cr - cl)和(cb - ct),否则的话,它的剪裁区域的宽度和高度就等于上一次的缓冲Bitmap的宽度和宽度,这是因为对于后者来说,前面在设置子视图child的偏移时,已经同时设置过它的缩放因子了。
注意,在子视图child的偏移、Alpha通道以及裁剪区域之前,这段代码首先会保存在参数canvas所描述的一块画布的堆栈状态,以便在绘制完成子视图child的UI之后,可以恢复这块画布的堆栈状态来绘制其它子视图的UI。保存在参数canvas所描述的一块画布的堆栈状态是通过调用参数canvas所指向的一个Canvas对象的成员函数save来实现的。
我们继续往下阅读代码:
- if (hasNoCache) {
- // Fast path for layouts with no backgrounds
- if ((child.mPrivateFlags & SKIP_DRAW) == SKIP_DRAW) {
- ......
- child.dispatchDraw(canvas);
- } else {
- child.draw(canvas);
- }
- } else {
- final Paint cachePaint = mCachePaint;
- if (alpha < 1.0f) {
- cachePaint.setAlpha((int) (alpha * 255));
- mGroupFlags |= FLAG_ALPHA_LOWER_THAN_ONE;
- } else if ((flags & FLAG_ALPHA_LOWER_THAN_ONE) == FLAG_ALPHA_LOWER_THAN_ONE) {
- cachePaint.setAlpha(255);
- mGroupFlags &= ~FLAG_ALPHA_LOWER_THAN_ONE;
- }
- ......
- canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
- }
1. 以非缓冲的方式来绘制。这时候需要检查子视图child的标志值mPrivateFlags的SKIP_DRAW位是否等于1。如果等于1的话,那么就说明需要跳过子视图child的绘制,但是需要绘制子视图child的子视图,否则的话,就需要先绘制子视图child的UI,再绘制它的子视图的UI。绘制子视图child的子视图是通过调用它的成员函数dispatchDraw来实现的,而绘制子视图child本身及其子视图是通过调用它的成员函数draw来实现的。
2. 以缓冲的方式来绘制。这时候只需要将上一次的缓冲的Bitmap对象cache绘制到参数canvas所描述的一块画布上面就行了。在绘制之前,需要先设置用来绘制Bitmap对象cache的一个Paint对象的Alpha值,这个Paint对象保存在ViewGroup类的成员变量mCachePaint中。这个Alpha值保存在变量alpha中,如果它的值小于1.0,那么就说明子视图child有一个透明值,因此,就需要将它设置到ViewGroup类的成员变量mCachePaint所指向的一个Paint对象中去,并且将ViewGroup类的成员变量mGroupFlags的FLAG_ALPHA_LOWER_THAN_ONE位设置为1。另一方面,如果变量alpha的值大于等于1.0,那么就说明不需要设置子视图child的透明值,但是如果之前设置过子视图child的透明值,即ViewGroup类的成员变量mGroupFlags的FLAG_ALPHA_LOWER_THAN_ONE位等于1,那么还需要修改ViewGroup类的成员变量mCachePaint所指向的一个Paint对象是不透明的,即将它的透明值设置为255,并且将ViewGroup类的成员变量mGroupFlags的FLAG_ALPHA_LOWER_THAN_ONE位重置为0。最后,就可以调用参数canvas所指向的一个Canvas对象的成员函数drawBitmap来绘制子视图child的UI了。
我们继续往下阅读最后一段代码:
- canvas.restoreToCount(restoreTo);
- ......
- return more;
- }
- ......
ViewGroup类的成员函数drawChild最后就将变量more的值返回给调用者了,以便调用者可以知道当前正在绘制的子视图child是否还处于动画状态中。
从上面的分析就可以知道,当子视图child不是以非缓冲模式来绘制,并且它需要绘制自己及其子视图时,它的成员函数draw就会被调用,这时候就会重复执行Step 8、Step 9和Step 10,直到所有子孙视图都绘制完成为止。
这一步执行完成之后,应用程序窗口的UI就全部绘制到在前面的Step 2中所获得的一块画布上面去了,返回到前面的Step 1中,即ViewRoot类的成员函数draw中,接下来就会Java层的Surface类的成员函数unlockCanvasAndPost来请求SurfaceFlinger服务渲染这块画布里面所包含的一个图形缓冲区了。
接下来,我们就继续分析Java层的Surface类的成员函数unlockCanvasAndPost的实现。
Step 11. Surface.unlockCanvasAndPost
- public class Surface implements Parcelable {
- ......
- /** unlock the surface and asks a page flip */
- public native void unlockCanvasAndPost(Canvas canvas);
- ......
- }
Surface类的成员函数unlockCanvasAndPost是一个JNI方法,它是由C++层的函数Surface_unlockCanvasAndPost来实现的,如下所示:
- static void Surface_unlockCanvasAndPost(
- JNIEnv* env, jobject clazz, jobject argCanvas)
- {
- jobject canvas = env->GetObjectField(clazz, so.canvas);
- if (canvas != argCanvas) {
- doThrow(env, "java/lang/IllegalArgumentException", NULL);
- return;
- }
- const sp& surface(getSurface(env, clazz));
- if (!Surface::isValid(surface))
- return;
- // detach the canvas from the surface
- SkCanvas* nativeCanvas = (SkCanvas*)env->GetIntField(canvas, no.native_canvas);
- int saveCount = env->GetIntField(clazz, so.saveCount);
- nativeCanvas->restoreToCount(saveCount);
- nativeCanvas->setBitmapDevice(SkBitmap());
- env->SetIntField(clazz, so.saveCount, 0);
- // unlock surface
- status_t err = surface->unlockAndPost();
- if (err < 0) {
- doThrow(env, "java/lang/IllegalArgumentException", NULL);
- }
- }
这个函数定义在文件frameworks/base/core/jni/android_view_Surface.cpp中。
参数clazz指向的是一个Java层的Surface对象,用来描述当前正在绘制的应用程序窗口的绘图表面,而参数argCanvas指向的是一个Java层的Canvas对象。
参数clazz指向的是一个Java层的Surface对象的成员变量mCanvas所指向的一个Canvas对象canvas应当等于参数argCanvas指向的是一个Java层的Canvas对象,否则的话,函数就会抛出一个异常。
从前面Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析一文可以知道,每一个Java层的Surface对象在C++层都有一个对应的Surface对象。因此,函数t就可以通过调用另外一个函数getSurface来获得与参数clazz所指向的是一个Java层的Surface对象所对应的C++层的Surface对象surface。C++层的Surface类的静态成员函数isValid用来验证Surface对象surface是否已经连接到SurfaceFlinger服务中。如果还没有连接到,那么函数就会出错返回了。
通过了上述的合法性检查之后,函数接下来主要就是做两件事情:
1. 恢复变量canvas所描述的一块画布的堆栈状态。变量canvas所描述的一块画布是在前面的Step 2中开始初始化的,每次应用程序窗口在上面绘制完成UI之后,我们都应该恢复它的堆栈状态,以便下一次使用时不会受上一次影响。变量canvas指向的是一个Java层的Canvas对象,函数首先找到与它所对应的一个C++层的SkCanvas对象nativeCanvas,然后就可以调用这个SkCanvas对象nativeCanvas的成员函数restoreToCount来恢复它所描述的一块画布的堆栈状态了。这块画布在绘制应用程序窗口UI前的堆栈状态保存在参数clazz所指向的一个Java层的Surface对象的成员变量mSaveCount中。因此,函数就先获得参数clazz所指向的一个Java层的Surface对象的成员变量mSaveCount的值,然后再以它为参数来调用SkCanvas对象nativeCanvas的成员函数restoreToCount,这样就可以恢复画布的堆栈状态了。
2. 请求SurfaceFlinger服务渲染Surface对象surface所描述的应用程序窗口的绘图表面。应用程序窗口的UI是绘制在SkCanvas对象nativeCanvas所描述的一块画布上的,而这块画布所使用的图形缓冲区是保存在Surface对象surface的内部的,因此,函数就调用Surface对象surface的成员函数unlockAndPost来请求SurfaceFlinger服务渲染这块图形缓冲区。
接下来,我们就继续分析C++层的Surface类的成员函数unlockAndPost的实现,以便可以了解用来绘制应用程序窗口UI的图形缓冲区是如何渲染的。
Step 12. Surface.unlockAndPost
- status_t Surface::unlockAndPost()
- {
- if (mLockedBuffer == 0) {
- LOGE("Surface::unlockAndPost failed, no locked buffer");
- return INVALID_OPERATION;
- }
- status_t err = mLockedBuffer->unlock();
- LOGE_IF(err, "failed unlocking buffer (%p)", mLockedBuffer->handle);
- err = queueBuffer(mLockedBuffer.get());
- LOGE_IF(err, "queueBuffer (idx=%d) failed (%s)",
- getBufferIndex(mLockedBuffer), strerror(-err));
- mPostedBuffer = mLockedBuffer;
- mLockedBuffer = 0;
- return err;
- }
从前面的Step 3可以知道,应用程序窗口当前正在使用的图形缓冲区保存在Surface类的成员变量mLockedBuffer中,因此,Surface类的成员函数unlockAndPost的目标就是要将它交给SurfaceFlinger服务来渲染,这是通过调用另外一个成员函数queueBuffer来实现的。在前面Android应用程序请求SurfaceFlinger服务渲染Surface的过程分析一文中,我们已经分析过Surface类的成员函数queueBuffer的实现了,它主要就是向应用程序窗口的待渲染图形缓冲区队列中添加一个图形缓冲区,然后再请请求SurfaceFlinger服务来渲染这个图形缓冲区。
在渲染成员变量mLockedBuffer所描述的一个图形缓冲区之前,Surface类的成员函数unlockAndPost还会调用它的成员函数unlock来执行一个“解锁”操作。从前面的Step 3可以知道,成员变量mLockedBuffer所描述的一个图形缓冲区在交给应用程序窗口使用之前,它会被执行一个“锁定”的操作,即它的成员函数lock会被调用,因此,这里执行的“解锁”操作是与前面的“锁定”操作相对应的。事实上,对成员变量mLockedBuffer所描述的一个图形缓冲区进行锁定,主要是为了获得这个图形缓冲区的地址,是否真的要对个图形缓冲区进行锁定,是由HAL层模块Gralloc的实现来决定的。
在请求SurfaceFlinger服务渲染了成员变量mLockedBuffer所描述的一个图形缓冲区之后,Surface类的成员函数unlockAndPost还会把成员变量mLockedBuffer所描述的一个图形缓冲区保存在另外一个成员变量mPostedBuffer中,表示这个图形缓冲区已经变成是正在渲染的图形缓冲区了,或者说是前端缓冲区了。
最后,Surface类的成员函数unlockAndPost就把成员变量mLockedBuffer的值设置为0,这样就可以将应用程序窗口下一次请求分配和使用的图形缓冲区保存在它里面。
Surface类的成员变量mLockedBuffer指向的是一个GraphicBuffer对象,接下来我们就继续分析它的成员函数unlock的实现,以便可以了解它所描述的图形缓冲区的“解锁”过程。
Step 13. GraphicBuffer.unlock
- status_t GraphicBuffer::unlock()
- {
- status_t res = getBufferMapper().unlock(handle);
- return res;
- }
从前面的Step 4可以知道,GraphicBuffer类的成员变量handle用来描述当前正在处理的图形缓冲区的句柄,而GraphicBuffer类的成员函数getBufferMapper返回的是一个GraphicBufferMapper对象。有了这个GraphicBufferMapper对象之后,就可以调用它的成员函数unlock解锁成员变量handle所描述的一个图形缓冲区了。
接下来,我们就继续分析GraphicBufferMapper类的成员函数unlock的实现。
Step 14. GraphicBufferMapper.unlock
- status_t GraphicBufferMapper::unlock(buffer_handle_t handle)
- {
- status_t err;
- if (sw_gralloc_handle_t::validate(handle) < 0) {
- err = mAllocMod->unlock(mAllocMod, handle);
- } else {
- err = sw_gralloc_handle_t::unlock((sw_gralloc_handle_t*)handle);
- }
- LOGW_IF(err, "unlock(...) failed %d (%s)", err, strerror(-err));
- return err;
- }
从前面的Step 5可以知道,参数handle所描述的图形缓冲区是在HAL模块Gralloc中分配的,这个HAL模块Gralloc是由GraphicBufferMapper类的成员变量mAllocMod来描述的,因此,函数就终就会调用GraphicBufferMapper类的成员变量mAllocMod所描述的一个HAL模块Gralloc的成员函数unlock来解锁参数andle所描述的一个图形缓冲区。HAL模块Gralloc的成员函数unlock的实现可以参考前面Android帧缓冲区(Frame Buffer)硬件抽象层(HAL)模块Gralloc的实现原理分析一文,这里不再详述。
至此,我们就分析完成Android应用程序窗口的渲染过程了,从中就可以看出:
1. 渲染Android应用程序窗口UI需要经过三步曲:测量、布局、绘制。
2. Android应用程序窗口UI首先是使用Skia图形库API来绘制在一块画布上,实际地是绘制在这块画布里面的一个图形缓冲区中,这个图形缓冲区最终会被交给SurfaceFlinger服务,而SurfaceFlinger服务再使用OpenGL图形库API来将这个图形缓冲区渲染到硬件帧缓冲区中。
Android应用程序窗口的渲染过程分析完成之后,Android应用程序窗口的实现框架就分析完成了,重新学习请回到Android应用程序窗口(Activity)实现框架简要介绍和学习计划一文中。
在Android应用程序窗口(Activity)实现框架简要介绍和学习计划这一系列文章中,我们主要是从单个应用程序窗口的角度来分析的。但是,Android系统在运行的过程中,需要管理的是一系列的应用程序窗口,并且这些应用程序窗口的类型可能各不相同,并且相互影响。因此,Android的窗口管理系统是非常复杂的。在接下来的一个系列的文章中,我们就将详细地分析Android窗口管理服务WindowManagerService的实现,以便可以从系统的角度来分析应用程序窗口的实现。敬请关注!
全文转自:http://blog.csdn.net/luoshengyang/article/details/8372924
- 相关文章
- Android应用程序窗口(Activity)的测量(Measure)、布局(L (2人浏览)
- Android应用程序窗口(Activity)的测量(Measure)、布局(L (4人浏览)
- Android应用程序窗口(Activity)的测量(Measure)、布局(L (1人浏览)
- Android应用程序窗口(Activity)的视图对象(View)的创建过程分 (2人浏览)
- Android应用程序窗口(Activity)的窗口对象(Window)的创建过 (0人浏览)
- Android应用程序窗口(Activity)的运行上下文环境(Context) (0人浏览)
- Android应用程序窗口(Activity)实现框架简要介绍和学习计划 (0人浏览)