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

[android] Fixes 'drawRenderNode called on a context with no surface' crash #33655

Merged
merged 1 commit into from
Jun 13, 2022

Conversation

0xZOne
Copy link
Member

@0xZOne 0xZOne commented May 27, 2022

When a memory pressure warning is received and the level equal {@code ComponentCallbacks2.TRIM_MEMORY_COMPLETE}, the Android system release the underlying surface. If we continue to use the surface (e.g., call lockHardwareCanvas), a crash occurs, and we found that this crash appeared on Android 10 and above.

Steps to reproduce:

  1. Open a PlatformView page in your app;
  2. Switch your app to the background;
  3. Simulate low memory : adb shell am send-trim-memory <your_package_name_here> COMPLETE;
  4. Bring your app to the foreground;
  5. Then crash

Here our workaround is to recreate the surface before using it.

Fixes flutter/flutter#103870

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on
    writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Copy link
Member Author

@0xZOne 0xZOne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The systems we can reproduce locally are Android 10, 11, and 12.
Android 8.1 and 9 are only present in the crash info collection system, probably for different reasons.

if (!flutterJNI.isAttached()) {
return;
}
Log.v(TAG, "Releasing a SurfaceTexture (" + id + ").");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this log - it's not really useful to end developers and will just end up adding to noise.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering the complexity of the finalize method, e.g., resurrection, I do not modify its logic here.

I'll try to replace the finalize method with PhantomReferece in combination with ReferenceQueue in a new PR later.

