Android UI绘制之独立线程绘制

       在之前博文介绍过 Android 绘制UI通常有两种方式:

1. 把图形绘制到布局中的View对象中,图形绘制由系统绘制View层次数来来处理。

2. 把图形直接绘制到画布上(Canvas对象),这种方法可以通过独立的线程来管理surfaceView对象,并由独立线程来管理绘制过

       本文介绍方式2,通过独立线程绘制的方法。在Android surfaceView分析及C/C++ 通过surfce绘制UI博文中已经简单介绍过,这里将深入讨论其实现的机制。Android中的图形系统采用 Client/Server 架构。Server (即SurfaceFlinger)主要由 C++ 代码编写而成。Client 端代码分为两部分,一部分是由 Java 提供的供应用程序使用的 API,另一部分则是用 C++ 写成的底层实现。下图概要介绍了 Android系统的架构以及使用的主要组件。

                                                  图1: AndroidUI系统组件

       每个应用可能有一个或多个surface(含surface的情况下),surfaceFlinger是本地服务,用于管理surface的创建、销毁、zorder合成。View及其子类(如TextView, Button)要画在surface上。每个surface创建一个Canvas对象 (但属性时常改变),用来管理view在surface上的绘图操作,如画点画线。每个canvas对象对应一个bitmap,存储画在surface上的内容。当然这里还有个Layer的概念,在后面创建surface流程中我们再介绍。

一:surface 创建

      在Android surfaceView分析中介绍了SurfaceView的创建是在updateWindow方法中调用通过绑定windowmanagerservice的mSession来创建的。而在Android应用中启动的布局将共用一个surface,其创建过程在Android UI 绘制机制之View创建过程博文中介绍了在ViewRoot第一次做初始化工作时也会调用windowmanagerservice来创建surface。那我们就来细致地再往下跟一边,看这个surface是如何创建的,只有了解这些才能自如地在这个surface上画图。

 SurfaceControl surfaceControl = winAnimator.createSurfaceLocked();
                    if (surfaceControl != null) {
                        outSurface.copyFrom(surfaceControl);
                        if (SHOW_TRANSACTIONS) Slog.i(TAG,
                                "  OUT SURFACE " + outSurface + ": copied");
                    } else {
                        // For some reason there isn't a surface.  Clear the
                        // caller's object so they see the same state.
                        outSurface.release();
                    }
        surfaceControl是Android 4.3引入进来的,为了更方便windowmanagerservice管理surface,也是能转换成一个surface。下图是整个surface的创建流程。

       

                                                      图2: Surface 创建过程

    

      研究一个surface如何创建的关键路径如下:

1. frameworks/base/core/java/android/view/SurfaceControl.java —nativeCreate。

2. frameworks/base/core/jni/android_view_SurfaceControl.cpp — nativeCreate。

在这个函数中SurfaceComposerClient 对象被创建。

3. frameworks/base/libs/gui/SurfaceComposerClient.cpp —SurfaceComposerClient::SurfaceComposerClient ().

这个函数非常重要,在这里建立了client和server之间的桥梁。通过函数_get_surface_manager()获得了一个指向server的IBinder 对象(具有ISurfaceComposer接口),之后通过这个IBinder就可以跨进程访问Server的功能。接着调用ISurfaceComposer::createConnection()创建并返回了一个ISurfaceFlingerClient的IBinder。

4.frameworks/base/libs/surfaceflinger/client.cpp —Client::createSurface ()。

Client由BnSurfaceComposerClient派生而来。

5.frameworks/base/libs/surfaceflinger/SurfaceFlinger.cpp — SurfaceFlinger::createLayer()。

