From 983b6e198d584b386d53846d5c9ade26820c3529 Mon Sep 17 00:00:00 2001 From: Dan Field Date: Fri, 12 Jun 2020 15:12:25 -0700 Subject: [PATCH] Call Shell::NotifyLowMemory when backgrounded/memory pressure occurs on Android (#19026) * Reland "Call Shell::NotifyLowMemoryWarning on Android Trim and LowMemory events (#18979)" (#19023)" This reverts commit 0a852d8ad7e0b132d86c0a604f2c41a110f2b3b6. --- shell/platform/android/android_shell_holder.cc | 4 ++++ shell/platform/android/android_shell_holder.h | 2 ++ .../FlutterActivityAndFragmentDelegate.java | 4 ++++ .../io/flutter/embedding/engine/FlutterJNI.java | 16 ++++++++++++++++ .../embedding/engine/dart/DartExecutor.java | 13 +++++++++++++ .../android/io/flutter/view/FlutterView.java | 1 + .../android/platform_view_android_jni_impl.cc | 11 +++++++++++ .../FlutterActivityAndFragmentDelegateTest.java | 14 +++++++++++--- .../embedding/engine/dart/DartExecutorTest.java | 11 +++++++++++ 9 files changed, 73 insertions(+), 3 deletions(-) diff --git a/shell/platform/android/android_shell_holder.cc b/shell/platform/android/android_shell_holder.cc index f2f39d4ca..e99c76e96 100644 --- a/shell/platform/android/android_shell_holder.cc +++ b/shell/platform/android/android_shell_holder.cc @@ -181,4 +181,8 @@ fml::WeakPtr AndroidShellHolder::GetPlatformView() { return platform_view_; } +void AndroidShellHolder::NotifyLowMemoryWarning() { + FML_DCHECK(shell_); + shell_->NotifyLowMemoryWarning(); +} } // namespace flutter diff --git a/shell/platform/android/android_shell_holder.h b/shell/platform/android/android_shell_holder.h index 9ebd3c7fe..107e93f18 100644 --- a/shell/platform/android/android_shell_holder.h +++ b/shell/platform/android/android_shell_holder.h @@ -40,6 +40,8 @@ class AndroidShellHolder { void UpdateAssetManager(fml::RefPtr asset_manager); + void NotifyLowMemoryWarning(); + private: const flutter::Settings settings_; const std::shared_ptr jni_facade_; diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index 93ff3e716..b48ba568f 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -629,6 +629,9 @@ import java.util.Arrays; void onTrimMemory(int level) { ensureAlive(); if (flutterEngine != null) { + // This is always an indication that the Dart VM should collect memory + // and free any unneeded resources. + flutterEngine.getDartExecutor().notifyLowMemoryWarning(); // Use a trim level delivered while the application is running so the // framework has a chance to react to the notification. if (level == TRIM_MEMORY_RUNNING_LOW) { @@ -651,6 +654,7 @@ import java.util.Arrays; void onLowMemory() { Log.v(TAG, "Forwarding onLowMemory() to FlutterEngine."); ensureAlive(); + flutterEngine.getDartExecutor().notifyLowMemoryWarning(); flutterEngine.getSystemChannel().sendMemoryPressureWarning(); } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 8ce1e0146..397c99e41 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -845,6 +845,22 @@ public class FlutterJNI { // TODO(mattcarroll): determine if this is nonull or nullable private native Bitmap nativeGetBitmap(long nativePlatformViewId); + /** + * Notifies the Dart VM of a low memory event, or that the application is in a state such that now + * is an appropriate time to free resources, such as going to the background. + * + *

This is distinct from sending a SystemChannel message about low memory, which only notifies + * the running Flutter application. + */ + @UiThread + public void notifyLowMemoryWarning() { + ensureRunningOnMainThread(); + ensureAttachedToNative(); + nativeNotifyLowMemoryWarning(nativePlatformViewId); + } + + private native void nativeNotifyLowMemoryWarning(long nativePlatformViewId); + private void ensureRunningOnMainThread() { if (Looper.myLooper() != mainLooper) { throw new RuntimeException( diff --git a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java index fd8061325..13b3ff05a 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java +++ b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java @@ -232,6 +232,19 @@ public class DartExecutor implements BinaryMessenger { } } + /** + * Notify the Dart VM of a low memory event, or that the application is in a state such that now + * is an appropriate time to free resources, such as going to the background. + * + *

This does not notify a Flutter application about memory pressure. For that, use the {@link + * SystemChannel#sendMemoryPressureWarning}. + */ + public void notifyLowMemoryWarning() { + if (flutterJNI.isAttached()) { + flutterJNI.notifyLowMemoryWarning(); + } + } + /** * Configuration options that specify which Dart entrypoint function is executed and where to find * that entrypoint and other assets required for Dart execution. diff --git a/shell/platform/android/io/flutter/view/FlutterView.java b/shell/platform/android/io/flutter/view/FlutterView.java index d5c79c9eb..e01f97530 100644 --- a/shell/platform/android/io/flutter/view/FlutterView.java +++ b/shell/platform/android/io/flutter/view/FlutterView.java @@ -320,6 +320,7 @@ public class FlutterView extends SurfaceView } public void onMemoryPressure() { + mNativeView.getFlutterJNI().notifyLowMemoryWarning(); systemChannel.sendMemoryPressureWarning(); } diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 6de577e83..4e2d0db99 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -438,6 +438,12 @@ static void InvokePlatformMessageEmptyResponseCallback(JNIEnv* env, ); } +static void NotifyLowMemoryWarning(JNIEnv* env, + jobject obj, + jlong shell_holder) { + ANDROID_SHELL_HOLDER->NotifyLowMemoryWarning(); +} + static jboolean FlutterTextUtilsIsEmoji(JNIEnv* env, jobject obj, jint codePoint) { @@ -508,6 +514,11 @@ bool RegisterApi(JNIEnv* env) { .fnPtr = reinterpret_cast( &InvokePlatformMessageEmptyResponseCallback), }, + { + .name = "nativeNotifyLowMemoryWarning", + .signature = "(J)V", + .fnPtr = reinterpret_cast(&NotifyLowMemoryWarning), + }, // Start of methods from FlutterView { diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java index e024f2530..5c6198f89 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterActivityAndFragmentDelegateTest.java @@ -1,6 +1,6 @@ package io.flutter.embedding.android; -import static android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; +import static android.content.ComponentCallbacks2.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.mockito.Matchers.any; @@ -442,7 +442,7 @@ public class FlutterActivityAndFragmentDelegateTest { } @Test - public void itSendsMessageOverSystemChannelWhenToldToTrimMemory() { + public void itNotifiesDartExecutorAndSendsMessageOverSystemChannelWhenToldToTrimMemory() { // Create the real object that we're testing. FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost); @@ -451,14 +451,21 @@ public class FlutterActivityAndFragmentDelegateTest { delegate.onAttach(RuntimeEnvironment.application); // Emulate the host and call the method that we expect to be forwarded. + delegate.onTrimMemory(TRIM_MEMORY_RUNNING_MODERATE); delegate.onTrimMemory(TRIM_MEMORY_RUNNING_LOW); + delegate.onTrimMemory(TRIM_MEMORY_RUNNING_CRITICAL); + delegate.onTrimMemory(TRIM_MEMORY_BACKGROUND); + delegate.onTrimMemory(TRIM_MEMORY_COMPLETE); + delegate.onTrimMemory(TRIM_MEMORY_MODERATE); + delegate.onTrimMemory(TRIM_MEMORY_UI_HIDDEN); // Verify that the call was forwarded to the engine. + verify(mockFlutterEngine.getDartExecutor(), times(7)).notifyLowMemoryWarning(); verify(mockFlutterEngine.getSystemChannel(), times(1)).sendMemoryPressureWarning(); } @Test - public void itSendsMessageOverSystemChannelWhenInformedOfLowMemory() { + public void itNotifiesDartExecutorAndSendsMessageOverSystemChannelWhenInformedOfLowMemory() { // Create the real object that we're testing. FlutterActivityAndFragmentDelegate delegate = new FlutterActivityAndFragmentDelegate(mockHost); @@ -470,6 +477,7 @@ public class FlutterActivityAndFragmentDelegateTest { delegate.onLowMemory(); // Verify that the call was forwarded to the engine. + verify(mockFlutterEngine.getDartExecutor(), times(1)).notifyLowMemoryWarning(); verify(mockFlutterEngine.getSystemChannel(), times(1)).sendMemoryPressureWarning(); } diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java index a17093686..6470651db 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/dart/DartExecutorTest.java @@ -6,6 +6,7 @@ import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import android.content.res.AssetManager; import io.flutter.embedding.engine.FlutterJNI; @@ -38,4 +39,14 @@ public class DartExecutorTest { verify(fakeFlutterJni, times(1)) .dispatchPlatformMessage(eq("fake_channel"), eq(fakeMessage), anyInt(), anyInt()); } + + @Test + public void itNotifiesLowMemoryWarning() { + FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); + when(mockFlutterJNI.isAttached()).thenReturn(true); + + DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class)); + dartExecutor.notifyLowMemoryWarning(); + verify(mockFlutterJNI, times(1)).notifyLowMemoryWarning(); + } } -- GitLab