@@ -860,6 +862,7 @@ void onLowMemory() {
ensureAlive();
flutterEngine.getDartExecutor().notifyLowMemoryWarning();
flutterEngine.getSystemChannel().sendMemoryPressureWarning();
flutterEngine.getRenderer().onTrimMemory(TRIM_MEMORY_COMPLETE);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weird. there's no call to delegate.onLowMemory from io.flutter.embedding.android.FlutterActivity, but there's one from io.flutter.embedding.android.FlutterFragment. This seems wrong. Do we know why that's the case? cc @dnfield

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Activity is using onTrimMemory.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, onTrimMemory is preferable over onLowMemory - whenever onLowMemory will get called, onTrimMemory will also get called (right before or right after, can't remember).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the Android documentation, onLowMemory is only used for API levels lower than 14:

To support API levels lower than 14, you can use the ComponentCallbacks.onLowMemory() method as a fallback that's roughly equivalent to the ComponentCallbacks2#TRIM_MEMORY_COMPLETE level.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should override onLowMemory in io.flutter.embedding.android.FlutterActivity class.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's confusing that onLowMemory is called from FlutterFragment but not FlutterActivity. We don't support API level 14, so should we remove all instances of onLowMemory?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's confusing that onLowMemory is called from FlutterFragment but not FlutterActivity. We don't support API level 14, so should we remove all instances of onLowMemory?

done.

Copy link
Member Author

@0xZOne 0xZOne left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove the unnecessary log

@0xZOne 0xZOne requested review from blasten and dnfield June 2, 2022 23:40
@@ -200,34 +241,30 @@ protected void finalize() throws Throwable {
return;
}

handler.post(new SurfaceTextureFinalizerRunnable(id, flutterJNI));
handler.post(
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ColdPaleLight, @jason-simmons I was going over the PR ce8fe8b

Can you comment on why the PR posted a task to the main looper instead of just running directly?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because 'unregisterTexture' must be called on the main thread. And 'finalize' method is called on 'Finalizer' thread.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like there's a race when reading released, or when unregisterTexture is called?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh, I see, here we need to make sure that the finalizer thread can see changes of released value (for example: use keyword volatile). Or we can defer released check to the main thread. WDYT?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure, deferring sounds good. Also, can we add comments about what is happening here, why we need all of this? and clarify that this is a case where someone forgot to do the right thing. flutter/flutter#88571

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The finalize() execution is not guaranteed. Can we consider using ReferenceQueue here?

@@ -44,6 +46,10 @@ public class FlutterRenderer implements TextureRegistry {
private boolean isDisplayingFlutterUi = false;
private Handler handler = new Handler();

@NonNull
private final Set<TextureRegistry.OnLowMemoryListener> onLowMemoryListeners =
new CopyOnWriteArraySet<>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this being accessed from different threads?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is still unresolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one has been solved.

// surface. If we continue to use the surface (e.g., call lockHardwareCanvas), a crash
// occurs, and we found that this crash appeared on Android10 and above.
//
// Here our workaround is to recreate the surface before using it.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: also point to the issue this is fixing

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@@ -91,6 +97,19 @@ public void removeIsDisplayingFlutterUiListener(@NonNull FlutterUiDisplayListene
flutterJNI.removeIsDisplayingFlutterUiListener(listener);
}

/** Adds a listener that is invoked when a memory pressure warning was forward. */
public void addOnLowMemoryListener(@NonNull OnLowMemoryListener listener) {
Copy link

@blasten blasten Jun 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make this private or package-only, and add VisibleForTesting?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

* Removes a {@link OnLowMemoryListener} that was added with {@link
* #addOnLowMemoryListener(OnLowMemoryListener)}.
*/
public void removeOnLowMemoryListener(@NonNull OnLowMemoryListener listener) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment about visibility

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

/** Listener invoked when a memory pressure warning was forward. */
interface OnLowMemoryListener {
/** This method will be invoked when a memory pressure warning was forward. */
void onLowMemory(int level);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be called onTrimMemory?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@@ -55,4 +65,10 @@ interface OnFrameConsumedListener {
*/
void onFrameConsumed();
}

/** Listener invoked when a memory pressure warning was forward. */
interface OnLowMemoryListener {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be called OnTrimMemoryListener?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@@ -45,6 +52,9 @@ interface SurfaceTextureEntry {

/** Set a listener that will be notified when the most recent image has been consumed. */
default void setOnFrameConsumedListener(@Nullable OnFrameConsumedListener listener) {}

/** Set a listener that will be notified when a memory pressure warning was forward. */
default void setOnLowMemoryListener(@Nullable OnLowMemoryListener listener) {}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this be called setOnTrimMemoryListener?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@0xZOne 0xZOne force-pushed the task/issue_103870 branch 2 times, most recently from a6179a0 to 96e6410 Compare June 4, 2022 17:59
@0xZOne 0xZOne requested a review from blasten June 4, 2022 18:12
@0xZOne 0xZOne marked this pull request as draft June 6, 2022 08:13
@flutter-dashboard
Copy link

This pull request has been changed to a draft. The currently pending flutter-gold status will not be able to resolve until a new commit is pushed or the change is marked ready for review again.

engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 13, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 13, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 13, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 13, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 13, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Jun 14, 2022
@GabrielBB
Copy link

Definately this commit is must needed as hotfix. @dnfield Could you forward this as hotfix to stable, 'cause it affects lots of low end devices with crashes.

@dnfield Any plans to send this to hotfix this week ? 👀

@dnfield
Copy link
Contributor

dnfield commented Jun 15, 2022

I think it would be good to at least see this pass Google's internal testing before requesting a hotfix for it.

houhuayong pushed a commit to houhuayong/engine that referenced this pull request Jun 21, 2022
@dnfield
Copy link
Contributor

dnfield commented Jun 21, 2022

This is undergoing testing internally via cl/456284311

If that lands without issue I'll file a CP request.

@dnfield
Copy link
Contributor

dnfield commented Jun 28, 2022

This has passed internal tests - filed flutter/flutter#106746

CaseyHillers pushed a commit to CaseyHillers/engine that referenced this pull request Jun 29, 2022
CaseyHillers pushed a commit that referenced this pull request Jun 30, 2022
…crash (#33655) (#34400)

Fixes cherry-pick conflict for deleted test, see
031fa76#diff-553d8d6ccf46421df54ff1b840606a323494ca2c73f3ce1731169c4d44504b2fL866

Co-authored-by: Rulong Chen(陈汝龙) <rulong.crl@alibaba-inc.com>
@cclink
Copy link

cclink commented Jul 1, 2022

When we could get this fix in stable channel?

@dnfield
Copy link
Contributor

dnfield commented Jul 1, 2022

The cherry pick has landed, but it will probably be a little bit at this point before it's propagated (next week is a US holiday and many people are taking vacation).

@CaseyHillers
Copy link
Contributor

This shipped an hour ago in the 3.0.4 stable. flutter upgrade will pull this fix in.

@dnfield
Copy link
Contributor

dnfield commented Jul 1, 2022

Oh, sorry, forgot that @CaseyHillers was on the job and he gets stuff done.

@Paitomax
Copy link

Paitomax commented Jul 1, 2022

I tested with version 3.0.4 and everything is fine, the error does not happen.

@0xZOne and everyone else who worked on this fix, thanks.

@raywoocn
Copy link

The problem is still there with flutter V3.3.2.

@weicheng59
Copy link

I also experienced this problem with 3.0.4,3.0.5,3.3.4. It's not reproducible with
Simulate low memory : adb shell am send-trim-memory <your_package_name_here> COMPLETE;
for most of our test phones but reproducible 100% with vivo x80 phone(android 12) without the need to simulate low memory.
We also notice around 2% of our user are having the same exception from bugly.
It happened with both Webview and Platformview.
I am willing to provide more details if anyone want to help out because this issue block our android release.

@pakeng
Copy link

pakeng commented Jan 9, 2023

@weicheng59 你们解决了这个问题吗

@pakeng
Copy link

pakeng commented Jan 9, 2023

@raywoocn Is any fix ?

@weicheng59
Copy link

@pakeng we avoid it by removing the platform view when the app go to background and rebuild it when the app go to foreground. You can get messages of app going background and foreground by having android native code send them to you. After this work around, no more crashes.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
platform-android waiting for tree to go green This PR is approved and tested, but waiting for the tree to be green to land.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Android] Crash when user return to an already opened app with google_mobile_ads