Add SurfaceProducer.Callback lifecycle hooks (flutter/engine#53280)

Work towards https://github.com/flutter/flutter/issues/148417.
This commit is contained in:
Matan Lurey 2024-06-24 08:06:56 -07:00 committed by GitHub
parent 9430dfd361
commit 233edc0b8f
12 changed files with 143 additions and 26 deletions

2
DEPS
View File

@ -785,7 +785,7 @@ deps = {
'packages': [
{
'package': 'flutter/android/embedding_bundle',
'version': 'last_updated:2023-08-11T11:35:44-0700'
'version': 'last_updated:2024-06-12T14:15:49-0700'
}
],
'condition': 'download_android_deps',

View File

@ -374,6 +374,7 @@ embedding_dependencies_jars = [
"//third_party/android_embedding_dependencies/lib/lifecycle-common-java8-2.2.0.jar",
"//third_party/android_embedding_dependencies/lib/lifecycle-livedata-2.0.0.jar",
"//third_party/android_embedding_dependencies/lib/lifecycle-livedata-core-2.0.0.jar",
"//third_party/android_embedding_dependencies/lib/lifecycle-process-2.2.0.jar",
"//third_party/android_embedding_dependencies/lib/lifecycle-runtime-2.2.0.jar",
"//third_party/android_embedding_dependencies/lib/lifecycle-viewmodel-2.1.0.jar",
"//third_party/android_embedding_dependencies/lib/loader-1.0.0.jar",

View File

@ -62,4 +62,3 @@ android {
implementation "org.mockito:mockito-android:$mockitoVersion"
}
}

View File

@ -24,6 +24,9 @@ import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.ProcessLifecycleOwner;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.view.TextureRegistry;
@ -78,6 +81,8 @@ public class FlutterRenderer implements TextureRegistry {
private final Set<WeakReference<TextureRegistry.OnTrimMemoryListener>> onTrimMemoryListeners =
new HashSet<>();
@NonNull private final List<ImageReaderSurfaceProducer> imageReaderProducers = new ArrayList<>();
@NonNull
private final FlutterUiDisplayListener flutterUiDisplayListener =
new FlutterUiDisplayListener() {
@ -95,6 +100,20 @@ public class FlutterRenderer implements TextureRegistry {
public FlutterRenderer(@NonNull FlutterJNI flutterJNI) {
this.flutterJNI = flutterJNI;
this.flutterJNI.addIsDisplayingFlutterUiListener(flutterUiDisplayListener);
ProcessLifecycleOwner.get()
.getLifecycle()
.addObserver(
new DefaultLifecycleObserver() {
@Override
public void onResume(@NonNull LifecycleOwner owner) {
Log.v(TAG, "onResume called; notifying SurfaceProducers");
for (ImageReaderSurfaceProducer producer : imageReaderProducers) {
if (producer.callback != null) {
producer.callback.onSurfaceCreated();
}
}
}
});
}
/**
@ -197,6 +216,7 @@ public class FlutterRenderer implements TextureRegistry {
final ImageReaderSurfaceProducer producer = new ImageReaderSurfaceProducer(id);
registerImageTexture(id, producer);
addOnTrimMemoryListener(producer);
imageReaderProducers.add(producer);
Log.v(TAG, "New ImageReaderSurfaceProducer ID: " + id);
entry = producer;
} else {
@ -453,6 +473,7 @@ public class FlutterRenderer implements TextureRegistry {
new HashMap<ImageReader, PerImageReader>();
private PerImage lastDequeuedImage = null;
private PerImageReader lastReaderDequeuedFrom = null;
private Callback callback = null;
/** Internal class: state held per Image produced by ImageReaders. */
private class PerImage {
@ -673,11 +694,15 @@ public class FlutterRenderer implements TextureRegistry {
}
cleanup();
createNewReader = true;
if (this.callback != null) {
this.callback.onSurfaceDestroyed();
}
}
private void releaseInternal() {
cleanup();
released = true;
imageReaderProducers.remove(this);
}
private void cleanup() {
@ -732,6 +757,11 @@ public class FlutterRenderer implements TextureRegistry {
this.id = id;
}
@Override
public void setCallback(Callback callback) {
this.callback = callback;
}
@Override
public long id() {
return id;

View File

@ -55,6 +55,11 @@ final class SurfaceTextureSurfaceProducer
released = true;
}
@Override
public void setCallback(Callback callback) {
// Intentionally blank: SurfaceTextures don't get platform notifications or cleanup.
}
@Override
@NonNull
public SurfaceTexture getSurfaceTexture() {

View File

@ -902,6 +902,7 @@ public class FlutterView extends SurfaceView
throw new UnsupportedOperationException("Image textures are not supported in this mode.");
}
@NonNull
@Override
public SurfaceProducer createSurfaceProducer() {
throw new UnsupportedOperationException(

View File

@ -62,7 +62,7 @@ public interface TextureRegistry {
/** @return The identity of this texture. */
long id();
/** Deregisters and releases all resources . */
/** De-registers and releases all resources . */
void release();
}
@ -79,18 +79,52 @@ public interface TextureRegistry {
int getHeight();
/**
* Get a Surface that can be used to update the texture contents.
* Direct access to the surface object.
*
* <p>NOTE: You should not cache the returned surface but instead invoke getSurface each time
* you need to draw. The surface may change when the texture is resized or has its format
* <p>When using this API, you will usually need to implement {@link SurfaceProducer.Callback}
* and provide it to {@link #setCallback(Callback)} in order to be notified when an existing
* surface has been destroyed (such as when the application goes to the background) or a new
* surface has been created (such as when the application is resumed back to the foreground).
*
* <p>NOTE: You should not cache the returned surface but instead invoke {@code getSurface} each
* time you need to draw. The surface may change when the texture is resized or has its format
* changed.
*
* @return a Surface to use for a drawing target for various APIs.
*/
Surface getSurface();
/**
* Sets a callback that is notified when a previously created {@link Surface} returned by {@link
* SurfaceProducer#getSurface()} is no longer valid, either due to being destroyed or being
* changed.
*
* @param callback The callback to notify, or null to remove the callback.
*/
void setCallback(Callback callback);
/** Callback invoked by {@link #setCallback(Callback)}. */
interface Callback {
/**
* Invoked when a previous surface is now invalid and a new surface is now available.
*
* <p>Typically plugins will use this callback as a signal to redraw, such as due to the
* texture being resized, the format being changed, or the application being resumed after
* being suspended in the background.
*/
void onSurfaceCreated();
/**
* Invoked when a previous surface is now invalid.
*
* <p>Typically plugins will use this callback as a signal to release resources.
*/
void onSurfaceDestroyed();
}
/** This method is not officially part of the public API surface and will be deprecated. */
void scheduleFrame();
};
}
/** A registry entry for a managed SurfaceTexture. */
@Keep
@ -144,7 +178,7 @@ public interface TextureRegistry {
* @return Image or null.
*/
@Nullable
public Image acquireLatestImage();
Image acquireLatestImage();
}
@Keep
@ -155,6 +189,6 @@ public interface TextureRegistry {
* @return SurfaceTexture.
*/
@NonNull
public SurfaceTexture getSurfaceTexture();
SurfaceTexture getSurfaceTexture();
}
}

View File

@ -23,6 +23,8 @@ import android.media.Image;
import android.os.Looper;
import android.view.Surface;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleRegistry;
import androidx.lifecycle.ProcessLifecycleOwner;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import io.flutter.embedding.android.FlutterActivity;
@ -728,8 +730,46 @@ public class FlutterRendererTest {
}
@Test
public void CanLaunchActivityUsingFlutterEngine() {
// This is a placeholder test that will be used to test lifecycle events w/ SurfaceProducer.
scenarioRule.getScenario().moveToState(Lifecycle.State.RESUMED);
public void ImageReaderSurfaceProducerIsDestroyedOnTrimMemory() {
FlutterRenderer flutterRenderer = engineRule.getFlutterEngine().getRenderer();
TextureRegistry.SurfaceProducer producer = flutterRenderer.createSurfaceProducer();
// Create and set a mock callback.
TextureRegistry.SurfaceProducer.Callback callback =
mock(TextureRegistry.SurfaceProducer.Callback.class);
producer.setCallback(callback);
// Trim memory.
((FlutterRenderer.ImageReaderSurfaceProducer) producer).onTrimMemory(40);
// Verify.
verify(callback).onSurfaceDestroyed();
}
@Test
public void ImageReaderSurfaceProducerIsCreatedOnLifecycleResume() throws Exception {
FlutterRenderer flutterRenderer = engineRule.getFlutterEngine().getRenderer();
TextureRegistry.SurfaceProducer producer = flutterRenderer.createSurfaceProducer();
// Create a callback.
CountDownLatch latch = new CountDownLatch(1);
TextureRegistry.SurfaceProducer.Callback callback =
new TextureRegistry.SurfaceProducer.Callback() {
@Override
public void onSurfaceCreated() {
latch.countDown();
}
@Override
public void onSurfaceDestroyed() {}
};
producer.setCallback(callback);
// Trigger a resume.
((LifecycleRegistry) ProcessLifecycleOwner.get().getLifecycle())
.setCurrentState(Lifecycle.State.RESUMED);
// Verify.
latch.await();
}
}

View File

@ -1582,13 +1582,16 @@ public class PlatformViewsControllerTest {
new TextureRegistry() {
public void TextureRegistry() {}
@NonNull
@Override
public SurfaceTextureEntry createSurfaceTexture() {
return registerSurfaceTexture(mock(SurfaceTexture.class));
}
@NonNull
@Override
public SurfaceTextureEntry registerSurfaceTexture(SurfaceTexture surfaceTexture) {
public SurfaceTextureEntry registerSurfaceTexture(
@NonNull SurfaceTexture surfaceTexture) {
return new SurfaceTextureEntry() {
@NonNull
@Override
@ -1606,6 +1609,7 @@ public class PlatformViewsControllerTest {
};
}
@NonNull
@Override
public ImageTextureEntry createImageTexture() {
return new ImageTextureEntry() {
@ -1622,9 +1626,13 @@ public class PlatformViewsControllerTest {
};
}
@NonNull
@Override
public SurfaceProducer createSurfaceProducer() {
return new SurfaceProducer() {
@Override
public void setCallback(SurfaceProducer.Callback cb) {}
@Override
public long id() {
return 0;

View File

@ -28,6 +28,7 @@ androidx.lifecycle:lifecycle-common-java8:2.2.0=debugAndroidTestCompileClasspath
androidx.lifecycle:lifecycle-common:2.3.1=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
androidx.lifecycle:lifecycle-livedata-core:2.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
androidx.lifecycle:lifecycle-livedata:2.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
androidx.lifecycle:lifecycle-process:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
androidx.lifecycle:lifecycle-runtime:2.2.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
androidx.lifecycle:lifecycle-viewmodel:2.1.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath
androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugCompileClasspath,debugImplementationDependenciesMetadata,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestRuntimeClasspath,implementationDependenciesMetadata,releaseCompileClasspath,releaseImplementationDependenciesMetadata,releaseRuntimeClasspath,releaseUnitTestCompileClasspath,releaseUnitTestRuntimeClasspath

View File

@ -13,17 +13,19 @@
"url": "https://maven.google.com/androidx/lifecycle/lifecycle-common-java8/2.2.0/lifecycle-common-java8-2.2.0.jar",
"out_file_name": "androidx_lifecycle_common_java8.jar",
"maven_dependency": "androidx.lifecycle:lifecycle-common-java8:2.2.0",
"provides": [
"androidx.lifecycle.DefaultLifecycleObserver"
]
"provides": ["androidx.lifecycle.DefaultLifecycleObserver"]
},
{
"url": "https://maven.google.com/androidx/lifecycle/lifecycle-process/2.2.0/lifecycle-process-2.2.0.aar",
"out_file_name": "androidx_lifecycle_process.aar",
"maven_dependency": "androidx.lifecycle:lifecycle-process:2.2.0",
"provides": ["androidx.lifecycle.ProcessLifecycleOwner"]
},
{
"url": "https://maven.google.com/androidx/lifecycle/lifecycle-runtime/2.2.0/lifecycle-runtime-2.2.0.aar",
"out_file_name": "androidx_lifecycle_runtime.aar",
"maven_dependency": "androidx.lifecycle:lifecycle-runtime:2.2.0",
"provides": [
"androidx.lifecycle.LifecycleRegistry"
]
"provides": ["androidx.lifecycle.LifecycleRegistry"]
},
{
"url": "https://maven.google.com/androidx/fragment/fragment/1.1.0/fragment-1.1.0.aar",
@ -54,17 +56,13 @@
"url": "https://maven.google.com/androidx/tracing/tracing/1.0.0/tracing-1.0.0.aar",
"out_file_name": "androidx_tracing.aar",
"maven_dependency": "androidx.tracing:tracing:1.0.0",
"provides": [
"androidx.tracing.Trace"
]
"provides": ["androidx.tracing.Trace"]
},
{
"url": "https://dl.google.com/android/maven2/androidx/core/core/1.6.0/core-1.6.0.aar",
"out_file_name": "androidx_core.aar",
"maven_dependency": "androidx.core:core:1.6.0",
"provides": [
"androidx.core.view.WindowInsetsControllerCompat"
]
"provides": ["androidx.core.view.WindowInsetsControllerCompat"]
},
{
"url": "https://maven.google.com/androidx/window/window-java/1.0.0-beta04/window-java-1.0.0-beta04.aar",

View File

@ -13,7 +13,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:3.5.0"
classpath "com.android.tools.build:gradle:7.0.2"
}
}