| package io.flutter.plugin.platform; |
| |
| import static android.content.ComponentCallbacks2.TRIM_MEMORY_COMPLETE; |
| |
| import android.annotation.TargetApi; |
| import android.graphics.Canvas; |
| import android.graphics.Color; |
| import android.graphics.PorterDuff; |
| import android.graphics.SurfaceTexture; |
| import android.os.Build; |
| import android.view.Surface; |
| import io.flutter.Log; |
| import io.flutter.view.TextureRegistry; |
| import io.flutter.view.TextureRegistry.SurfaceTextureEntry; |
| import java.util.concurrent.atomic.AtomicLong; |
| |
| @TargetApi(26) |
| public class SurfaceTexturePlatformViewRenderTarget implements PlatformViewRenderTarget { |
| private static final String TAG = "SurfaceTexturePlatformViewRenderTarget"; |
| |
| private final AtomicLong pendingFramesCount = new AtomicLong(0L); |
| |
| private void onFrameProduced() { |
| if (Build.VERSION.SDK_INT == 29) { |
| pendingFramesCount.incrementAndGet(); |
| } |
| } |
| |
| private final SurfaceTextureEntry surfaceTextureEntry; |
| |
| private SurfaceTexture surfaceTexture; |
| private Surface surface; |
| private int bufferWidth = 0; |
| private int bufferHeight = 0; |
| |
| private final TextureRegistry.OnFrameConsumedListener frameConsumedListener = |
| new TextureRegistry.OnFrameConsumedListener() { |
| @Override |
| public void onFrameConsumed() { |
| if (Build.VERSION.SDK_INT == 29) { |
| pendingFramesCount.decrementAndGet(); |
| } |
| } |
| }; |
| |
| private boolean shouldRecreateSurfaceForLowMemory = false; |
| private final TextureRegistry.OnTrimMemoryListener trimMemoryListener = |
| new TextureRegistry.OnTrimMemoryListener() { |
| @Override |
| public void onTrimMemory(int level) { |
| // When a memory pressure warning is received and the level equal {@code |
| // ComponentCallbacks2.TRIM_MEMORY_COMPLETE}, the Android system releases the |
| // underlying |
| // surface. If we continue to use the surface (e.g., call lockHardwareCanvas), a |
| // crash |
| // occurs, and we found that this crash appeared on Android10 and above. |
| // See https://github.com/flutter/flutter/issues/103870 for more details. |
| // |
| // Here our workaround is to recreate the surface before using it. |
| if (level == TRIM_MEMORY_COMPLETE && Build.VERSION.SDK_INT >= 29) { |
| shouldRecreateSurfaceForLowMemory = true; |
| } |
| } |
| }; |
| |
| private void recreateSurfaceIfNeeded() { |
| if (!shouldRecreateSurfaceForLowMemory) { |
| return; |
| } |
| if (surface != null) { |
| surface.release(); |
| surface = null; |
| } |
| surface = createSurface(); |
| shouldRecreateSurfaceForLowMemory = false; |
| } |
| |
| protected Surface createSurface() { |
| return new Surface(surfaceTexture); |
| } |
| |
| private void init() { |
| if (bufferWidth > 0 && bufferHeight > 0) { |
| surfaceTexture.setDefaultBufferSize(bufferWidth, bufferHeight); |
| } |
| if (surface != null) { |
| surface.release(); |
| surface = null; |
| } |
| surface = createSurface(); |
| |
| // Fill the entire canvas with a transparent color. |
| // As a result, the background color of the platform view container is displayed |
| // to the user until the platform view draws its first frame. |
| final Canvas canvas = lockHardwareCanvas(); |
| try { |
| canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR); |
| } finally { |
| unlockCanvasAndPost(canvas); |
| } |
| } |
| |
| /** Implementation of PlatformViewRenderTarget */ |
| public SurfaceTexturePlatformViewRenderTarget(SurfaceTextureEntry surfaceTextureEntry) { |
| if (Build.VERSION.SDK_INT < 23) { |
| throw new UnsupportedOperationException( |
| "Platform views cannot be displayed below API level 23" |
| + "You can prevent this issue by setting `minSdkVersion: 23` in build.gradle."); |
| } |
| this.surfaceTextureEntry = surfaceTextureEntry; |
| this.surfaceTexture = surfaceTextureEntry.surfaceTexture(); |
| surfaceTextureEntry.setOnFrameConsumedListener(frameConsumedListener); |
| surfaceTextureEntry.setOnTrimMemoryListener(trimMemoryListener); |
| init(); |
| } |
| |
| public Canvas lockHardwareCanvas() { |
| recreateSurfaceIfNeeded(); |
| |
| // We've observed on Android Q that we have to wait for the consumer of {@link |
| // SurfaceTexture} |
| // to consume the last image before continuing to draw, otherwise subsequent |
| // calls to |
| // {@code dequeueBuffer} to request a free buffer from the {@link BufferQueue} |
| // will fail. |
| // See https://github.com/flutter/flutter/issues/98722 |
| if (Build.VERSION.SDK_INT == 29 && pendingFramesCount.get() > 0L) { |
| return null; |
| } |
| if (surfaceTexture == null || surfaceTexture.isReleased()) { |
| Log.e(TAG, "Invalid RenderTarget: null or already released SurfaceTexture"); |
| return null; |
| } |
| onFrameProduced(); |
| return surface.lockHardwareCanvas(); |
| } |
| |
| public void unlockCanvasAndPost(Canvas canvas) { |
| surface.unlockCanvasAndPost(canvas); |
| } |
| |
| public void resize(int width, int height) { |
| bufferWidth = width; |
| bufferHeight = height; |
| if (surfaceTexture != null) { |
| surfaceTexture.setDefaultBufferSize(bufferWidth, bufferHeight); |
| } |
| } |
| |
| public int getWidth() { |
| return bufferWidth; |
| } |
| |
| public int getHeight() { |
| return bufferHeight; |
| } |
| |
| public long getId() { |
| return this.surfaceTextureEntry.id(); |
| } |
| |
| public boolean isReleased() { |
| return surfaceTexture == null; |
| } |
| |
| public void release() { |
| // Don't release the texture, let the GC finalize it. |
| surfaceTexture = null; |
| if (surface != null) { |
| surface.release(); |
| surface = null; |
| } |
| } |
| |
| public Surface getSurface() { |
| recreateSurfaceIfNeeded(); |
| return surface; |
| } |
| } |