Android GLSurfaceView 不调用 setRen

2021-06-22  本文已影响0人  雁过留声_泪落无痕
  1. 结论:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    GLSurfaceView surfaceView = new GLSurfaceView(this);
    surfaceView.setEGLContextClientVersion(2);
    surfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            Log.d(TAG, "surfaceCreated()");

            Paint paint = new Paint();
            paint.setStyle(Paint.Style.FILL_AND_STROKE);
            paint.setColor(Color.RED);

            Rect rect = new Rect(0, 0, 100, 100);
            Canvas canvas = holder.lockCanvas();
            canvas.drawRect(rect, paint);
            holder.unlockCanvasAndPost(canvas);
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            Log.d(TAG, "surfaceChanged()");
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            Log.d(TAG, "surfaceDestroyed()");
        }
    });
    /*surfaceView.setRenderer(new GLSurfaceView.Renderer() {
        @Override
        public void onSurfaceCreated(GL10 gl, EGLConfig config) {

        }

        @Override
        public void onSurfaceChanged(GL10 gl, int width, int height) {

        }

        @Override
        public void onDrawFrame(GL10 gl) {

        }
    });*/
    setContentView(surfaceView);
}

将 setRenderer() 相关的代码注释掉,则 SurfaceHolder.Callback 相关回调得不到调用,logcat 里没有任何日志。

将注释的代码打开则能收到回调,同时也能在界面上正确绘制出一个红色的方形块。

打开注释绘制出的图形.png
  1. 分析:

注释掉代码的情况下,在 GLSurfaceView.java 的 surfaceCreated() 方法中添加断点

public void surfaceCreated(SurfaceHolder holder) {
    mGLThread.surfaceCreated();
}

查看变量,发现 mGLThread 变量为 null,这里势必发生空指针异常

空指针异常.png

同时,查看调用栈:

调用栈.png

根据调用栈,我们找到 SurfaceView.java 的 updateSurface() 方法,并找到调用 surfaceCreated() 方法的地方查看源码