这个函数为Surface创建一个对应的Layer。

       上述关键路径中,1,2,3运行于client进程中,而4,5运行与server进程中。server作为一个service提供给client访问。从上面可以看出Surface会创建一个对应的Layer,这个Layer维护了Zorder,同时会通过OPENGL接口生产绘制图形的buffer。

       二、Surface的绘制

       在Android4.0 以后引入了硬件加速绘制图形,在Android系统刷新过程中ViewRoot会调用performTraversals方法并依次调用performMeasure、performLayout、performDraw。在performDraw中会区分是否支持硬件加速,如果支持直接通过OPENGL做硬件加速绘制,如果不支持则走软件绘制。因为我们在独立线程绘制过程中一般走的是软件绘制。这里对软件绘制的方法做介绍以掌握如何在独立线程中绘制UI。

    

   private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
            boolean scalingRequired, Rect dirty) {

        // Draw with software renderer.
        Canvas canvas;
        try {
            int left = dirty.left;
            int top = dirty.top;
            int right = dirty.right;
            int bottom = dirty.bottom;

            canvas = mSurface.lockCanvas(dirty);

            // The dirty rectangle can be modified by Surface.lockCanvas()
            //noinspection ConstantConditions
            if (left != dirty.left || top != dirty.top || right != dirty.right ||
                    bottom != dirty.bottom) {
                attachInfo.mIgnoreDirtyState = true;
            }

            // TODO: Do this in native
            canvas.setDensity(mDensity);
        } catch (Surface.OutOfResourcesException e) {
            handleOutOfResourcesException(e);
            return false;
        } catch (IllegalArgumentException e) {
            Log.e(TAG, "Could not lock surface", e);
            // Don't assume this is due to out of memory, it could be
            // something else, and if it is something else then we could
            // kill stuff (or ourself) for no reason.
            mLayoutRequested = true;    // ask wm for a new surface next time.
            return false;
        }

        try {
            if (DEBUG_ORIENTATION || DEBUG_DRAW) {
                Log.v(TAG, "Surface " + surface + " drawing to bitmap w="
                        + canvas.getWidth() + ", h=" + canvas.getHeight());
                //canvas.drawARGB(255, 255, 0, 0);
            }

            // If this bitmap's format includes an alpha channel, we
            // need to clear it before drawing so that the child will
            // properly re-composite its drawing on a transparent
            // background. This automatically respects the clip/dirty region
            // or
            // If we are applying an offset, we need to clear the area
            // where the offset doesn't appear to avoid having garbage
            // left in the blank areas.
            if (!canvas.isOpaque() || yoff != 0) {
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }

            dirty.setEmpty();
            mIsAnimating = false;
            attachInfo.mDrawingTime = SystemClock.uptimeMillis();
            mView.mPrivateFlags |= View.PFLAG_DRAWN;

            if (DEBUG_DRAW) {
                Context cxt = mView.getContext();
                Log.i(TAG, "Drawing: package:" + cxt.getPackageName() +
                        ", metrics=" + cxt.getResources().getDisplayMetrics() +
                        ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
            }
            try {
                canvas.translate(0, -yoff);
                if (mTranslator != null) {
                    mTranslator.translateCanvas(canvas);
                }
                canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                attachInfo.mSetIgnoreDirtyState = false;

                mView.draw(canvas);

                drawAccessibilityFocusedDrawableIfNeeded(canvas);
            } finally {
                if (!attachInfo.mSetIgnoreDirtyState) {
                    // Only clear the flag if it was not set during the mView.draw() call
                    attachInfo.mIgnoreDirtyState = false;
                }
            }
        } finally {
            try {
                surface.unlockCanvasAndPost(canvas);
            } catch (IllegalArgumentException e) {
                Log.e(TAG, "Could not unlock surface", e);
                mLayoutRequested = true;    // ask wm for a new surface next time.
                //noinspection ReturnInsideFinallyBlock
                return false;
            }

            if (LOCAL_LOGV) {
                Log.v(TAG, "Surface " + surface + " unlockCanvasAndPost");
            }
        }
        return true;
    }
       其中关键就是canvas = mSurface.lockCanvas(dirty) 与 surface.unlockC anvasAndPost(canvas)。这个流程与 Android surfaceView分析及C/C++ 通过surfce绘制UI介绍的是一致的,先lockCanvas,绘制UI,最后通过unlockCanvasAndPost通知surfaceFlinger先做zorder组合显示。

      lockCanvas(dirty) 就是通过JNI调用nativeLockCanvas返回一个Canvas下面看nativeLockCanvas的实现。

    

sttic void nativeLockCanvas(JNIEnv* env, jclass clazz,
        jint nativeObject, jobject canvasObj, jobject dirtyRectObj) {
    sp<Surface> surface(reinterpret_cast<Surface *>(nativeObject));

    if (!isSurfaceValid(surface)) {
        doThrowIAE(env);
        return;
    }

    // get dirty region
    Region dirtyRegion;
    if (dirtyRectObj) {
        Rect dirty;
        dirty.left = env->GetIntField(dirtyRectObj, gRectClassInfo.left);
        dirty.top = env->GetIntField(dirtyRectObj, gRectClassInfo.top);
        dirty.right = env->GetIntField(dirtyRectObj, gRectClassInfo.right);
        dirty.bottom = env->GetIntField(dirtyRectObj, gRectClassInfo.bottom);
        if (!dirty.isEmpty()) {
            dirtyRegion.set(dirty);
        }
    } else {
        dirtyRegion.set(Rect(0x3FFF, 0x3FFF));
    }

    ANativeWindow_Buffer outBuffer;
    Rect dirtyBounds(dirtyRegion.getBounds());
    status_t err = surface->lock(&outBuffer, &dirtyBounds);
    dirtyRegion.set(dirtyBounds);
    if (err < 0) {
        const char* const exception = (err == NO_MEMORY) ?
                OutOfResourcesException :
                "java/lang/IllegalArgumentException";
        jniThrowException(env, exception, NULL);
        return;
    }

    // Associate a SkCanvas object to this surface
    env->SetIntField(canvasObj, gCanvasClassInfo.mSurfaceFormat, outBuffer.format);

    SkBitmap bitmap;
    ssize_t bpr = outBuffer.stride * bytesPerPixel(outBuffer.format);
    bitmap.setConfig(convertPixelFormat(outBuffer.format), outBuffer.width, outBuffer.height, bpr);
    if (outBuffer.format == PIXEL_FORMAT_RGBX_8888) {
        bitmap.setIsOpaque(true);
    }
    if (outBuffer.width > 0 && outBuffer.height > 0) {
        bitmap.setPixels(outBuffer.bits);
    } else {
        // be safe with an empty bitmap.
        bitmap.setPixels(NULL);
    }

    SkCanvas* nativeCanvas = SkNEW_ARGS(SkCanvas, (bitmap));
    swapCanvasPtr(env, canvasObj, nativeCanvas);

    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);

    if (dirtyRectObj) {
        const Rect& bounds(dirtyRegion.getBounds());
        env->SetIntField(dirtyRectObj, gRectClassInfo.left, bounds.left);
        env->SetIntField(dirtyRectObj, gRectClassInfo.top, bounds.top);
        env->SetIntField(dirtyRectObj, gRectClassInfo.right, bounds.right);
        env->SetIntField(dirtyRectObj, gRectClassInfo.bottom, bounds.bottom);
    }
}
        在JNI层实现的就是通过surface获取到Layer中的buffer,并生成一个skiabitmap, Android 2D软件绘图使用skia作为核心引擎,skia将会单独写一博文来介绍,这个bitmap的存储空间为Layer buffer。绘制的UI就是写入到这个buffer中,绘制好后通过 unlockCanvasAndPost通知surfaceflinger输出显示。如果是独立线程绘制UI,那么流程与上描述基本一致。但需要注意的是如果独立线程绘制的话,surface可通过surfaceView来获取。
private void draw() {  
try {  
 //  步骤1  
canvas = sfh.lockCanvas(); // 得到一个canvas实例    
//  步骤2  
canvas.drawColor(Color.WHITE);// 刷屏                   
canvas.drawText("test", 100, 100, paint);// 画文字文本  
} catch (Exception ex) {  
} finally {   
 // 步骤3  
if (canvas != null)  
 sfh.unlockCanvasAndPost(canvas); // 将画好的画布提交  
}  
}  
     




            


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值