Android 进阶——图形系统的另一种“画布” Surface 详解

引言

相信很多Android开发者都知道Canvas类是UI的画布(虽然这种说法并不严谨),因为我们在Canvas上完成各种图形的绘制,那么我们Activity上的各种交互控件又是如何展示并渲染到屏幕上的呢,所以在另一个层面上也有一个“画布”角色——Surface,接下来我们将一起揭开其神秘面纱。

一、Surface 概述

Surface 是Android系统中真正的画布,Activity上的所有UI都是在Surface 上完成绘制的,每一个Surface 对象都在SurfaceFlinger中有对应的图层(Layer),SurfaceFlinger 负责把这些Layer按需混合处理后输出到Frame Buffer中,再由Display设备(屏幕或显示器)把Frame Buffer里的数据呈现到屏幕上。

在ViewRootImpl.java里中涉及到Surface传递,Activity本身创建的Surface为空壳子,通过系统的SurfaceControl进行赋值,通过SurfaceControl是用来截图的就是这个原理。

二、Surface的创建

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

1、Surface 构造方法

如上文所示,Surface 一共有两个public 的构造方法,其中一个无参且方法体为空的实现,另一个通过传递SurfaceTexture对象来创建,不过遗憾的是仅仅通过构造方法我们得到的只是一个“空壳子”,换言之,仅仅是在内存中分配了一个地址,还不能真正成为真正的“画布”在上面绘制UI。

2、Surface在Java层被创建

以SurfaceView例分析,在SurfaceView中定义了两个Surface的成员变量mSurfacemNewSurface并且直接调用无参构造方法初始化,很明显会在其他方法中进行处理。

一般来说 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 的同步),当同步失调的时候,比较容易出现掉帧或者吞帧导致的卡顿和抖动现象。

2.1、SurfaceView 成员变量定义时直接调用Surface无参构造方法

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 会被赋值。

2.2、SurfaceView#updateWindow 处理mSurface和mNewSurface

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

2.2.1、IWindowSession#replayout给mNewSurface赋值

mNewSurface最终被windowManagerSurface赋值,relayout 方法细节不再本篇文章讨论范围,请关注后续关于WindowManagerService系列文章。

2.2.2、mSurface#transferFrom(mNewSurface) 被mSurface初始化

此次被赋值并不是说给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方法。

2.3、读取mSurface的内容

本质上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());
}

未完待续…

  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CrazyMo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值