protected void updateSurface() {
    if (!mHaveFrame) {
        return;
    }
    ViewRootImpl viewRoot = getViewRootImpl();
    if (viewRoot == null || viewRoot.mSurface == null || !viewRoot.mSurface.isValid()) {
        return;
    }

    mTranslator = viewRoot.mTranslator;
    if (mTranslator != null) {
        mSurface.setCompatibilityTranslator(mTranslator);
    }

    int myWidth = mRequestedWidth;
    if (myWidth <= 0) myWidth = getWidth();
    int myHeight = mRequestedHeight;
    if (myHeight <= 0) myHeight = getHeight();

    final boolean formatChanged = mFormat != mRequestedFormat;
    final boolean visibleChanged = mVisible != mRequestedVisible;
    final boolean creating = (mSurfaceControl == null || formatChanged || visibleChanged)
            && mRequestedVisible;
    final boolean sizeChanged = mSurfaceWidth != myWidth || mSurfaceHeight != myHeight;
    final boolean windowVisibleChanged = mWindowVisibility != mLastWindowVisibility;
    boolean redrawNeeded = false;

    if (creating || formatChanged || sizeChanged || visibleChanged || windowVisibleChanged) {
        getLocationInWindow(mLocation);

        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                + "Changes: creating=" + creating
                + " format=" + formatChanged + " size=" + sizeChanged
                + " visible=" + visibleChanged
                + " left=" + (mWindowSpaceLeft != mLocation[0])
                + " top=" + (mWindowSpaceTop != mLocation[1]));

        try {
            final boolean visible = mVisible = mRequestedVisible;
            mWindowSpaceLeft = mLocation[0];
            mWindowSpaceTop = mLocation[1];
            mSurfaceWidth = myWidth;
            mSurfaceHeight = myHeight;
            mFormat = mRequestedFormat;
            mLastWindowVisibility = mWindowVisibility;

            mScreenRect.left = mWindowSpaceLeft;
            mScreenRect.top = mWindowSpaceTop;
            mScreenRect.right = mWindowSpaceLeft + getWidth();
            mScreenRect.bottom = mWindowSpaceTop + getHeight();
            if (mTranslator != null) {
                mTranslator.translateRectInAppWindowToScreen(mScreenRect);
            }

            final Rect surfaceInsets = getParentSurfaceInsets();
            mScreenRect.offset(surfaceInsets.left, surfaceInsets.top);

            if (creating) {
                mSurfaceSession = new SurfaceSession(viewRoot.mSurface);
                mDeferredDestroySurfaceControl = mSurfaceControl;

                updateOpaqueFlag();
                final String name = "SurfaceView - " + viewRoot.getTitle().toString();

                mSurfaceControl = new SurfaceControlWithBackground(
                        name,
                        (mSurfaceFlags & SurfaceControl.OPAQUE) != 0,
                        new SurfaceControl.Builder(mSurfaceSession)
                                .setSize(mSurfaceWidth, mSurfaceHeight)
                                .setFormat(mFormat)
                                .setFlags(mSurfaceFlags));
            } else if (mSurfaceControl == null) {
                return;
            }

            boolean realSizeChanged = false;

            mSurfaceLock.lock();
            try {
                mDrawingStopped = !visible;

                if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                        + "Cur surface: " + mSurface);

                SurfaceControl.openTransaction();
                try {
                    mSurfaceControl.setLayer(mSubLayer);
                    if (mViewVisibility) {
                        mSurfaceControl.show();
                    } else {
                        mSurfaceControl.hide();
                    }

                    // While creating the surface, we will set it's initial
                    // geometry. Outside of that though, we should generally
                    // leave it to the RenderThread.
                    //
                    // There is one more case when the buffer size changes we aren't yet
                    // prepared to sync (as even following the transaction applying
                    // we still need to latch a buffer).
                    // b/73
                    if (sizeChanged || creating || !mRtHandlingPositionUpdates) {
                        mSurfaceControl.setPosition(mScreenRect.left, mScreenRect.top);
                        mSurfaceControl.setMatrix(mScreenRect.width() / (float) mSurfaceWidth,
                                0.0f, 0.0f,
                                mScreenRect.height() / (float) mSurfaceHeight);
                    }
                    if (sizeChanged) {
                        mSurfaceControl.setSize(mSurfaceWidth, mSurfaceHeight);
                    }
                } finally {
                    SurfaceControl.closeTransaction();
                }

                if (sizeChanged || creating) {
                    redrawNeeded = true;
                }

                mSurfaceFrame.left = 0;
                mSurfaceFrame.top = 0;
                if (mTranslator == null) {
                    mSurfaceFrame.right = mSurfaceWidth;
                    mSurfaceFrame.bottom = mSurfaceHeight;
                } else {
                    float appInvertedScale = mTranslator.applicationInvertedScale;
                    mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f);
                    mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f);
                }

                final int surfaceWidth = mSurfaceFrame.right;
                final int surfaceHeight = mSurfaceFrame.bottom;
                realSizeChanged = mLastSurfaceWidth != surfaceWidth
                        || mLastSurfaceHeight != surfaceHeight;
                mLastSurfaceWidth = surfaceWidth;
                mLastSurfaceHeight = surfaceHeight;
            } finally {
                mSurfaceLock.unlock();
            }

            try {
                redrawNeeded |= visible && !mDrawFinished;

                SurfaceHolder.Callback callbacks[] = null;

                final boolean surfaceChanged = creating;
                if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
                    mSurfaceCreated = false;
                    if (mSurface.isValid()) {
                        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                                + "visibleChanged -- surfaceDestroyed");
                        callbacks = getSurfaceCallbacks();
                        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. However
                        // as we didn't do this in previous releases, clients weren't
                        // necessarily required to clean up properly in
                        // surfaceDestroyed. This leads to problems for example when
                        // clients don't destroy their EGL context, and try
                        // and create a new one on the same surface following reuse.
                        // Since there is no valid use of the surface in-between
                        // surfaceDestroyed and surfaceCreated, we force a disconnect,
                        // so the next connect will always work if we end up reusing
                        // the surface.
                        if (mSurface.isValid()) {
                            mSurface.forceScopedDisconnect();
                        }
                    }
                }

                if (creating) {
                    mSurface.copyFrom(mSurfaceControl);
                }

                if (sizeChanged && getContext().getApplicationInfo().targetSdkVersion
                        < Build.VERSION_CODES.O) {
                    // Some legacy applications use the underlying native {@link Surface} object
                    // as a key to whether anything has changed. In these cases, updates to the
                    // existing {@link Surface} will be ignored when the size changes.
                    // Therefore, we must explicitly recreate the {@link Surface} in these
                    // cases.
                    mSurface.createFrom(mSurfaceControl);
                }

                if (visible && mSurface.isValid()) {
                    if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
                        mSurfaceCreated = true;
                        mIsCreating = true;
                        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                                + "visibleChanged -- surfaceCreated");
                        if (callbacks == null) {
                            callbacks = getSurfaceCallbacks();
                        }
                        for (SurfaceHolder.Callback c : callbacks) {
                            c.surfaceCreated(mSurfaceHolder);
                        }
                    }
                    if (creating || formatChanged || sizeChanged
                            || visibleChanged || realSizeChanged) {
                        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                                + "surfaceChanged -- format=" + mFormat
                                + " w=" + myWidth + " h=" + myHeight);
                        if (callbacks == null) {
                            callbacks = getSurfaceCallbacks();
                        }
                        for (SurfaceHolder.Callback c : callbacks) {
                            c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
                        }
                    }
                    if (redrawNeeded) {
                        if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " "
                                + "surfaceRedrawNeeded");
                        if (callbacks == null) {
                            callbacks = getSurfaceCallbacks();
                        }

                        mPendingReportDraws++;
                        viewRoot.drawPending();
                        SurfaceCallbackHelper sch =
                                new SurfaceCallbackHelper(this::onDrawFinished);
                        sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
                    }
                }
            } finally {
                mIsCreating = false;
                if (mSurfaceControl != null && !mSurfaceCreated) {
                    mSurface.release();

                    mSurfaceControl.destroy();
                    mSurfaceControl = null;
                }
            }
        } catch (Exception ex) {
            Log.e(TAG, "Exception configuring surface", ex);
        }
        if (DEBUG) Log.v(
            TAG, "Layout: x=" + mScreenRect.left + " y=" + mScreenRect.top
            + " w=" + mScreenRect.width() + " h=" + mScreenRect.height()
            + ", frame=" + mSurfaceFrame);
    } else {
        // Calculate the window position in case RT loses the window
        // and we need to fallback to a UI-thread driven position update
        getLocationInSurface(mLocation);
        final boolean positionChanged = mWindowSpaceLeft != mLocation[0]
                || mWindowSpaceTop != mLocation[1];
        final boolean layoutSizeChanged = getWidth() != mScreenRect.width()
                || getHeight() != mScreenRect.height();
        if (positionChanged || layoutSizeChanged) { // Only the position has changed
            mWindowSpaceLeft = mLocation[0];
            mWindowSpaceTop = mLocation[1];
            // For our size changed check, we keep mScreenRect.width() and mScreenRect.height()
            // in view local space.
            mLocation[0] = getWidth();
            mLocation[1] = getHeight();

            mScreenRect.set(mWindowSpaceLeft, mWindowSpaceTop,
                    mWindowSpaceLeft + mLocation[0], mWindowSpaceTop + mLocation[1]);

            if (mTranslator != null) {
                mTranslator.translateRectInAppWindowToScreen(mScreenRect);
            }

            if (mSurfaceControl == null) {
                return;
            }

            if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) {
                try {
                    if (DEBUG) Log.d(TAG, String.format("%d updateSurfacePosition UI, " +
                            "postion = [%d, %d, %d, %d]", System.identityHashCode(this),
                            mScreenRect.left, mScreenRect.top,
                            mScreenRect.right, mScreenRect.bottom));
                    setParentSpaceRectangle(mScreenRect, -1);
                } catch (Exception ex) {
                    Log.e(TAG, "Exception configuring surface", ex);
                }
            }
        }
    }
}

