Skip to content

Commit be7db94

Browse files
authored
Add SurfaceProducer.Callback lifecycle hooks (flutter#53280)
Work towards flutter#148417.
1 parent 6523cce commit be7db94

12 files changed

Lines changed: 143 additions & 26 deletions

File tree

DEPS

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -785,7 +785,7 @@ deps = {
785785
'packages': [
786786
{
787787
'package': 'flutter/android/embedding_bundle',
788-
'version': 'last_updated:2023-08-11T11:35:44-0700'
788+
'version': 'last_updated:2024-06-12T14:15:49-0700'
789789
}
790790
],
791791
'condition': 'download_android_deps',

shell/platform/android/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,7 @@ embedding_dependencies_jars = [
374374
"//third_party/android_embedding_dependencies/lib/lifecycle-common-java8-2.2.0.jar",
375375
"//third_party/android_embedding_dependencies/lib/lifecycle-livedata-2.0.0.jar",
376376
"//third_party/android_embedding_dependencies/lib/lifecycle-livedata-core-2.0.0.jar",
377+
"//third_party/android_embedding_dependencies/lib/lifecycle-process-2.2.0.jar",
377378
"//third_party/android_embedding_dependencies/lib/lifecycle-runtime-2.2.0.jar",
378379
"//third_party/android_embedding_dependencies/lib/lifecycle-viewmodel-2.1.0.jar",
379380
"//third_party/android_embedding_dependencies/lib/loader-1.0.0.jar",

shell/platform/android/build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,3 @@ android {
6262
implementation "org.mockito:mockito-android:$mockitoVersion"
6363
}
6464
}
65-

shell/platform/android/io/flutter/embedding/engine/renderer/FlutterRenderer.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
import androidx.annotation.NonNull;
2525
import androidx.annotation.Nullable;
2626
import androidx.annotation.VisibleForTesting;
27+
import androidx.lifecycle.DefaultLifecycleObserver;
28+
import androidx.lifecycle.LifecycleOwner;
29+
import androidx.lifecycle.ProcessLifecycleOwner;
2730
import io.flutter.Log;
2831
import io.flutter.embedding.engine.FlutterJNI;
2932
import io.flutter.view.TextureRegistry;
@@ -78,6 +81,8 @@ public class FlutterRenderer implements TextureRegistry {
7881
private final Set<WeakReference<TextureRegistry.OnTrimMemoryListener>> onTrimMemoryListeners =
7982
new HashSet<>();
8083

84+
@NonNull private final List<ImageReaderSurfaceProducer> imageReaderProducers = new ArrayList<>();
85+
8186
@NonNull
8287
private final FlutterUiDisplayListener flutterUiDisplayListener =
8388
new FlutterUiDisplayListener() {
@@ -95,6 +100,20 @@ public void onFlutterUiNoLongerDisplayed() {
95100
public FlutterRenderer(@NonNull FlutterJNI flutterJNI) {
96101
this.flutterJNI = flutterJNI;
97102
this.flutterJNI.addIsDisplayingFlutterUiListener(flutterUiDisplayListener);
103+
ProcessLifecycleOwner.get()
104+
.getLifecycle()
105+
.addObserver(
106+
new DefaultLifecycleObserver() {
107+
@Override
108+
public void onResume(@NonNull LifecycleOwner owner) {
109+
Log.v(TAG, "onResume called; notifying SurfaceProducers");
110+
for (ImageReaderSurfaceProducer producer : imageReaderProducers) {
111+
if (producer.callback != null) {
112+
producer.callback.onSurfaceCreated();
113+
}
114+
}
115+
}
116+
});
98117
}
99118

100119
/**
@@ -197,6 +216,7 @@ public SurfaceProducer createSurfaceProducer() {
197216
final ImageReaderSurfaceProducer producer = new ImageReaderSurfaceProducer(id);
198217
registerImageTexture(id, producer);
199218
addOnTrimMemoryListener(producer);
219+
imageReaderProducers.add(producer);
200220
Log.v(TAG, "New ImageReaderSurfaceProducer ID: " + id);
201221
entry = producer;
202222
} else {
@@ -453,6 +473,7 @@ final class ImageReaderSurfaceProducer
453473
new HashMap<ImageReader, PerImageReader>();
454474
private PerImage lastDequeuedImage = null;
455475
private PerImageReader lastReaderDequeuedFrom = null;
476+
private Callback callback = null;
456477

457478
/** Internal class: state held per Image produced by ImageReaders. */
458479
private class PerImage {
@@ -673,11 +694,15 @@ public void onTrimMemory(int level) {
673694
}
674695
cleanup();
675696
createNewReader = true;
697+
if (this.callback != null) {
698+
this.callback.onSurfaceDestroyed();
699+
}
676700
}
677701

678702
private void releaseInternal() {
679703
cleanup();
680704
released = true;
705+
imageReaderProducers.remove(this);
681706
}
682707

683708
private void cleanup() {
@@ -732,6 +757,11 @@ private void maybeWaitOnFence(Image image) {
732757
this.id = id;
733758
}
734759

760+
@Override
761+
public void setCallback(Callback callback) {
762+
this.callback = callback;
763+
}
764+
735765
@Override
736766
public long id() {
737767
return id;

shell/platform/android/io/flutter/embedding/engine/renderer/SurfaceTextureSurfaceProducer.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ public void release() {
5555
released = true;
5656
}
5757

58+
@Override
59+
public void setCallback(Callback callback) {
60+
// Intentionally blank: SurfaceTextures don't get platform notifications or cleanup.
61+
}
62+
5863
@Override
5964
@NonNull
6065
public SurfaceTexture getSurfaceTexture() {

shell/platform/android/io/flutter/view/FlutterView.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -902,6 +902,7 @@ public ImageTextureEntry createImageTexture() {
902902
throw new UnsupportedOperationException("Image textures are not supported in this mode.");
903903
}
904904

905+
@NonNull
905906
@Override
906907
public SurfaceProducer createSurfaceProducer() {
907908
throw new UnsupportedOperationException(

shell/platform/android/io/flutter/view/TextureRegistry.java

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ interface TextureEntry {
6262
/** @return The identity of this texture. */
6363
long id();
6464

65-
/** Deregisters and releases all resources . */
65+
/** De-registers and releases all resources . */
6666
void release();
6767
}
6868

@@ -79,18 +79,52 @@ interface SurfaceProducer extends TextureEntry {
7979
int getHeight();
8080

8181
/**
82-
* Get a Surface that can be used to update the texture contents.
82+
* Direct access to the surface object.
8383
*
84-
* <p>NOTE: You should not cache the returned surface but instead invoke getSurface each time
85-
* you need to draw. The surface may change when the texture is resized or has its format
84+
* <p>When using this API, you will usually need to implement {@link SurfaceProducer.Callback}
85+
* and provide it to {@link #setCallback(Callback)} in order to be notified when an existing
86+
* surface has been destroyed (such as when the application goes to the background) or a new
87+
* surface has been created (such as when the application is resumed back to the foreground).
88+
*
89+
* <p>NOTE: You should not cache the returned surface but instead invoke {@code getSurface} each
90+
* time you need to draw. The surface may change when the texture is resized or has its format
8691
* changed.
8792
*
8893
* @return a Surface to use for a drawing target for various APIs.
8994
*/
9095
Surface getSurface();
9196

97+
/**
98+
* Sets a callback that is notified when a previously created {@link Surface} returned by {@link
99+
* SurfaceProducer#getSurface()} is no longer valid, either due to being destroyed or being
100+
* changed.
101+
*
102+
* @param callback The callback to notify, or null to remove the callback.
103+
*/
104+
void setCallback(Callback callback);
105+
106+
/** Callback invoked by {@link #setCallback(Callback)}. */
107+
interface Callback {
108+
/**
109+
* Invoked when a previous surface is now invalid and a new surface is now available.
110+
*
111+
* <p>Typically plugins will use this callback as a signal to redraw, such as due to the
112+
* texture being resized, the format being changed, or the application being resumed after
113+
* being suspended in the background.
114+
*/
115+
void onSurfaceCreated();
116+
117+
/**
118+
* Invoked when a previous surface is now invalid.
119+
*
120+
* <p>Typically plugins will use this callback as a signal to release resources.
121+
*/
122+
void onSurfaceDestroyed();
123+
}
124+
125+
/** This method is not officially part of the public API surface and will be deprecated. */
92126
void scheduleFrame();
93-
};
127+
}
94128

95129
/** A registry entry for a managed SurfaceTexture. */
96130
@Keep
@@ -144,7 +178,7 @@ interface ImageConsumer {
144178
* @return Image or null.
145179
*/
146180
@Nullable
147-
public Image acquireLatestImage();
181+
Image acquireLatestImage();
148182
}
149183

150184
@Keep
@@ -155,6 +189,6 @@ interface GLTextureConsumer {
155189
* @return SurfaceTexture.
156190
*/
157191
@NonNull
158-
public SurfaceTexture getSurfaceTexture();
192+
SurfaceTexture getSurfaceTexture();
159193
}
160194
}

shell/platform/android/test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import android.os.Looper;
2424
import android.view.Surface;
2525
import androidx.lifecycle.Lifecycle;
26+
import androidx.lifecycle.LifecycleRegistry;
27+
import androidx.lifecycle.ProcessLifecycleOwner;
2628
import androidx.test.ext.junit.rules.ActivityScenarioRule;
2729
import androidx.test.ext.junit.runners.AndroidJUnit4;
2830
import io.flutter.embedding.android.FlutterActivity;
@@ -728,8 +730,46 @@ public void SurfaceTextureSurfaceProducerCreatesAConnectedTexture() {
728730
}
729731

730732
@Test
731-
public void CanLaunchActivityUsingFlutterEngine() {
732-
// This is a placeholder test that will be used to test lifecycle events w/ SurfaceProducer.
733-
scenarioRule.getScenario().moveToState(Lifecycle.State.RESUMED);
733+
public void ImageReaderSurfaceProducerIsDestroyedOnTrimMemory() {
734+
FlutterRenderer flutterRenderer = engineRule.getFlutterEngine().getRenderer();
735+
TextureRegistry.SurfaceProducer producer = flutterRenderer.createSurfaceProducer();
736+
737+
// Create and set a mock callback.
738+
TextureRegistry.SurfaceProducer.Callback callback =
739+
mock(TextureRegistry.SurfaceProducer.Callback.class);
740+
producer.setCallback(callback);
741+
742+
// Trim memory.
743+
((FlutterRenderer.ImageReaderSurfaceProducer) producer).onTrimMemory(40);
744+
745+
// Verify.
746+
verify(callback).onSurfaceDestroyed();
747+
}
748+
749+
@Test
750+
public void ImageReaderSurfaceProducerIsCreatedOnLifecycleResume() throws Exception {
751+
FlutterRenderer flutterRenderer = engineRule.getFlutterEngine().getRenderer();
752+
TextureRegistry.SurfaceProducer producer = flutterRenderer.createSurfaceProducer();
753+
754+
// Create a callback.
755+
CountDownLatch latch = new CountDownLatch(1);
756+
TextureRegistry.SurfaceProducer.Callback callback =
757+
new TextureRegistry.SurfaceProducer.Callback() {
758+
@Override
759+
public void onSurfaceCreated() {
760+
latch.countDown();
761+
}
762+
763+
@Override
764+
public void onSurfaceDestroyed() {}
765+
};
766+
producer.setCallback(callback);
767+
768+
// Trigger a resume.
769+
((LifecycleRegistry) ProcessLifecycleOwner.get().getLifecycle())
770+
.setCurrentState(Lifecycle.State.RESUMED);
771+
772+
// Verify.
773+
latch.await();
734774
}
735775
}

shell/platform/android/test/io/flutter/plugin/platform/PlatformViewsControllerTest.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1582,13 +1582,16 @@ private static void attachToFlutterView(
15821582
new TextureRegistry() {
15831583
public void TextureRegistry() {}
15841584

1585+
@NonNull
15851586
@Override
15861587
public SurfaceTextureEntry createSurfaceTexture() {
15871588
return registerSurfaceTexture(mock(SurfaceTexture.class));
15881589
}
15891590

1591+
@NonNull
15901592
@Override
1591-
public SurfaceTextureEntry registerSurfaceTexture(SurfaceTexture surfaceTexture) {
1593+
public SurfaceTextureEntry registerSurfaceTexture(
1594+
@NonNull SurfaceTexture surfaceTexture) {
15921595
return new SurfaceTextureEntry() {
15931596
@NonNull
15941597
@Override
@@ -1606,6 +1609,7 @@ public void release() {}
16061609
};
16071610
}
16081611

1612+
@NonNull
16091613
@Override
16101614
public ImageTextureEntry createImageTexture() {
16111615
return new ImageTextureEntry() {
@@ -1622,9 +1626,13 @@ public void pushImage(Image image) {}
16221626
};
16231627
}
16241628

1629+
@NonNull
16251630
@Override
16261631
public SurfaceProducer createSurfaceProducer() {
16271632
return new SurfaceProducer() {
1633+
@Override
1634+
public void setCallback(SurfaceProducer.Callback cb) {}
1635+
16281636
@Override
16291637
public long id() {
16301638
return 0;

testing/scenario_app/android/app/gradle.lockfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ androidx.lifecycle:lifecycle-common-java8:2.2.0=debugAndroidTestCompileClasspath
2828
androidx.lifecycle:lifecycle-common:2.3.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
2929
androidx.lifecycle:lifecycle-livedata-core:2.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
3030
androidx.lifecycle:lifecycle-livedata:2.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
31+
androidx.lifecycle:lifecycle-process:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
3132
androidx.lifecycle:lifecycle-runtime:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
3233
androidx.lifecycle:lifecycle-viewmodel:2.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
3334
androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath

0 commit comments

Comments
 (0)