- 结论:
@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- 分析:
注释掉代码的情况下,在 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);
}
- 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 线程,自然不会发生空指针,因此图形得到正确绘制。
- 另外,如果在 SurfaceHolder.Callback 中调用了 lockCanvas() 则 Renderer 中的回调不会执行;相应地,一旦 Renderer 中的回调 开始执行后,则不能再使用 GLSurfaceView 的 holder 或者 holder 的 surface 对象了(可以通过 OpenGL 创建 TextureId,然后生成 SurfaceTexture,进而生成 Surface)。
网友评论