发现了如下的源码

try {
    ...

    for (SurfaceHolder.Callback c : callbacks) {
        c.surfaceCreated(mSurfaceHolder);
    }

    ...
} finally {
    ...
}

再看调用栈,发现 callbacks 包含了两个 回调,其中一个是我们在 Activity 里添加的,另一个是 GLSurfaceView#init() 方法中添加的,而这里是顺序进行回调,第一个回调则是回调到了 GLSurfaceView#surfaceCreated(SurfaceHolder) 方法中,也是我们的断点断下来的地方。第一个回调就发生了异常,第二个回调自然就调用不到了,因为直接走到了 finally 块中去了。

callbacks 顺序回调

GLSurfaceView#init() 方法:

private void init() {
    // Install a SurfaceHolder.Callback so we get notified when the
    // underlying surface is created and destroyed
    SurfaceHolder holder = getHolder();
    holder.addCallback(this);
    // setFormat is done by SurfaceView in SDK 2.3 and newer. Uncomment
    // this statement if back-porting to 2.2 or older:
    // holder.setFormat(PixelFormat.RGB_565);
    //
    // setType is not needed for SDK 2.0 or newer. Uncomment this
    // statement if back-porting this code to older SDKs.
    // holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
}
  1. setRenderer() 中干了什么
public void setRenderer(Renderer renderer) {
    checkRenderThreadState();
    if (mEGLConfigChooser == null) {
        mEGLConfigChooser = new SimpleEGLConfigChooser(true);
    }
    if (mEGLContextFactory == null) {
        mEGLContextFactory = new DefaultContextFactory();
    }
    if (mEGLWindowSurfaceFactory == null) {
        mEGLWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
    }
    mRenderer = renderer;
    mGLThread = new GLThread(mThisWeakRef);
    mGLThread.start();
}

初始化了 mGLThread 变量,并开启了 GL 线程,自然不会发生空指针,因此图形得到正确绘制。

  1. 另外,如果在 SurfaceHolder.Callback 中调用了 lockCanvas() 则 Renderer 中的回调不会执行;相应地,一旦 Renderer 中的回调 开始执行后,则不能再使用 GLSurfaceView 的 holder 或者 holder 的 surface 对象了(可以通过 OpenGL 创建 TextureId,然后生成 SurfaceTexture,进而生成 Surface)。
上一篇 下一篇

猜你喜欢

热点阅读