相信很多Android开发者都知道Canvas类是UI的画布(虽然这种说法并不严谨),因为我们在Canvas上完成各种图形的绘制,那么我们Activity上的各种交互控件又是如何展示并渲染到屏幕上的呢,所以在另一个层面上也有一个“画布”角色——Surface,接下来我们将一起揭开其神秘面纱。
Surface 是Android系统中真正的画布,Activity上的所有UI都是在Surface 上完成绘制的,每一个Surface 对象都在SurfaceFlinger中有对应的图层(Layer),SurfaceFlinger 负责把这些Layer按需混合处理后输出到Frame Buffer中,再由Display设备(屏幕或显示器)把Frame Buffer里的数据呈现到屏幕上。
在ViewRootImpl.java里中涉及到Surface传递,Activity本身创建的Surface为空壳子,通过系统的SurfaceControl进行赋值,通过SurfaceControl是用来截图的就是这个原理。
Surface 实现了Parcelable 接口,意味着Surface对象肯定是会在Binder对象之间传递的。
public class Surface implements Parcelable {
private final Canvas mCanvas = new CompatibleCanvas();
/**
* Create an empty surface, which will later be filled in by readFromParcel().
* @hide
*/
public Surface() {
//默认的构造方法体里没有做任何额外的事
}
/**
* Create Surface from a {@link SurfaceTexture}.
*
* Images drawn to the Surface will be made available to the {@link
* SurfaceTexture}, which can attach them to an OpenGL ES texture via {@link
* SurfaceTexture#updateTexImage}.
* @param surfaceTexture The {@link SurfaceTexture} that is updated by this
* Surface.
* @throws OutOfResourcesException if the surface could not be created.
*/
public Surface(SurfaceTexture surfaceTexture) {
if (surfaceTexture == null) {
throw new IllegalArgumentException("surfaceTexture must not be null");
}
mIsSingleBuffered = surfaceTexture.isSingleBuffered();
synchronized (mLock) {
mName = surfaceTexture.toString();
setNativeObjectLocked(nativeCreateFromSurfaceTexture(surfaceTexture));
}
}
...
}
如上文所示,Surface 一共有两个public 的构造方法,其中一个无参且方法体为空的实现,另一个通过传递SurfaceTexture对象来创建,不过遗憾的是仅仅通过构造方法我们得到的只是一个“空壳子”,换言之,仅仅是在内存中分配了一个地址,还不能真正成为真正的“画布”在上面绘制UI。
以SurfaceView例分析,在SurfaceView中定义了两个Surface的成员变量mSurface
和mNewSurface
并且直接调用无参构造方法初始化,很明显会在其他方法中进行处理。
一般来说 SurfaceView 能够提供更好的性能,但是因为 SurfaceView 本身的输出不是通过 Android 的 UI Renderer(HWUI),而是直接走系统的窗口合成器 SurfaceFlinger,所以无法实现对普通 View 的完全兼容。包括不支持 transform 动画,不支持半透明混合,移动,大小改变,隐藏/显示等时机会出现各种瑕疵等等,总的来说 SurfaceView 只适用于有限的场景。TextureView 正是为了解决 SurfaceView 这些的问题而诞生,在使用上它基本可以无缝替换 SurfaceView,并且因为 TextureView 跟普通 View 一样是通过 UI Renderer 绘制到当前 Activity 的窗口上,所以它跟普通 View 基本上是完全兼容的,不存在 SurfaceView 的种种问题。但同时正是因为 TextureView 需要通过 UI Renderer 输出,也导致了新的问题的出现。除了性能比较 SurfaceView 会有明显下降外(低端机,高 GPU 负荷场景可能存在 15% 左右的帧率下降),另外因为需要在三个线程之间进行写读同步(包括 CPU 和 GPU 的同步),当同步失调的时候,比较容易出现掉帧或者吞帧导致的卡顿和抖动现象。
public class SurfaceView extends View {
final ArrayList<SurfaceHolder.Callback> mCallbacks
= new ArrayList<SurfaceHolder.Callback>();
final int[] mLocation = new int[2];
final ReentrantLock mSurfaceLock = new ReentrantLock();
final Surface mSurface = new Surface(); // Current surface in use
final Surface mNewSurface = new Surface(); // New surface we are switching to
boolean mDrawingStopped = true;
final WindowManager.LayoutParams mLayout= new WindowManager.LayoutParams();
IWindowSession mSession;
...
mSession 为WindowManagerService中的Session的Binder代理对象,而Session是一个Binder服务类,IWindowSession.aidl 中对传入的Surface参数是使用了out修饰符,意味着这个参数是返回参数,即经过Binder通信后Surface 会被赋值。
SurfaceView#updateWindow 里首先传入mNewSurface通过Binder调用IWindowSession#relayout后在底层给mNewSurface赋值,然后再把mNewSurface的内容复制到mSurface中备用。
/** @hide */
protected void updateWindow(boolean force, boolean redrawNeeded) {
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null) {
mTranslator = viewRoot.mTranslator;
}
if (force || creating || formatChanged || sizeChanged || visibleChanged
|| mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {
getLocationInWindow(mLocation);
try {
...
mSurfaceLock.lock();
try {
//@ link IWindowSession mSession 传入mNewSurface
relayoutResult = mSession.relayout(
mWindow, mWindow.mSeq, mLayout, mWindowSpaceWidth, mWindowSpaceHeight,
visible ? VISIBLE : GONE,
WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY,
mWinFrame, mOverscanInsets, mContentInsets,
mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame,
mConfiguration, mNewSurface);
} finally {
mSurfaceLock.unlock();
}
try {
SurfaceHolder.Callback callbacks[] = null;
final boolean surfaceChanged = (relayoutResult
& WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED) != 0;
if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
mSurfaceCreated = false;
if (mSurface.isValid()) {
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceDestroyed(mSurfaceHolder);//触发回调
}
// Since Android N the same surface may be reused and given to us again by the system server at a later point.
}
}
mSurface.transferFrom(mNewSurface);//把mNewSurface的内容复制到mSurface中
if (visible && mSurface.isValid()) {
if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
mSurfaceCreated = true;
if (callbacks == null) {
callbacks = getSurfaceCallbacks();
}
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceCreated(mSurfaceHolder);//触发SurfaceHolder相关回调下同
}
}
if (creating || formatChanged || sizeChanged
|| visibleChanged || realSizeChanged) {
...
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
}
}
if (redrawNeeded) {
for (SurfaceHolder.Callback c : callbacks) {
if (c instanceof SurfaceHolder.Callback2) {
((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
mSurfaceHolder);
}
}
}
}
} finally {
...
}
}
} else {
// Calculate the window position in case RT loses the window
// and we need to fallback to a UI-thread driven position update
getLocationInWindow(mLocation);
...
}
}
mNewSurface最终被windowManagerSurface赋值,relayout 方法细节不再本篇文章讨论范围,请关注后续关于WindowManagerService系列文章。
此次被赋值并不是说给mSurface上的内容赋值,仅仅是在Native层完成创建一个真正可用的Surface对象备用。
思考下,为什么要设计两个成员变成存储呢?
/**
* This is intended to be used by {@link SurfaceView#updateWindow} only.
* @param other access is not thread safe
* @hide
*/
@Deprecated
public void transferFrom(Surface other) {
if (other == null) {
throw new IllegalArgumentException("other must not be null");
}
if (other != this) {
final long newPtr;
synchronized (other.mLock) {
//@ long mNativeObject; // package scope only for SurfaceControl access
newPtr = other.mNativeObject;//得到Native 层对应对象的句柄
other.setNativeObjectLocked(0);
}
synchronized (mLock) {
if (mNativeObject != 0) {
nativeRelease(mNativeObject);
}
setNativeObjectLocked(newPtr);
}
}
}
接下来就是通过JNI 调用native方法并返回相应句柄。
private void setNativeObjectLocked(long ptr) {
if (mNativeObject != ptr) {
if (mNativeObject == 0 && ptr != 0) {
mCloseGuard.open("release");
} else if (mNativeObject != 0 && ptr == 0) {
mCloseGuard.close();
}
mNativeObject = ptr;
mGenerationId += 1;
if (mHwuiContext != null) {
mHwuiContext.updateSurface();//
}
}
}
CloseGurad是Dalvik虚拟机提供的一种机制或者说是一个工具类,用来记录资源泄露的场景,比如使用完的资源(比如cursor/fd)没有正常关闭。可以参考CloseGuard代码注释中提供的Demo为需要管理的对象接入监控。接入之后,如果发生资源使用后没有正常关闭,会在finalize方法中触发CloseGuard的warnIfOpen方法。
本质上Surface是一个Parcel对象,所以无论哪个地方使用都是通过Surface提供的访问Parcel对象的方法来获取里面的内容,首先读取一个字符串并赋值给Surface的名称mName
public void readFromParcel(Parcel source) {
synchronized (mLock) {
// nativeReadFromParcel() will either return mNativeObject, or
// create a new native Surface and return it after reducing
// the reference count on mNativeObject. Either way, it is
// not necessary to call nativeRelease() here.
// NOTE: This must be kept synchronized with the native parceling code
// in frameworks/native/libs/Surface.cpp
mName = source.readString();//
mIsSingleBuffered = source.readInt() != 0;
setNativeObjectLocked(nativeReadFromParcel(mNativeObject, source));
}
}
,然后JNI调用android_view_Surface.cpp#nativeReadFromParcel方法。
static jlong nativeReadFromParcel(JNIEnv* env, jclass clazz,
jlong nativeObject, jobject parcelObj) {
Parcel* parcel = parcelForJavaObject(env, parcelObj);
android::view::Surface surfaceShim;
// Calling code in Surface.java has already read the name of the Surface
// from the Parcel 虚函数由Surface 子类去实现
surfaceShim.readFromParcel(parcel, /*nameAlreadyRead*/true);
sp<Surface> self(reinterpret_cast<Surface *>(nativeObject));
// update the Surface only if the underlying IGraphicBufferProducer
// has changed.
if (self != nullptr
&& (IInterface::asBinder(self->getIGraphicBufferProducer()) ==
IInterface::asBinder(surfaceShim.graphicBufferProducer))) {
// same IGraphicBufferProducer, return ourselves
return jlong(self.get());//
}
sp<Surface> sur;
if (surfaceShim.graphicBufferProducer != nullptr) {
// we have a new IGraphicBufferProducer, create a new Surface for it
sur = new Surface(surfaceShim.graphicBufferProducer, true);
// and keep a reference before passing to java
sur->incStrong(&sRefBaseOwner);
}
if (self != NULL) {
// and loose the java reference to ourselves
self->decStrong(&sRefBaseOwner);
}
return jlong(sur.get());
}
未完待续…