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); // 将画好的画布提交
}
}