Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit c74ff37

Browse files
committed
[Android] Fix the issue of blank or frozen pages in shared engine scenarios
1 parent da3d49c commit c74ff37

File tree

4 files changed

+127
-6
lines changed

4 files changed

+127
-6
lines changed

shell/platform/android/io/flutter/embedding/android/FlutterSurfaceView.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import android.view.SurfaceView;
1313
import androidx.annotation.NonNull;
1414
import androidx.annotation.Nullable;
15+
import androidx.annotation.VisibleForTesting;
1516
import io.flutter.Log;
1617
import io.flutter.embedding.engine.renderer.FlutterRenderer;
1718
import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener;
@@ -168,6 +169,10 @@ public FlutterRenderer getAttachedRenderer() {
168169
return flutterRenderer;
169170
}
170171

172+
@VisibleForTesting
173+
/* package */ boolean isSurfaceAvailableForRendering() {
174+
return isSurfaceAvailableForRendering;
175+
}
171176
/**
172177
* Invoked by the owner of this {@code FlutterSurfaceView} when it wants to begin rendering a
173178
* Flutter UI to this {@code FlutterSurfaceView}.
@@ -215,8 +220,6 @@ public void detachFromRenderer() {
215220
disconnectSurfaceFromRenderer();
216221
}
217222

218-
pause();
219-
220223
// Make the SurfaceView invisible to avoid showing a black rectangle.
221224
setAlpha(0.0f);
222225
flutterRenderer.removeIsDisplayingFlutterUiListener(flutterUiDisplayListener);
@@ -248,7 +251,7 @@ public void resume() {
248251

249252
// If we're already attached to an Android window then we're now attached to both a renderer
250253
// and the Android window. We can begin rendering now.
251-
if (isSurfaceAvailableForRendering) {
254+
if (isSurfaceAvailableForRendering()) {
252255
Log.v(
253256
TAG,
254257
"Surface is available for rendering. Connecting FlutterRenderer to Android surface.");

shell/platform/android/io/flutter/embedding/android/FlutterTextureView.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ public FlutterRenderer getAttachedRenderer() {
124124
return flutterRenderer;
125125
}
126126

127+
@VisibleForTesting
128+
/* package */ boolean isSurfaceAvailableForRendering() {
129+
return isSurfaceAvailableForRendering;
130+
}
131+
127132
/**
128133
* Invoked by the owner of this {@code FlutterTextureView} when it wants to begin rendering a
129134
* Flutter UI to this {@code FlutterTextureView}.
@@ -170,8 +175,6 @@ public void detachFromRenderer() {
170175
disconnectSurfaceFromRenderer();
171176
}
172177

173-
pause();
174-
175178
flutterRenderer = null;
176179
} else {
177180
Log.w(TAG, "detachFromRenderer() invoked when no FlutterRenderer was attached.");
@@ -198,7 +201,7 @@ public void resume() {
198201

199202
// If we're already attached to an Android window then we're now attached to both a renderer
200203
// and the Android window. We can begin rendering now.
201-
if (isSurfaceAvailableForRendering) {
204+
if (isSurfaceAvailableForRendering()) {
202205
Log.v(
203206
TAG,
204207
"Surface is available for rendering. Connecting FlutterRenderer to Android surface.");
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package io.flutter.embedding.android;
2+
3+
import static org.mockito.ArgumentMatchers.any;
4+
import static org.mockito.Mockito.mock;
5+
import static org.mockito.Mockito.never;
6+
import static org.mockito.Mockito.spy;
7+
import static org.mockito.Mockito.times;
8+
import static org.mockito.Mockito.verify;
9+
import static org.mockito.Mockito.when;
10+
11+
import android.annotation.TargetApi;
12+
import android.view.Surface;
13+
import android.view.SurfaceHolder;
14+
import androidx.test.core.app.ApplicationProvider;
15+
import androidx.test.ext.junit.runners.AndroidJUnit4;
16+
import io.flutter.embedding.engine.FlutterJNI;
17+
import io.flutter.embedding.engine.renderer.FlutterRenderer;
18+
import org.junit.Test;
19+
import org.junit.runner.RunWith;
20+
import org.robolectric.annotation.Config;
21+
22+
@Config(manifest = Config.NONE)
23+
@RunWith(AndroidJUnit4.class)
24+
@TargetApi(30)
25+
public class FlutterSurfaceViewTest {
26+
@Test
27+
public void itShouldCreateANewSurfaceWhenReattachedAfterDetachingFromRenderer() {
28+
// Consider the following scenario:
29+
// In an add-to-app context, multiple FlutterActivities share the same engine. When navigating
30+
// from FlutterActivity1 to FlutterActivity2, the FlutterView of FlutterActivity1 is detached
31+
// from the engine, and then the FlutterView of FlutterActivity2 is attached to it. Upon
32+
// navigating back to FlutterActivity1 from FlutterActivity2, its FlutterView is re-attached to
33+
// the previously used engine.
34+
35+
// Setup the test.
36+
final FlutterSurfaceView surfaceView =
37+
spy(new FlutterSurfaceView(ApplicationProvider.getApplicationContext()));
38+
39+
FlutterJNI fakeFlutterJNI = mock(FlutterJNI.class);
40+
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
41+
42+
SurfaceHolder fakeSurfaceHolder = mock(SurfaceHolder.class);
43+
Surface fakeSurface = mock(Surface.class);
44+
when(surfaceView.getHolder()).thenReturn(fakeSurfaceHolder);
45+
when(fakeSurfaceHolder.getSurface()).thenReturn(fakeSurface);
46+
when(surfaceView.isSurfaceAvailableForRendering()).thenReturn(true);
47+
when(surfaceView.getWindowToken()).thenReturn(mock(android.os.IBinder.class));
48+
49+
// Execute the behavior under test.
50+
surfaceView.attachToRenderer(flutterRenderer);
51+
52+
// Verify the behavior under test.
53+
verify(fakeFlutterJNI, times(1)).onSurfaceCreated(any(Surface.class));
54+
55+
// Execute the behavior under test.
56+
surfaceView.detachFromRenderer();
57+
58+
// Verify the behavior under test.
59+
verify(fakeFlutterJNI, times(1)).onSurfaceDestroyed();
60+
61+
// Execute the behavior under test.
62+
surfaceView.attachToRenderer(flutterRenderer);
63+
64+
// Verify the behavior under test.
65+
verify(fakeFlutterJNI, never()).onSurfaceWindowChanged(any(Surface.class));
66+
verify(fakeFlutterJNI, times(2)).onSurfaceCreated(any(Surface.class));
67+
}
68+
}

shell/platform/android/test/io/flutter/embedding/android/FlutterTextureViewTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
package io.flutter.embedding.android;
22

3+
import static org.mockito.ArgumentMatchers.any;
34
import static org.mockito.Mockito.mock;
5+
import static org.mockito.Mockito.never;
6+
import static org.mockito.Mockito.spy;
7+
import static org.mockito.Mockito.times;
48
import static org.mockito.Mockito.verify;
9+
import static org.mockito.Mockito.when;
510

611
import android.annotation.TargetApi;
712
import android.graphics.SurfaceTexture;
813
import android.view.Surface;
914
import android.view.TextureView;
1015
import androidx.test.core.app.ApplicationProvider;
1116
import androidx.test.ext.junit.runners.AndroidJUnit4;
17+
import io.flutter.embedding.engine.FlutterJNI;
18+
import io.flutter.embedding.engine.renderer.FlutterRenderer;
1219
import org.junit.Test;
1320
import org.junit.runner.RunWith;
1421
import org.robolectric.annotation.Config;
@@ -30,4 +37,44 @@ public void surfaceTextureListenerReleasesRenderer() {
3037

3138
verify(mockRenderSurface).release();
3239
}
40+
41+
@Test
42+
public void itShouldCreateANewSurfaceWhenReattachedAfterDetachingFromRenderer() {
43+
// Consider the following scenario:
44+
// In an add-to-app context, multiple FlutterActivities share the same engine. When navigating
45+
// from FlutterActivity1 to FlutterActivity2, the FlutterView of FlutterActivity1 is detached
46+
// from the engine, and then the FlutterView of FlutterActivity2 is attached to it. Upon
47+
// navigating back to FlutterActivity1 from FlutterActivity2, its FlutterView is re-attached to
48+
// the previously used engine.
49+
50+
// Setup the test.
51+
final FlutterTextureView textureView =
52+
spy(new FlutterTextureView(ApplicationProvider.getApplicationContext()));
53+
54+
FlutterJNI fakeFlutterJNI = mock(FlutterJNI.class);
55+
FlutterRenderer flutterRenderer = new FlutterRenderer(fakeFlutterJNI);
56+
57+
when(textureView.isSurfaceAvailableForRendering()).thenReturn(true);
58+
when(textureView.getSurfaceTexture()).thenReturn(mock(SurfaceTexture.class));
59+
when(textureView.getWindowToken()).thenReturn(mock(android.os.IBinder.class));
60+
61+
// Execute the behavior under test.
62+
textureView.attachToRenderer(flutterRenderer);
63+
64+
// Verify the behavior under test.
65+
verify(fakeFlutterJNI, times(1)).onSurfaceCreated(any(Surface.class));
66+
67+
// Execute the behavior under test.
68+
textureView.detachFromRenderer();
69+
70+
// Verify the behavior under test.
71+
verify(fakeFlutterJNI, times(1)).onSurfaceDestroyed();
72+
73+
// Execute the behavior under test.
74+
textureView.attachToRenderer(flutterRenderer);
75+
76+
// Verify the behavior under test.
77+
verify(fakeFlutterJNI, never()).onSurfaceWindowChanged(any(Surface.class));
78+
verify(fakeFlutterJNI, times(2)).onSurfaceCreated(any(Surface.class));
79+
}
3380
}

0 commit comments

Comments
 (0)