Flutter Engine
The Flutter Engine
FlutterTextureView.java
Go to the documentation of this file.
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package io.flutter.embedding.android;
6
7import android.content.Context;
8import android.graphics.SurfaceTexture;
9import android.util.AttributeSet;
10import android.view.Surface;
11import android.view.TextureView;
12import androidx.annotation.NonNull;
13import androidx.annotation.Nullable;
14import androidx.annotation.VisibleForTesting;
15import io.flutter.Log;
16import io.flutter.embedding.engine.renderer.FlutterRenderer;
17import io.flutter.embedding.engine.renderer.RenderSurface;
18
19/**
20 * Paints a Flutter UI on a {@link SurfaceTexture}.
21 *
22 * <p>To begin rendering a Flutter UI, the owner of this {@code FlutterTextureView} must invoke
23 * {@link #attachToRenderer(FlutterRenderer)} with the desired {@link FlutterRenderer}.
24 *
25 * <p>To stop rendering a Flutter UI, the owner of this {@code FlutterTextureView} must invoke
26 * {@link #detachFromRenderer()}.
27 *
28 * <p>A {@code FlutterTextureView} is intended for situations where a developer needs to render a
29 * Flutter UI, but does not require any keyboard input, gesture input, accessibility integrations or
30 * any other interactivity beyond rendering. If standard interactivity is desired, consider using a
31 * {@link FlutterView} which provides all of these behaviors and utilizes a {@code
32 * FlutterTextureView} internally.
33 */
34public class FlutterTextureView extends TextureView implements RenderSurface {
35 private static final String TAG = "FlutterTextureView";
36
37 private boolean isSurfaceAvailableForRendering = false;
38 private boolean isPaused = false;
39 @Nullable private FlutterRenderer flutterRenderer;
40 @Nullable private Surface renderSurface;
41
42 private boolean shouldNotify() {
43 return flutterRenderer != null && !isPaused;
44 }
45
46 // Connects the {@code SurfaceTexture} beneath this {@code TextureView} with Flutter's native
47 // code.
48 // Callbacks are received by this Object and then those messages are forwarded to our
49 // FlutterRenderer, and then on to the JNI bridge over to native Flutter code.
50 private final SurfaceTextureListener surfaceTextureListener =
51 new SurfaceTextureListener() {
52 @Override
53 public void onSurfaceTextureAvailable(
54 SurfaceTexture surfaceTexture, int width, int height) {
55 Log.v(TAG, "SurfaceTextureListener.onSurfaceTextureAvailable()");
56 isSurfaceAvailableForRendering = true;
57
58 // If we're already attached to a FlutterRenderer then we're now attached to both a
59 // renderer
60 // and the Android window, so we can begin rendering now.
61 if (shouldNotify()) {
62 connectSurfaceToRenderer();
63 }
64 }
65
66 @Override
67 public void onSurfaceTextureSizeChanged(
68 @NonNull SurfaceTexture surface, int width, int height) {
69 Log.v(TAG, "SurfaceTextureListener.onSurfaceTextureSizeChanged()");
70 if (shouldNotify()) {
71 changeSurfaceSize(width, height);
72 }
73 }
74
75 @Override
76 public void onSurfaceTextureUpdated(@NonNull SurfaceTexture surface) {
77 // Invoked every time a new frame is available. We don't care.
78 }
79
80 @Override
81 public boolean onSurfaceTextureDestroyed(@NonNull SurfaceTexture surface) {
82 Log.v(TAG, "SurfaceTextureListener.onSurfaceTextureDestroyed()");
83 isSurfaceAvailableForRendering = false;
84
85 // If we're attached to a FlutterRenderer then we need to notify it that our
86 // SurfaceTexture
87 // has been destroyed.
88 if (shouldNotify()) {
89 disconnectSurfaceFromRenderer();
90 }
91
92 // Definitively release the surface to avoid leaked closeables, just in case
93 if (renderSurface != null) {
94 renderSurface.release();
95 renderSurface = null;
96 }
97
98 // Return true to indicate that no further painting will take place
99 // within this SurfaceTexture.
100 return true;
101 }
102 };
103
104 /** Constructs a {@code FlutterTextureView} programmatically, without any XML attributes. */
105 public FlutterTextureView(@NonNull Context context) {
106 this(context, null);
107 }
108
109 /** Constructs a {@code FlutterTextureView} in an XML-inflation-compliant manner. */
110 public FlutterTextureView(@NonNull Context context, @Nullable AttributeSet attrs) {
111 super(context, attrs);
112 init();
113 }
114
115 private void init() {
116 // Listen for when our underlying SurfaceTexture becomes available, changes size, or
117 // gets destroyed, and take the appropriate actions.
118 setSurfaceTextureListener(surfaceTextureListener);
119 }
120
121 @Nullable
122 @Override
124 return flutterRenderer;
125 }
126
127 @VisibleForTesting
128 /* package */ boolean isSurfaceAvailableForRendering() {
129 return isSurfaceAvailableForRendering;
130 }
131
132 /**
133 * Invoked by the owner of this {@code FlutterTextureView} when it wants to begin rendering a
134 * Flutter UI to this {@code FlutterTextureView}.
135 *
136 * <p>If an Android {@link SurfaceTexture} is available, this method will give that {@link
137 * SurfaceTexture} to the given {@link FlutterRenderer} to begin rendering Flutter's UI to this
138 * {@code FlutterTextureView}.
139 *
140 * <p>If no Android {@link SurfaceTexture} is available yet, this {@code FlutterTextureView} will
141 * wait until a {@link SurfaceTexture} becomes available and then give that {@link SurfaceTexture}
142 * to the given {@link FlutterRenderer} to begin rendering Flutter's UI to this {@code
143 * FlutterTextureView}.
144 */
145 public void attachToRenderer(@NonNull FlutterRenderer flutterRenderer) {
146 Log.v(TAG, "Attaching to FlutterRenderer.");
147 if (this.flutterRenderer != null) {
148 Log.v(
149 TAG,
150 "Already connected to a FlutterRenderer. Detaching from old one and attaching to new"
151 + " one.");
152 this.flutterRenderer.stopRenderingToSurface();
153 }
154
155 this.flutterRenderer = flutterRenderer;
156
157 resume();
158 }
159
160 /**
161 * Invoked by the owner of this {@code FlutterTextureView} when it no longer wants to render a
162 * Flutter UI to this {@code FlutterTextureView}.
163 *
164 * <p>This method will cease any on-going rendering from Flutter to this {@code
165 * FlutterTextureView}.
166 */
167 public void detachFromRenderer() {
168 if (flutterRenderer != null) {
169 // If we're attached to an Android window then we were rendering a Flutter UI. Now that
170 // this FlutterTextureView is detached from the FlutterRenderer, we need to stop rendering.
171 // TODO(mattcarroll): introduce a isRendererConnectedToSurface() to wrap "getWindowToken() !=
172 // null"
173 if (getWindowToken() != null) {
174 Log.v(TAG, "Disconnecting FlutterRenderer from Android surface.");
175 disconnectSurfaceFromRenderer();
176 }
177
178 flutterRenderer = null;
179 } else {
180 Log.w(TAG, "detachFromRenderer() invoked when no FlutterRenderer was attached.");
181 }
182 }
183
184 /**
185 * Invoked by the owner of this {@code FlutterTextureView} when it should pause rendering Flutter
186 * UI to this {@code FlutterTextureView}.
187 */
188 public void pause() {
189 if (flutterRenderer == null) {
190 Log.w(TAG, "pause() invoked when no FlutterRenderer was attached.");
191 return;
192 }
193 isPaused = true;
194 }
195
196 public void resume() {
197 if (flutterRenderer == null) {
198 Log.w(TAG, "resume() invoked when no FlutterRenderer was attached.");
199 return;
200 }
201
202 // If we're already attached to an Android window then we're now attached to both a renderer
203 // and the Android window. We can begin rendering now.
204 if (isSurfaceAvailableForRendering()) {
205 Log.v(
206 TAG,
207 "Surface is available for rendering. Connecting FlutterRenderer to Android surface.");
208 connectSurfaceToRenderer();
209 }
210 isPaused = false;
211 }
212
213 /**
214 * Manually set the render surface for this view.
215 *
216 * <p>This should only be used for testing purposes.
217 */
218 @VisibleForTesting
219 public void setRenderSurface(Surface renderSurface) {
220 this.renderSurface = renderSurface;
221 }
222
223 // FlutterRenderer and getSurfaceTexture() must both be non-null.
224 private void connectSurfaceToRenderer() {
225 if (flutterRenderer == null || getSurfaceTexture() == null) {
226 throw new IllegalStateException(
227 "connectSurfaceToRenderer() should only be called when flutterRenderer and"
228 + " getSurfaceTexture() are non-null.");
229 }
230
231 // Definitively release the surface to avoid leaked closeables, just in case
232 if (renderSurface != null) {
233 renderSurface.release();
234 renderSurface = null;
235 }
236
237 renderSurface = new Surface(getSurfaceTexture());
238 flutterRenderer.startRenderingToSurface(renderSurface, isPaused);
239 }
240
241 // FlutterRenderer must be non-null.
242 private void changeSurfaceSize(int width, int height) {
243 if (flutterRenderer == null) {
244 throw new IllegalStateException(
245 "changeSurfaceSize() should only be called when flutterRenderer is non-null.");
246 }
247
248 Log.v(
249 TAG,
250 "Notifying FlutterRenderer that Android surface size has changed to "
251 + width
252 + " x "
253 + height);
254 flutterRenderer.surfaceChanged(width, height);
255 }
256
257 // FlutterRenderer must be non-null.
258 private void disconnectSurfaceFromRenderer() {
259 if (flutterRenderer == null) {
260 throw new IllegalStateException(
261 "disconnectSurfaceFromRenderer() should only be called when flutterRenderer is"
262 + " non-null.");
263 }
264
265 flutterRenderer.stopRenderingToSurface();
266 if (renderSurface != null) {
267 renderSurface.release();
268 renderSurface = null;
269 }
270 }
271}
static void v(@NonNull String tag, @NonNull String message)
Definition: Log.java:40
static void w(@NonNull String tag, @NonNull String message)
Definition: Log.java:76
VkSurfaceKHR surface
Definition: main.cc:49
void attachToRenderer(@NonNull FlutterRenderer renderer)
static bool init()
#define TAG()
int32_t height
int32_t width