Closed timbotimbo closed 1 month ago
I am not familiar with Flutter unity, but this might help, just make sure targetSdkVersion
/ compileSdkVersion
hit at least 33.
Additional Unity configuration for Android
- In Unity, go to
File -> Build Settings
, and enable theExport project
tickbox- In Unity, go to
File -> Build Settings -> Player Settings -> Other settings
, and make the d following changes:
- Find
Target API level
: it's not required, but recommended, to set this to the same target API level of your Flutter project's android app (this is usually set in<your flutter project>/android/app/build.grade
astargetSdkVersion
)
Target and compile SDK are at 34.
Recent Unity versions that have a predictive back support
toggle in the settings don't help either.
But this only happens on Android 12 and lower, which don't even support predictive back.
Given that it doesn't happen on earlier flutter versions, something new in the platform channel somehow ends up creating a message that Unity catches and tries to resolve.
Based on your logs:
E/Unity (26660): AndroidJavaException: java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback;
E/Unity (26660): java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback;
E/Unity (26660): at java.lang.reflect.Executable.getMethodReturnTypeInternal(Native Method)
E/Unity (26660): at java.lang.reflect.Method.getReturnType(Method.java:148)
E/Unity (26660): at java.lang.Class.getDeclaredMethods(Class.java:1880)
E/Unity (26660): at com.unity3d.player.ReflectionHelper.getMethodID(Unknown Source:0)
This code is the problem:
@VisibleForTesting
protected OnBackInvokedCallback getOnBackInvokedCallback() {
return onBackInvokedCallback;
}
Since the getOnBackInvokedCallback
method compiles into the codebase, and Unity sees its return type, which is not available under SDK 33, annotating it with @TargetApi
/ @RequiresApi
might solve the problem. Alternatively, changing private final OnBackInvokedCallback onBackInvokedCallback
to public
and removing getOnBackInvokedCallback
might help.
Anyway flutter_unity_widget uses a very old unity dependecy.
Which method could be patched like this:
protected static Method getMethodID(Class class_, String string, String string2, boolean bl) {
Method method = null;
a a2 = new a(class_, string, string2);
if (ReflectionHelper.a(a2)) {
method = (Method)a2.a;
} else {
Class[] arrclass = ReflectionHelper.a(string2);
float f2 = 0.0f;
while (class_ != null) {
+ try {
for (Method method2 : class_.getDeclaredMethods()) {
float f3;
if (bl != Modifier.isStatic(method2.getModifiers()) || method2.getName().compareTo(string) != 0 || (f3 = ReflectionHelper.a(method2.getReturnType(), method2.getParameterTypes(), arrclass)) <= f2) continue;
method = method2;
f2 = f3;
if (f2 == 1.0f) break;
}
+ } catch (NoClassDefFoundError e) {
+ // Log the error or handle it as needed
+ break;
}
if (f2 == 1.0f || class_.isPrimitive() || class_.isInterface() || class_.equals(Object.class) || class_.equals(Void.TYPE)) break;
class_ = class_.getSuperclass();
}
ReflectionHelper.a(a2, method);
}
if (method == null) {
Object[] arrobject = new Object[4];
arrobject[0] = bl ? "non-static" : "static";
arrobject[1] = string;
arrobject[2] = string2;
arrobject[3] = class_.getName();
throw new NoSuchMethodError(String.format("no %s method with name='%s' signature='%s' in class L%s;", arrobject));
}
return method;
}
Thanks for the hint of what to try.
I haven't used custom engine changes with the flutter repo yet, so that will take some time to figure out.
My sample code uses the alternative embed plugin, which is a lot newer and cleaner.
As far as I know the unity-classes.jar file gets replaced by the export of the unity project that actually gets included in the project. (android/unityLibrary/libs/unity-classes.jar
in the sample code)
However that means it gets recreated on any change in the Unity project. And the internal (compiled) classes aren't a public API so they might change in any Unity update. Which is why I prefer to avoid editing it.
There is a much higher chance of getting Flutter to incorporate a possible bugfix than Unity, so the engine annotations are the next step.
Thanks for the detailed report @timbotimbo Can you try below to see if they help or not ?
android:enableOnBackInvokedCallback
valueAlso, if below issues along with underlying comments help ?
https://github.com/flutter/flutter/issues/109513 https://github.com/flutter/flutter/issues/117061
@darshankawar
android:enableOnBackInvokedCallback="false"
and true.The problem is not with the predictive back action or gesture itself. Just the code added to support it ends up including a class that doesn't exist on Android < 13. And only in this edge case where Unity receives a message and tries to call it using reflection does this exception show up.
Like MaRci002 mentioned above, it is likely something like a missing api annotations such as@TargetApi
and @RequiresApi
.
@timbotimbo Check out Setting up the engine development environment and Compiling the engine and let me know how it goes. If you can confirm that the change suggested above fixes your problem then I'm definitely happy to help us land the change in the engine!
Thanks so much for including all of this detail in the issue, and thanks @maRci002 for jumping on this.
I can reproduce it.
Minimal flutter code:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
static const platform = MethodChannel('samples.flutter.dev/battery');
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () => platform.invokeMethod<int>('getBatteryLevel'),
child: const Text('Get batter level'),
),
),
),
);
}
}
Minimal android code:
package com.example.back;
import android.annotation.TargetApi;
import android.window.OnBackInvokedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import java.lang.reflect.Method;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "samples.flutter.dev/battery";
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler(
(call, result) -> {
getBatteryLevel();
result.success(null);
}
);
}
@TargetApi(33)
@RequiresApi(33)
@VisibleForTesting
protected OnBackInvokedCallback getOnBackInvokedCallback2() {
return null;
}
private void getBatteryLevel() {
try {
final Method method = MainActivity.class.getDeclaredMethod("getOnBackInvokedCallback2");
System.out.println(method.getName() + " -> " + method.getReturnType().getName());
} catch (NoClassDefFoundError e) {
System.err.println("getOnBackInvokedCallback2\n" + e);
} catch (NoSuchMethodException e) {
System.err.println("An error occurred:\n" + e);
}
try {
final Method method = FlutterActivity.class.getDeclaredMethod("getOnBackInvokedCallback");
System.out.println(method.getName() + " -> " + method.getReturnType().getName());
} catch (NoClassDefFoundError e) {
System.err.println("getOnBackInvokedCallback\n" + e);
} catch (NoSuchMethodException e) {
System.err.println("An error occurred:\n" + e);
}
}
}
Log:
getOnBackInvokedCallback2
java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback;
getOnBackInvokedCallback
java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback;
As you can see, I added an extra getOnBackInvokedCallback2
method and it compiles into the codebase even though I used the annotations @TargetApi(33)
and @RequiresApi(33)
. This means that when using java.lang.reflect.Method.getReturnType
, the caller is responsible for guarding it with an SDK version check or using a try-catch block. Therefore, I believe @justinmc, you can remove the regression label.
Class Loading Mechanics: Basically, when we use reflection to dig into methods and their return types, the JVM (or ART on Android) tries to load up every class that's tied to those methods. If the class, like a return type, was added in a newer version of Android and we're running the code on an older version that doesn't have this class, the JVM just can't find it and freaks out, throwing a NoClassDefFoundError
or ClassNotFoundException
.
@maRci002 As a confirmation I just tested this with Unity using my linked sample code.
I ran this It using Flutter 3.13.9, which doesn't (and shouldn't) produce any errors,
Then I added a function like yours to the MainAcitivty.kt
@TargetApi(33)
@RequiresApi(33)
@VisibleForTesting
protected fun getOnBackInvokedCallback2() : OnBackInvokedCallback? {
return null;
}
Unity now triggers AndroidJavaException: java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback;
.
This confirms that Unity always triggered reflection at this point, it just didn't run into this specific error on older Flutter versions.
Since it uses getDeclaredMethods
, just having a function defined can cause this, it doesn't even need to be explicitly called.
If Unity were to call FlutterActivity.class.getDeclaredFields();
on older Flutter versions (for instace 3.16), it would also throw a java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback;
. This error occurs because private final OnBackInvokedCallback onBackInvokedCallback
is available since 3.14.0-7.0.pre.
So, the conclusion is that when we declare a field
or method
with a type that is not available in the SDK, it will throw an error if we try to inspect it via reflection.
From triage: This sounds like a problem that arises from the use of reflection by Unity. If we are mistaken, please reopen and explain.
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v
and a minimal reproduction of the issue.
Description
Since updating to Flutter 3.22 I'm running into an Android exception when embedding Unity into Flutter using either the flutter_unity_widget or flutter_embed_unity plugin. This only happens on Android < 13.
Even though the exception happens in the native Unity code, I believe this is at least partially a Flutter issue as I can pinpoint the exact Flutter commit that introduces it.
This triggers when Augmented Reality is enabled in Unity, and has nothing to do with performing the Predicitve back gesture itself.
It looks like a function with return type
OnBackInvokedCallback
is called from the Flutter internals.Looking up
OnBackInvokedCallback
, it has only been added since API 33 for the predicitive back functionality, and doesn't seem to be backwards compatible.I already tried enabling and disabling predicitve back by adding
android:enableOnBackInvokedCallback="false"
(and "true") to all the androidManifest files in the project.Flutter commit with regression
The crash is introduced on master with Flutter commit commit https://github.com/flutter/flutter/commit/66d1872cd5cd3f4693a0e95a8fd6fadab5801b6b, when The engine PR Platform channel for predictive back in route transitions on android got merged into master. If I checkout Flutter master at the commit before this, the exception doesn't occur.
Reproduction
This
I can only trigger this when enabling Augmented Reality functionality in Unity. If the build does not include AR, or the android device does not support AR, the exception doesn't happen. I assume Google Arcore starts a new Activity or Thread or something that triggers a response from the new Flutter platform channel.
Reproduction devices
Devices that I tested this on:
All devices functioned normally on older Flutter versions like 3.16.9. This only reproduces on 3.22.x and master.
Reproduction steps
Using a combination of Flutter >= 3.22 and Android <13 you will see the platformview freeze at step 2. Any older Flutter version should just work.
If you test this on Flutter 3.16 or 3.19, you need to use Android >= 10, otherwise a Flutter bug causes the platformview to be invisible.
Reproduction on Emulators
I had issues getting AR support to be detected on emulators, which is needed to reproduce this exception.
The attached screen recording shows 2 emulators on mac.
In some cases you might get a purple screen instead of seeing Unity, this is a compaitiblity issue with Unity as it doesn't officially support running on emulators.
Expected result
No exception that Freezes the platformview, just like on Android 13+ or Flutter < 3.22.
Actual result
Unity inside the platform view receives a message that triggers an exception, which crashes Unity.
Code sample
This reproduction project uses the flutter_embed_unity plugin and comes with a prebuilt unity project so that you only need Flutter to run it. This was built using Unity LTS 2022.3.37 released on 09-07-2024, the latest LTS version.
https://github.com/timbotimbo/flutter_embed_unity_repro/tree/ar_crash
Screenshots or Video
The exception crashes Unity, so in the screen recording you will just see Unity freeze.
APi 32 vs API 34 emulator on mac
https://github.com/user-attachments/assets/a9f4f2db-d20a-464a-b29f-2de5329e1a56Logs
Running from Visual Studio code on Mac, on an API 32 Arm64 emulator.
Debug console
``` Launching lib/main.dart on sdk gphone64 arm64 in debug mode... You are applying Flutter's app_plugin_loader Gradle plugin imperatively using the apply script method, which is deprecated and will be removed in a future release. Migrate to applying Gradle plugins with the declarative plugins block: https://flutter.dev/go/flutter-gradle-plugin-apply You are applying Flutter's main Gradle plugin imperatively using the apply script method, which is deprecated and will be removed in a future release. Migrate to applying Gradle plugins with the declarative plugins block: https://flutter.dev/go/flutter-gradle-plugin-apply ✓ Built build/app/outputs/flutter-apk/app-debug.apk D/FlutterEmbedLog(15782): onAttachedToEngine D/FlutterEmbedLog(15782): onAttachedToActivity D/FlutterEmbedLog(15782): Activity resumed, resuming Unity Connecting to VM Service at ws://127.0.0.1:58582/mka4ULEzDh8=/ws D/com.learntoflutter.flutter_embed_unity_android.platformView.UnityViewFactory@7c30631(15782): UnityViewFactory creating view 0 I/IL2CPP (15782): JNI_OnLoad I/FlutterEmbedLog(15782): Attached Unity to view I/FlutterEmbedLog(15782): Attached Unity to new view D/FlutterEmbedLog(15782): UnityPlatformView onFlutterViewAttached I/PlatformViewsController(15782): Hosting view in a virtual display for platform view: 0 I/PlatformViewsController(15782): PlatformView is using SurfaceProducer backend I/flutter (15782): FlutterEmbed: onPlatformViewCreated(0) D/FlutterEmbedLog(15782): UnityPlayerSingleton onWindowVisibilityChanged 0 D/FlutterEmbedLog(15782): UnityPlayerSingleton became visible, so pausing and resuming Unity I/Unity (15782): MemoryManager: Using 'Dynamic Heap' Allocator. W/d_unity_exampl(15782): Accessing hidden method Ljava/lang/invoke/MethodHandles$Lookup;->Flutter Doctor output
Windows ( Flutter master)
``` [√] Flutter (Channel master, 3.24.0-1.0.pre.69, on Microsoft Windows [Version 10.0.19045.4529], locale en-GB) • Flutter version 3.24.0-1.0.pre.69 on channel master at C:\Flutter\flutter_windows_default\flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 515c52679b (2 hours ago), 2024-07-10 03:40:26 -0400 • Engine revision db2b45bea2 • Dart version 3.6.0 (build 3.6.0-22.0.dev) • DevTools version 2.37.1 [√] Windows Version (Installed version of Windows is version 10 or higher) [√] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at C:\Users\XXXXXX\AppData\Local\Android\sdk • Platform android-34, build-tools 34.0.0 • Java binary at: C:\Program Files\Android\Android Studio\jbr\bin\java • Java version OpenJDK Runtime Environment (build 17.0.10+0--11609105) • All Android licenses accepted. [√] Chrome - develop for the web • Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe [√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.9.0) • Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community • Visual Studio Community 2022 version 17.9.34607.119 • Windows 10 SDK version 10.0.22000.0 [√] Android Studio (version 2024.1) • Android Studio at C:\Program Files\Android\Android Studio • Flutter plugin can be installed from: https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 17.0.10+0--11609105) [√] VS Code (version 1.91.0) • VS Code at C:\Users\XXXXXX\AppData\Local\Programs\Microsoft VS Code • Flutter extension version 3.92.0 [√] Connected device (5 available) • SM G780G (mobile) • RF8R8021WSR • android-arm64 • Android 13 (API 33) • sdk gphone64 x86 64 (mobile) • emulator-5554 • android-x64 • Android 14 (API 34) (emulator) • Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version 10.0.19045.4529] • Chrome (web) • chrome • web-javascript • Google Chrome 126.0.6478.127 • Edge (web) • edge • web-javascript • Microsoft Edge 126.0.2592.87 [√] Network resources • All expected network resources are available. • No issues found! ```Mac (Flutter 3.22.2)
``` [✓] Flutter (Channel stable, 3.22.2, on macOS 14.5 23F79 darwin-arm64, locale en-GB) • Flutter version 3.22.2 on channel stable at /Users/XXXXXX/Development/Flutter/3.22.2-stable/flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 761747bfc5 (6 weeks ago), 2024-06-05 22:15:13 +0200 • Engine revision edd8546116 • Dart version 3.4.3 • DevTools version 2.34.3 [✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0) • Android SDK at /Users/XXXXXX/Library/Android/sdk • Platform android-34, build-tools 34.0.0 • Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java • Java version OpenJDK Runtime Environment (build 17.0.10+0-17.0.10b1087.21-11609105) • All Android licenses accepted. [!] Xcode - develop for iOS and macOS ✗ Xcode installation is incomplete; a full installation is necessary for iOS and macOS development. Download at: https://developer.apple.com/xcode/ Or install Xcode via the App Store. Once installed, run: sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer sudo xcodebuild -runFirstLaunch • CocoaPods version 1.15.2 [✓] Chrome - develop for the web • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome [✓] Android Studio (version 2024.1) • Android Studio at /Applications/Android Studio.app/Contents • Flutter plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: 🔨 https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 17.0.10+0-17.0.10b1087.21-11609105) [✓] VS Code (version 1.87.2) • VS Code at /Applications/Visual Studio Code.app/Contents • Flutter extension version 3.84.0 [✓] Connected device (4 available) • sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64 • Android 14 (API 34) (emulator) • sdk gphone64 arm64 (mobile) • emulator-5556 • android-arm64 • Android 12 (API 32) (emulator) • macOS (desktop) • macos • darwin-arm64 • macOS 14.5 23F79 darwin-arm64 • Chrome (web) • chrome • web-javascript • Google Chrome 126.0.6478.127 [✓] Network resources • All expected network resources are available. ! Doctor found issues in 1 category. ```