View类的成员变量mPrivateFlags的DRAWN位描述的是当前视图上一次请求的UI绘制操作是否已经执行过了。如果它的值等于1,就表示已经执行过了,否则的话,就表示还没在等待执行。前面第一次调用View类的成员函数invalidate来检查当前视图上次请求的UI绘制操作是否已经执行时,如果发现已经执行了,那么就会重新请求执行一次新的UI绘制操作,这时候会导致当前视图的成员变量mPrivateFlags的DRAWN位重置为0。注意,新请求执行的UI绘制只是为了在修改当前视图的大小以及大小之前,先将它在上一次设置的大小以及位置中绘制出来,这样就可以使得当前视图的大小以及位置出现平滑的变换。换句话说,新请求执行的UI绘制只是为了获得一个中间效果,它不应该影响当前视图的绘制状态,即不可以修改当前视图的成员变量mPrivateFlags的DRAWN位。因此,我们就需要在前面第一次调用View类的成员函数invalidate前,先将当前视图的成员变量mPrivateFlags的DRAWN位保存下来,即保存在变量drawn中,然后等到调用之后,再将变量drawn的值恢复到当前视图的成员变量mPrivateFlags的DRAWN位中去。
另一方面,如果当前视图的大小和位置发生了变化,View类的成员函数setFrame还会将成员变量mBackgroundSizeChanged的值设置为true,以便可以表示当前视图的背景大小发生了变化。
最后,View类的成员函数setFrame将变量changed的值返回给调用者,以便调用者可以知道当前视图的大小和位置是否发生了变化。
接下来,我们继续分析View类的成员函数invalidate的实现,以便可以了解当前视图是如何执行一次UI绘制操作的。
Step 3. View.invalidate
- public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
- ......
- protected ViewParent mParent;
- ......
- int mPrivateFlags;
- ......
- public void invalidate() {
- ......
- if ((mPrivateFlags & (DRAWN | HAS_BOUNDS)) == (DRAWN | HAS_BOUNDS)) {
- mPrivateFlags &= ~DRAWN & ~DRAWING_CACHE_VALID;
- final ViewParent p = mParent;
- final AttachInfo ai = mAttachInfo;
- if (p != null && ai != null) {
- final Rect r = ai.mTmpInvalRect;
- r.set(0, 0, mRight - mLeft, mBottom - mTop);
- // Don't call invalidate -- we don't want to internally scroll
- // our own bounds
- p.invalidateChild(this, r);
- }
- }
- }
- ......
- }
View类的成员函数invalidate首先检查成员变量mPrivateFlags的DRAWN位和HAS_BOUNDS位是否都被设置为1。如果是的话,那么就说明当前视图上一次请求执行的UI绘制操作已经执行完成了,这时候View类的成员函数invalidate才可以请求执行新的UI绘制操作。
View类的成员函数invalidate在请求新的UI绘制操作之前,会将成员变量mPrivateFlags的DRAWN位和DRAWING_CACHE_VALID位重置为0,其中,后者表示当前视图正在缓存的一些绘图对象已经失效了,这是因为接下来就要重新开始绘制当前视图的UI了。
请求绘制当前视图的UI是通过调用View类的成员变量mParent所描述的一个ViewParent接口的成员函数invalidateChild来实现的。前面我们假设当前视图是应用程序窗口的顶层视图,即它是一个类型为DecoreView的视图,它的成员变量mParent指向的是与其所关联的一个ViewRoot对象。因此,绘制当前视图的UI的操作实际上是通过调用ViewRoot类的成员函数invalidateChild来实现的。
注意,在调用ViewRoot类的成员函数invalidateChild的成员函数invalidateChild来绘制当前视图的UI之前,会将当前视图即将要绘制的区域记录在View类的成员变量mAttachInfo所描述的一个AttachInfo对象的成员变量mTmpInvalRect中。
接下来,我们就继续分析ViewRoot类的成员函数invalidateChild的实现。
Step 4. ViewRoot.invalidateChild
- public final class ViewRoot extends Handler implements ViewParent,
- View.AttachInfo.Callbacks {
- ......
- public void invalidateChild(View child, Rect dirty) {
- checkThread();
- ......
- if (mCurScrollY != 0 || mTranslator != null) {
- mTempRect.set(dirty);
- dirty = mTempRect;
- if (mCurScrollY != 0) {
- dirty.offset(0, -mCurScrollY);
- }
- if (mTranslator != null) {
- mTranslator.translateRectInAppWindowToScreen(dirty);
- }
- if (mAttachInfo.mScalingRequired) {
- dirty.inset(-1, -1);
- }
- }
- mDirty.union(dirty);
- if (!mWillDrawSoon) {
- scheduleTraversals();
- }
- }
- ......
- }
ViewRoot类的成员函数invalidateChild首先调用另外一个成员函数checkThread来检查当前正在执行的是否是一个UI线程。如果不是的话,ViewRoot类的成员函数checkThread就会抛出一个异常出来。这是因为所有的UI操作都必须要在UI线程中执行。
ViewRoot类的成员函数invalidateChild接下来还会检查当前正在处理的应用程序窗口在Y轴上是否出现有滚动条,即成员变量mCurScrollY的值不等于0, 或者前正在处理的应用程序窗口是否运行在兼容模式之下,即成员变量mTranslator的值不等于null。当一个应用程序窗口运行在兼容模式时,它显示出来的大小和它实际被设置的大小是不一样的,要经过相应的转换处理。对于上述这两种情况,ViewRoot类的成员函数invalidateChild都需要调整参数dirty所描述的一个需要重新绘制的区域的大小和位置。
调整好参数dirty所描述的一个需要重新绘制的区域之后, ViewRoot类的成员函数invalidateChild就将它所描述的一个区域与成员变量mDirty所描述的一区域执行一个合并操作,并且将得到的新区域保存在成员变量mDirty中。从这个操作就可以看出,ViewRoot类的成员变量mDirty描述的就是当前正在处理的应用程序窗口下一次所要重新绘制的总区域。
设置好当前正在处理的应用程序窗口下一次所要重新绘制的总区域之后,ViewRoot类的成员函数invalidateChild最后就检查成员变量mWillDrawSoon的值是否不等于true。如果ViewRoot类的成员mWillDrawSoon的值等于true的话,那么就说明UI线程的消息队列中已经有一个DO_TRAVERSAL消息在等待执行了,这时候就不需要调用ViewRoot类的成员函数scheduleTraversals来向UI线程的消息队列发送一个DO_TRAVERSAL消息了,否则的话,就需要调用ViewRoot类的成员函数scheduleTraversals来向UI线程的消息队列发送一个DO_TRAVERSAL消息。
ViewRoot类的成员函数scheduleTraversals在前面Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析一文中已经分析过了,这里不再详述。
这一步执行完成之后,返回到前面的Step 1中,即View类的成员函数layout中,接下来它就会调用另外一个成员函数onLayout来重新布局当前视图的子视图的布局了。View类的成员函数onLayout是由子类来重写的,并且只有当该子类描述的是一个容器视图时,它才会重写父类View的成员函数onLayout。前面我们已经假设当前正在处理的是应用程序窗口的顶层视图,它的类型为DecorView,并且它描述的是一个容器视图,因此,接下来我们就会继续分析DecorView类的成员函数onLayout的实现。
事实上,DecorView类是通过FrameLayout类来间接继承View类的,并且它的成员函数onLayout是从FrameLayout类继承下来的,因此,接下来我们实际上要分析的是FrameLayout类的成员函数onLayout的实现。
Step 5. FrameLayout.onLayout
- public class FrameLayout extends ViewGroup {
- ......
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- final int count = getChildCount();
- final int parentLeft = mPaddingLeft + mForegroundPaddingLeft;
- final int parentRight = right - left - mPaddingRight - mForegroundPaddingRight;
- final int parentTop = mPaddingTop + mForegroundPaddingTop;
- final int parentBottom = bottom - top - mPaddingBottom - mForegroundPaddingBottom;
- mForegroundBoundsChanged = true;
- for (int i = 0; i < count; i++) {
- final View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- final LayoutParams lp = (LayoutParams) child.getLayoutParams();
- final int width = child.getMeasuredWidth();
- final int height = child.getMeasuredHeight();
- int childLeft = parentLeft;
- int childTop = parentTop;
- final int gravity = lp.gravity;
- if (gravity != -1) {
- final int horizontalGravity = gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
- final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
- switch (horizontalGravity) {
- case Gravity.LEFT:
- childLeft = parentLeft + lp.leftMargin;
- break;
- case Gravity.CENTER_HORIZONTAL:
- childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
- lp.leftMargin - lp.rightMargin;
- break;
- case Gravity.RIGHT:
- childLeft = parentRight - width - lp.rightMargin;
- break;
- default:
- childLeft = parentLeft + lp.leftMargin;
- }
- switch (verticalGravity) {
- case Gravity.TOP:
- childTop = parentTop + lp.topMargin;
- break;
- case Gravity.CENTER_VERTICAL:
- childTop = parentTop + (parentBottom - parentTop - height) / 2 +
- lp.topMargin - lp.bottomMargin;
- break;
- case Gravity.BOTTOM:
- childTop = parentBottom - height - lp.bottomMargin;
- break;
- default:
- childTop = parentTop + lp.topMargin;
- }
- }
- child.layout(childLeft, childTop, childLeft + width, childTop + height);
- }
- }
- }
- ......
- }
FrameLayout类的成员变量mPaddingLeft、mPaddingRight、mPaddingTop、mPaddingBottom和mForegroundPaddingLeft、mForegroundPaddingRight、mForegroundPaddingTop、mForegroundPaddingBottom的含义我们在前面分析Android应用程序窗品的测量过程时已经解释过了,它们描述的是当前视图的内边距,而参数left、top、right和bottom描述的是当前视图的外边距,即它与父窗口的边距。通过上述这些参数,我们就可以得到当前视图的子视图所能布局在的区域。
FrameLayout类的成员函数onLayout通过一个for循环来布局当前视图的每一个子视图。如果一个子视图child是可见的,那么FrameLayout类的成员函数onLayout就会根据当前视图可以用来显示子视图的区域以及它所设置的gravity属性来得到它在应用程序窗口中的左上角位置(childeLeft,childTop)。
当一个子视图child在应用程序窗口中的左上角位置确定了之后,再结合它在前面的测量过程中所确定的宽度width和高度height,我们就可以完全地确定它在应用程序窗口中的布局了,即可以调用它的成员函数layout来设置它的位置和大小了,这刚好就是前面的Step 1所执行的操作。注意,如果当前正在布局的子视图child描述的也是一个视图容器,那么它又会重复执行Step 5的操作,直到它的所有子孙视图都布局完成为止。
至此,我们就分析完成Android应用程序窗口的布局过程了,接下来我们继续分析Android应用程序窗口的绘制过程。
3. Android应用程序窗口的绘制过程
ViewRoot类的成员函数draw首先会创建一块画布,接着再在画布上绘制Android应用程序窗口的UI,最后再将画布的内容交给SurfaceFlinger服务来渲染,这个过程如图4所示:
图4 Android应用程序窗口的绘制过程
这个过程可以分为14个步骤,接下来我们就详细分析每一个步骤。
Step 1. ViewRoot.draw
- public final class ViewRoot extends Handler implements ViewParent,
- View.AttachInfo.Callbacks {
- ......
- private void draw(boolean fullRedrawNeeded) {
- Surface surface = mSurface;
- ......
- int yoff;
- final boolean scrolling = mScroller != null && mScroller.computeScrollOffset();
- if (scrolling) {
- yoff = mScroller.getCurrY();
- } else {
- yoff = mScrollY;
- }
- if (mCurScrollY != yoff) {
- mCurScrollY = yoff;
- fullRedrawNeeded = true;
- }
- float appScale = mAttachInfo.mApplicationScale;
- boolean scalingRequired = mAttachInfo.mScalingRequired;
- Rect dirty = mDirty;
- ......
- if (mUseGL) {
- if (!dirty.isEmpty()) {
- Canvas canvas = mGlCanvas;
- if (mGL != null && canvas != null) {
- ......
- int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
- try {
- canvas.translate(0, -yoff);
- if (mTranslator != null) {
- mTranslator.translateCanvas(canvas);
- }
- canvas.setScreenDensity(scalingRequired
- ? DisplayMetrics.DENSITY_DEVICE : 0);
- mView.draw(canvas);
- ......
- } finally {
- canvas.restoreToCount(saveCount);
- }
- ......
- }
- }
- if (scrolling) {
- mFullRedrawNeeded = true;
- scheduleTraversals();
- }
- return;
- }
- if (fullRedrawNeeded) {
- ......
- dirty.union(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
- }
- ......
- if (!dirty.isEmpty() || mIsAnimating) {
- Canvas canvas;
- try {
- ......
- canvas = surface.lockCanvas(dirty);
- ......
- } catch (Surface.OutOfResourcesException e) {
- ......
- return;
- } catch (IllegalArgumentException e) {
- ......
- return;
- }
- try {
- if (!dirty.isEmpty() || mIsAnimating) {
- .....
- mView.mPrivateFlags |= View.DRAWN;
- ......
- int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG);
- try {
- canvas.translate(0, -yoff);
- if (mTranslator != null) {
- mTranslator.translateCanvas(canvas);
- }
- canvas.setScreenDensity(scalingRequired
- ? DisplayMetrics.DENSITY_DEVICE : 0);
- mView.draw(canvas);
- } finally {
- ......
- canvas.restoreToCount(saveCount);
- }
- ......
- }
- } finally {
- surface.unlockCanvasAndPost(canvas);
- }
- }
- ......
- if (scrolling) {
- mFullRedrawNeeded = true;
- scheduleTraversals();
- }
- }
- ......
- }
ViewRoot类的成员函数draw的执行流程如下所示:
1. 将成员变量mSurface所描述的应用程序窗口的绘图表面保存在变量surface中,以便接下来可以通过变量surface来操作应用程序窗口的绘图表面。
2. 调用成员变量mScroller所描述的一个Scroller对象的成员函数computeScrollOffset来计算应用程序窗口是否处于正在滚动的状态中。如果是的话,那么得到的变量scrolling就会等于true,这时候调用成员变量mScroller所描述的一个Scroller对象的成员函数getCurrY就可以得到应用程序窗口在Y轴上的即时滚动位置yoff。
3. 成员变量mScrollY用来描述应用程序窗口下一次绘制时在Y轴上应该滚动到的位置,因此,如果应用程序窗口不是处于正在滚动的状态,那么它在下一次绘制时,就应该直接将它在Y轴上的即时滚动位置yoff设置为mScrollY。
4. 成员变量mCurScrollY用来描述应用程序窗口上一次绘制时在Y轴上的滚动位置,如果它的值不等变量yoff的值,那么就表示应用程序窗口在Y轴上的滚动位置发生变化了,这时候就需要将变量yoff的值保存在成员变量mCurScrollY中,并且将参数fullRedrawNeeded的设置为true,表示要重新绘制应用程序窗口的所有区域。
5. 成员变量mAttachInfo所描述的一个AttachInfo对象的成员变量mScalingRequired表示应用程序窗口是否正在请求进行大小缩放,如果是的话,那么所请求的大小缩放因子就保存在这个AttachInfo对象的另外一个成员变量mApplicationScale中。函数将这两个值保存在变量scalingRequired和appScale中,以便接下来可以使用。
6. 成员变量mDirty描述的是一个矩形区域,表示应用程序窗口的脏区域,即需要重新绘制的区域。函数将这个脏区域保存变量dirty中,以便接下来可以使用。
7. 成员变量mUseGL用来描述应用程序窗口是否直接使用OpenGL接口来绘制UI。当应用程序窗口的绘图表面的内存类型等于WindowManager.LayoutParams.MEMORY_TYPE_GPU时,那么就表示它需要使用OpenGL接口来绘制UI,以便可以利用GPU来绘制UI。当应用程序窗口需要直接使用OpenGL接口来绘制UI时,另外一个成员变量mGlCanvas就表示应用程序窗口的绘图表面所使用的画布,这块画布同样是通过OpenGL接口来创建的。
8. 当应用程序窗口需要直接使用OpenGL接口来绘制UI时,函数接下来就会将它的UI绘制在成员变量mGlCanvas所描述的一块画布上,这是通过调用成员变量mView所描述的一个类型为DecorView的顶层视图的成员函数draw来实现的。注意,在绘制之前,还需要对画布进行适当的转换:A. 设置画布在Y轴上的偏移值yoff,以便可以正确反映应用程序窗口的滚动状态;B. 如果成员变量mTranslator的值不等于null,即它指向了一个Translator对象,那么就说明应用程序窗口运行在兼容模式下,这时候就需要相应对画布进行变换,以便可以正确反映应用程序窗口的大小;C. 当变量scalingRequired的值等于true时,同样说明应用程序窗口是运行在兼容模式下,这时候就需要修改画布在兼容模式下的点密度,以便可以正确地反映应用程序窗口的分辨率,注意,这时候屏幕在兼容模式下的点密度保存在DisplayMetrics类的静态成员变量DENSITY_DEVICE中。由于上述画布的转换操作只针对当前的这一次绘制操作有效,因此,函数就需要在绘制之后,调用画布的成员函数save来保存它在转换前的矩阵变换堆栈状态,以便在绘制完成之后,可以调用画布的成员函数restoreToCount来恢复之前的矩阵变换堆栈状态。
9. 使用OpenGL接口来绘制完成UI后,如果变量scrolling的值等于true,即应用程序窗口是处于正在滚动的状态,那么就意味着应用程序窗口接下来还需要马上进行下一次重绘,而且是所有的区域都需要重绘,因此,函数接下来就会将成员变量mFullRedrawNeeded的值设置为true,并且调用另外一个成员函数scheduleTraversals来请求执行下一次的重绘操作。
10. 以下的步骤针适用于使用非OpenGL接口来绘制UI的情况,也是本文所要关注的重点。
11. 参数fullRedrawNeeded用来描述是否需要绘制应用程序窗口的所有区域。如果需要的话,那么就会将应用程序窗口的脏区域的大小设置为整个应用程序窗口的大小(0,0,mWidth,mHeight),其中,成员变量mWidth和mHeight表示应用程序窗口的宽度和高度。注意,如果应用程序窗口的大小被设置了一个缩放因子,即变量appScale的值不等于1,那么就需要将应用程序窗口的宽度mWidth和高度mHeight乘以这个缩放因子,然后才可以得到应用程序窗口的实际大小。
12. 经过前面的一系列计算之后,如果应用程序窗口的脏区域dirty不等于空,或者应用程序窗口在正处于动画状态,即成员变量mIsAnimating的值等于true,那么函数接下来就需要重新绘制应用程序窗口的UI了。在绘制之前,首先会调用用来描述应用程序窗口的绘图表面的一个Surface对象surface的成员函数lockCanvas来创建一块画布canvas。有了这块画布之后,接下来就可以调用成员变量mView所描述的一个类型为DecorView的顶层视图的成员函数draw来在上面绘制应用程序窗口的UI了。 与前面的第8步一样,在绘制之前,还需要对画布进行适当的A、B和C转换,以及需要在绘制之后恢复画布在绘制之前的矩阵变换堆栈状态。
13. 绘制完成之后,应用程序窗口的UI就都体现在前面所创建的画布canvas上了,因此,这时候就需要将它交给SurfaceFlinger服务来渲染,这是通过调用用来描述应用程序窗口的绘图表面的一个Surface对象surface的成员函数unlockCanvasAndPost来实现的。
14. 在请求SurfaceFlinger服务渲染应用程序窗口的UI之后,函数同样是需要判断变量scrolling的值是否等于true。如果等于的话,那么就与前面的第9步一样,函数需要将成员变量mFullRedrawNeeded的值设置为true,并且调用另外一个成员函数scheduleTraversals来请求执行下一次的重绘操作。
在本文中,我们只关注使用非OpenGL接口来绘制应用程序窗口的UI的步骤,其中,第12步和第13步是关键所在。第12步调用了Java层的Surface类的成员函数lockCanvas来为应用程序窗口的绘图表面创建了一块画布,并且调用了DecorView类的成员函数draw来在这块画布上绘制了应用程序窗口的UI,而第13步调用了Java层的Surface类的成员函数unlockCanvasAndPost来将前面已经绘制了应用程序窗口UI的画布交给SurfaceFlinger服务来渲染。接下来,我们就分别分析Java层的Surface类的成员函数lockCanvas、DecorView类的成员函数draw和Java层的Surface类的成员函数unlockCanvasAndPost的实现。
Step 2. Surface.lockCanvas
- public class Surface implements Parcelable {
- ......
- public Canvas lockCanvas(Rect dirty) throws OutOfResourcesException, IllegalArgumentException
- {
- /* the dirty rectangle may be expanded to the surface's size, if
- * for instance it has been resized or if the bits were lost, since
- * the last call.
- */
- return lockCanvasNative(dirty);
- }
- private native Canvas lockCanvasNative(Rect dirty);
- ......
- }
这个函数定义在文件frameworks/base/core/java/android/view/Surface.java中。
Surface类的成员函数lockCanvas调用另外一个成员函数lockCanvasNative来创建一块画布。Surface类的成员函数lockCanvasNative是一个JNI方法,它是由C++层的函数Surface_lockCanvas来实现的,如下所示:
- static jobject Surface_lockCanvas(JNIEnv* env, jobject clazz, jobject dirtyRect)
- {
- const sp<Surface>& surface(getSurface(env, clazz));
- ......
- // get dirty region
- Region dirtyRegion;
- if (dirtyRect) {
- Rect dirty;
- dirty.left = env->GetIntField(dirtyRect, ro.l);
- dirty.top = env->GetIntField(dirtyRect, ro.t);
- dirty.right = env->GetIntField(dirtyRect, ro.r);
- dirty.bottom= env->GetIntField(dirtyRect, ro.b);
- if (!dirty.isEmpty()) {
- dirtyRegion.set(dirty);
- }
- }
- ......
- Surface::SurfaceInfo info;
- status_t err = surface->lock(&info, &dirtyRegion);
- ......
- // Associate a SkCanvas object to this surface
- jobject canvas = env->GetObjectField(clazz, so.canvas);
- ......
- SkCanvas* nativeCanvas = (SkCanvas*)env->GetIntField(canvas, no.native_canvas);
- SkBitmap bitmap;
- ......
- if (info.w > 0 && info.h > 0) {
- bitmap.setPixels(info.bits);
- } else {
- // be safe with an empty bitmap.
- bitmap.setPixels(NULL);
- }
- ......
- SkRegion clipReg;
- if (dirtyRegion.isRect()) { // very common case
- const Rect b(dirtyRegion.getBounds());
- clipReg.setRect(b.left, b.top, b.right, b.bottom);
- } else {
- size_t count;
- Rect const* r = dirtyRegion.getArray(&count);
- while (count) {
- clipReg.op(r->left, r->top, r->right, r->bottom, SkRegion::kUnion_Op);
- r++, count--;
- }
- }
- nativeCanvas->clipRegion(clipReg);
- int saveCount = nativeCanvas->save();
- env->SetIntField(clazz, so.saveCount, saveCount);
- if (dirtyRect) {
- const Rect& bounds(dirtyRegion.getBounds());
- env->SetIntField(dirtyRect, ro.l, bounds.left);
- env->SetIntField(dirtyRect, ro.t, bounds.top);
- env->SetIntField(dirtyRect, ro.r, bounds.right);
- env->SetIntField(dirtyRect, ro.b, bounds.bottom);
- }
- return canvas;
- }
参数clazz指向的是一个Java层的Surface对象。从前面Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析一文可以知道,每一个Java层的Surface对象在C++层都对应有一个Surface对象。因此,函数首先调用另外一个函数getSurface来获得与参数clazz所对应的C++层的Surface对象surface。
参数dirtyRect指向的是一个Java层的Rect对象,它描述的是应用程序窗口即将要重绘的一块矩形区域,函数接下来就将它所描述的矩形区域转换成一个C++层的Region对象dirtyRegion来表示。
函数接下来就调用前面所获得的C++层的Surface对象surface的成员函数lock来获得一个图形缓冲区,这个图形缓冲区使用一个SurfaceInfo对象info来描述,其中,图形缓冲区的地址就保存在它的成员变量bits中。
获得图形缓冲区之后,我们就可以在上面绘制应用程序窗口的UI了。由于Java层的应用程序窗口是通Skia图形库来绘制应用程序窗口的UI的,而Skia图形库在绘制UI时,是需要一块画布的,因此,函数接下来就会将前面所获得的图形缓冲区封装在一块画布中。
从前面Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析一文还可以知道,每一个Java层的Surface对象内部都有一块画布,这块画布是通过它的成员变量mCanvas所指向的一个Java层的CompatibleCanvas对象来描述的。so是一个类型为so_t的结构体,它的成员变量canvas描述的是Java层的Surface类的成员变量mCanva在类中的偏移量,因此,通过这个偏移量就可以获得参数clazz所指向的一个Java层的Surface对象的内部的一块类型为CompatibleCanvas的画布canvas。
画布canvas的类型为Java层的CompatibleCanvas,它是从Canvas类继承下来的。Canvas类有一个成员变量mNativeCanvas,它指向的是一个C++层的SkCanvas对象,这个C++层的SkCanvas对象描述的就是Skia图形库绘制应用程序窗口UI时所需要的画布。no是一个类型为no_t的结构体,它的成员变量native_canvas描述的是Java层的Canvas类的成员变量mNativeCanvas在类中的偏移量,因此,通过这个偏移量就可以获得变量canvas所指向的一个Java层的CompatibleCanvas对象的内部的一块类型为SkCanvas的画布nativeCanvas。
获得了Skia图形库所需要的画布nativeCanvas之后,函数就可以将前面所获得的图形缓冲区的地址,即SurfaceInfo对象info的成员变量bits封装到它内部去了,这是通过调用它的成员函数setPixels来实现的。
函数接下来还会设置画布nativeCanvas的裁剪区域。这个裁剪区域是通过Region对象dirtyRegion来描述的,不过Skia图形库需要使用另外一个类型为SkRegion的对象clipReg来描述它。Region对象dirtyRegion所描述的区域有可能是一个矩形区域,也可能是一个不规则的区域。如果Region对象dirtyRegion描述的是一个矩形区域,那么就可以直接将这个矩形区域设置到SkRegion的对象clipReg里面去。如果Region对象dirtyRegion描述的是一个不规则区域,那么这个不规则区域就是由一系列的矩形小区域来描述的,这时候就将这些矩形小区域合并起来,并且设置到kRegion的对象clipReg里面去。
设置好SkRegion的对象clipReg所包含的区域之后,函数就可以调用前面得到的SkCanvas画布nativeCanvas的成员函数clipRegion来将它设置为自己的裁剪区域了,接下来函数还会将该裁剪区域所围成的一个矩形区域的位置和大小设置到参数dirtyRect所描述的一个Java层的Rect对象中去,以便调用者可以知道现在正在创建的画布的大小。
函数在将与C++层的SkCanvas画布nativeCanvas所关联的一个Java层的CompatibleCanvas画布canvas返回给调用者之前,还会将画布的当前堆栈状态保存下来,以便在绘制完成应用程序窗口的UI之后,可以恢复回来,这是通过调用C++层的SkCanvas画布nativeCanvas的成员函数save来实现的。画布的当前堆栈状态是通过一个整数来描述的,这个整数即为C++层的SkCanvas画布nativeCanvas的成员函数save的返回值saveCount,它会被保存在参数clazz所描述的一个Java层的Surface对象的成员变量mSaveCount中,等到应用程序窗口的UI绘制完成之后,就可以通过这个整数来恢复画布的堆栈状态了。
接下来,我们继续分析C++层的Surface类的成员函数lock的实现,以便可以了解用来创建绘制应用程序窗口UI所需要的画布的图形缓冲区是如何获得的。
Step 3. Surface.lock
- status_t Surface::lock(SurfaceInfo* other, Region* dirtyIn, bool blocking)
- {
- ......
- if (mApiLock.tryLock() != NO_ERROR) {
- ......
- return WOULD_BLOCK;
- }
- /* Here we're holding mApiLock */
- if (mLockedBuffer != 0) {
- ......
- mApiLock.unlock();
- return INVALID_OPERATION;
- }
- // we're intending to do software rendering from this point
- setUsage(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
- android_native_buffer_t* out;
- status_t err = dequeueBuffer(&out);
- ......
- if (err == NO_ERROR) {
- sp<GraphicBuffer> backBuffer(GraphicBuffer::getSelf(out));
- err = lockBuffer(backBuffer.get());
- ......
- if (err == NO_ERROR) {
- const Rect bounds(backBuffer->width, backBuffer->height);
- const Region boundsRegion(bounds);
- Region scratch(boundsRegion);
- Region& newDirtyRegion(dirtyIn ? *dirtyIn : scratch);
- newDirtyRegion &= boundsRegion;
- // figure out if we can copy the frontbuffer back
- const sp<GraphicBuffer>& frontBuffer(mPostedBuffer);
- const bool canCopyBack = (frontBuffer != 0 &&
- backBuffer->width == frontBuffer->width &&
- backBuffer->height == frontBuffer->height &&
- backBuffer->format == frontBuffer->format &&
- !(mFlags & ISurfaceComposer::eDestroyBackbuffer));
- ......
- if (canCopyBack) {
- // copy the area that is invalid and not repainted this round
- const Region copyback(mOldDirtyRegion.subtract(newDirtyRegion));
- if (!copyback.isEmpty())
- copyBlt(backBuffer, frontBuffer, copyback);
- } else {
- // if we can't copy-back anything, modify the user's dirty
- // region to make sure they redraw the whole buffer
- newDirtyRegion = boundsRegion;
- }
- ......
- void* vaddr;
- status_t res = backBuffer->lock(
- GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
- newDirtyRegion.bounds(), &vaddr);
- ......
- mLockedBuffer = backBuffer;
- other->w = backBuffer->width;
- other->h = backBuffer->height;
- other->s = backBuffer->stride;
- other->usage = backBuffer->usage;
- other->format = backBuffer->format;
- other->bits = vaddr;
- }
- }
- mApiLock.unlock();
- return err;
- }
Surface类的成员变量mApiLock是一个类型为Mutex的互斥锁,它是用来保证Surface类的成员函数lock是线程安全的。如果调用Surface类的成员变量mApiLock所描述的一个Mutex对象的成员函数tryLock的返回值不等于NO_ERROR,那么就说明这个Mutex对象已经被另外一个线程获得了,因此,这时候函数就直接返回一个错误码WOULD_BLOCK给调用者了。
Surface类的成员变量mLockedBuffer的类型为GraphicBuffer,如果它的值不等于0,那么它指向的就是应用程序窗口当前正在使用的图形缓冲区。如果应用程序窗口正在使用一个图形缓冲区,那么它是不可以再请求分配另一个图形缓冲区的,因此,当Surface类的成员变量mLockedBuffer的值不等于0时,函数就直接返回一个错误码INVALID_OPERATION给调用者了。
通过了前面的检查之后,Surface类的成员函数lock接下来就开始要分配一个图形缓冲区了,不过在分配之后,首先调用另外一个成员函数setUsage来将当前正在处理的Surface对象所描述的应用程序窗口的绘图表面的属性设置为(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN),表示该应用程序窗口的UI是需要通过软件方式来渲染的,这是相对于使用GPU来渲染而言的。
Surface类的成员函数lock接下来就调用另外一个成员函数dequeueBuffer来获得一个新的图形缓冲区了,这个新的图形缓冲区使用一个android_native_buffer_t对象out来描述的。在前面Android应用程序请求SurfaceFlinger服务渲染Surface的过程分析一文中,我们已经分析过Surface类的成员函数dequeueBuffer的实现了,它主要就是请求SurfaceFlinger服务来分配一个图形缓冲区。
前面获得的android_native_buffer_t对象out接下来又会被封装成一个GraphicBuffer对象backBuffer,这样,Surface类的成员函数lock接下来就会通过GraphicBuffer对象backBuffer来访问前面所获得的图形缓冲区。
Surface类是使用一种称为双缓冲的技术来渲染应用程序窗口的UI的。这种双缓冲技术需要两个图形缓冲区,其中一个称为前端缓冲区,另外一个称为后端缓冲区。前端缓冲区是正在渲染的图形缓冲区,而后端缓冲区是接下来要渲染的图形缓冲区,它们分别通过Surface类的成员变量mPostedBuffer和mLockedBuffer所指向的两个GraphicBuffer对象来描述。前面所获得的图形缓冲区backBuffer是作为后端缓冲区来使用的,即接下来它所指向的图形缓冲区也需要保存在Surface类的成员变量mLockedBuffer中。
在将图形缓冲区backBuffer返回给调用者之前,Surface类的成员函数lock还需要对它进行进一步的处理,即判断是否需要前端缓冲区mPostedBuffer的内容拷贝回它里面去,以便可以支持部分更新应用程序窗口UI的功能。在满足以下三个条件下,Surface类的成员函数lock可以将前端缓冲区的内容拷贝到后端缓冲区中去:
1. 前端缓冲区的内容拷贝到后端缓冲区所描述的区域的宽度和高度相同。
2. 前端缓冲区和后端缓冲区的像素格式相同。
3. 应用程序窗口绘图表面的属性值mFlags的ISurfaceComposer::eDestroyBackbuffer位等于0,即在渲染了应用程序窗口的UI之后,应该保留正在渲染的图形缓冲区的内容。
如果能将前端缓冲区的内容拷贝到后端缓冲区中去,那么就不用重新绘制应用程序窗口的所有区域,而只需要绘制那些脏的区域,即Region对象newDirtyRegion所描述的区域。注意,参数dirtyIn所描述的区域是原先指定的脏区域,但是在分配了新的后端缓冲区backBuffer之后,我们需要将新的图形缓冲区backBuffer所描述的区域boundsRegion与原先指定的脏区域作一个与操作,得到才是最后需要重绘的脏区域newDirtyRegion。由于在这种情况下,我们只在后端缓冲区backBuffer绘制中绘制应用程序窗口的脏区域,因此,就需要将那些干净的区域从前端缓冲区frontBuffer拷贝到图形缓冲区backBuffer的对应位置去,这是通过调用函数copyBlt来实现的。应用程序窗口的干净区域使用Region对象copyback来描述,它是从应用程序窗口上一次所重绘的区域减去接下来需要重绘的脏区域newDirtyRegion得到的,而应用程序窗口上一次所重绘的区域是保存在Surface类的成员变量mOldDirtyRegion中的。
如果不能将前端缓冲区的内容拷贝到后端缓冲区中去,那么接下来就需要重新绘制应用程序窗口的所有区域了,这时候应用程序窗口的脏区域newDirtyRegion就会被修改为后端缓冲区backBuffer所描述的区域boundsRegion。
Surface类的成员函数lock处理完成前后端缓冲区的拷贝问题之后,接下来就会调用后端缓冲区backBuffer所指向的一个GraphicBuffer对象的成员函数lock来获得它的地址vaddr,以便接下来保存在参数other所描述的一个SurfaceInfo对象的成员变量bits中,这样调用者就获得后端缓冲区backBuffer的地址值了。注意,同时保存在SurfaceInfo对象中的信息还包括后端缓冲区backBuffer的宽度width、高度height、每行像素点stride、用途usage和像素格式format。
Surface类的成员函数lock还会将接下来要重绘的脏缓冲区newDirtyRegion保存在Surface类的成员变量mOldDirtyRegion中,以便再下一次为应用程序窗口分配图形缓冲区时,可以知道应用程序窗口的上一次重绘区域,即上一次干净区域。
此外,Surface类的成员函数lock还会将后端缓冲区backBuffer保存在Surface类的成员变量mLockedBuffer,这样就可以知道应用程序窗口当前正在使用的图形缓冲区,即下一次要请求SurfaceFlinger服务渲染的图形缓冲区。
最后,Surface类的成员函数lock首先调用成员变量mApiLock所指向的一个Mutex对象的成员函数unlock,以便中可以释放前面所获得的锁,然后再返回到上一步去。
接下来,我们继续分析GraphicBuffer类的成员函数lock的实现,以便可以了解一个图形缓冲区的地址是如何获得的。
Step 4. GraphicBuffer.lock
- status_t GraphicBuffer::lock(uint32_t usage, const Rect& rect, void** vaddr)
- {
- ......
- status_t res = getBufferMapper().lock(handle, usage, rect, vaddr);
- return res;
- }
GraphicBuffer类的成员变量handle是从父类android_native_buffer_t继承下来的,它的类型为buffer_handle_t,用来作为一个图形缓冲区的句柄,这个知识点可以参考前面Android应用程序请求SurfaceFlinger服务渲染Surface的过程分析一文。
GraphicBuffer类的成员函数lockGraphicBuffer首先调用另外一个成员函数getBufferMapper来获得一个GraphicBufferMapper对象,然后再调用这个GraphicBufferMapper对象的成员函数lock来获得成员变量handle所描述的一个图形缓冲区的地址,并且保存在输出参数vaddr中。
接下来,我们就继续分析GraphicBufferMapper类的成员函数lock的实现。
Step 5. GraphicBufferMapper.lock
- status_t GraphicBufferMapper::lock(buffer_handle_t handle,
- int usage, const Rect& bounds, void** vaddr)
- {
- status_t err;
- if (sw_gralloc_handle_t::validate(handle) < 0) {
- err = mAllocMod->lock(mAllocMod, handle, usage,
- bounds.left, bounds.top, bounds.width(), bounds.height(),
- vaddr);
- } else {
- err = sw_gralloc_handle_t::lock((sw_gralloc_handle_t*)handle, usage,
- bounds.left, bounds.top, bounds.width(), bounds.height(),
- vaddr);
- }
- LOGW_IF(err, "lock(...) failed %d (%s)", err, strerror(-err));
- return err;
- }
GraphicBufferMapper类的成员函数lock首先调用sw_gralloc_handle_t类的静态成员函数validate来验证参数andle所描述的一个图形缓冲区是否是在w_gralloc_handle_t模块中分配的。如果是的话,那么就需要调用sw_gralloc_handle_t类的静态成员函数lock来获得参数andle所描述的一个图形缓冲区的地址,否则的话,就需要调用GraphicBufferMapper类的成员变量mAllocMod所描述的一个HAL模块Gralloc的成员函数lock来获得参数andle所描述的一个图形缓冲区的地址。
从前面Android应用程序请求SurfaceFlinger服务渲染Surface的过程分析一文可以知道,应用程序窗口所使用的图形缓冲区一般都是在HAL模块Gralloc中分配的,因此,GraphicBufferMapper类的成员函数lock接下来就会调用成员变量mAllocMod所描述的一个HAL模块Gralloc的成员函数lock来获得参数andle所描述的一个图形缓冲区的地址,并且保存在输出参数vaddr中。HAL模块Gralloc的成员函数lock的实现可以参考前面Android帧缓冲区(Frame Buffer)硬件抽象层(HAL)模块Gralloc的实现原理分析一文,这里不再详述。
这一步执行完成之后,返回到前面的Step 1中,即ViewRoot类的成员函数draw中,接下来就会继续调用其成员变量mView所描述的一个DecorView对象的成员函数draw来在前面所获得一块画布上面绘制应用程序窗口的UI。
Step 6. DecorView.draw
- public class PhoneWindow extends Window implements MenuBuilder.Callback {
- ......
- private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
- ......
- private Drawable mMenuBackground;
- ......
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
- if (mMenuBackground != null) {
- mMenuBackground.draw(canvas);
- }
- }
- ......
- }
- ......
- }
这个函数定义在文件frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindow.java中。
DecorView类的成员函数draw首先调用父类FrameLayout的成员函数draw来绘制它的UI内容,然后再检查它是否设置了菜单背景,即成员变量mMenuBackground的值是否不等于null。如果不等于null的话,那么就会调用它所指向的一个Drawable对象的成员函数draw来在画布canvas上绘制这个菜单背景。
未完待续。。。
- 相关文章
- Android应用程序窗口(Activity)的测量(Measure)、布局(L (1人浏览)
- Android应用程序窗口(Activity)的视图对象(View)的创建过程分 (2人浏览)
- Android应用程序窗口(Activity)的窗口对象(Window)的创建过 (0人浏览)
- Android应用程序窗口(Activity)的运行上下文环境(Context) (0人浏览)
- Android应用程序窗口(Activity)实现框架简要介绍和学习计划 (0人浏览)
- Android系统Surface机制的SurfaceFlinger服务的线程模型 (3人浏览)
- Android系统Surface机制的SurfaceFlinger服务对帧缓冲区 (3人浏览)