flutter-unity-view-widget
flutter-unity-view-widget copied to clipboard
AndroidJavaProxy breaks with Unity 2022.2.0+ when using fuw-2022.2.0
Describe the bug
There was a change in Unity 2022.1.7 to 2022.2.0+ in AndroidJNI, which now requires a reference to mUnityPlayer
when creating an AndroidJavaProxy
:
public static IntPtr CreateJavaProxy (AndroidJavaProxy proxy)
{
GCHandle value = GCHandle.Alloc (proxy);
try {
return _AndroidJNIHelper.CreateJavaProxy (Permission.GetActivity ().Get<AndroidJavaObject> ("mUnityPlayer").GetRawObject (), GCHandle.ToIntPtr (value), proxy);
} catch {
value.Free ();
throw;
}
}
Note that Permissions.GetActivity()
will return the currentActivity
from a UnityPlayer
object. When the FlutterUnityWidget is used to launch Unity (see CustomUnityPlayer.kt
) it will initialise UnityPlayer
with MainActivity
. Hence, CreateJavaProxy
will fail to find a reference to mUnityPlayer
and log the following error:
Non-fatal Exception: java.lang.Exception: AndroidJavaException : java.lang.NoSuchFieldError: no "Ljava/lang/Object;" field "mUnityPlayer" in class "Lcom/example/app/MainActivity;" or its superclasses
at com.unity3d.player.UnityPlayer.nativeRender(com.unity3d.player.UnityPlayer)
at com.unity3d.player.UnityPlayer.-$$Nest$mnativeRender(com.unity3d.player.UnityPlayer)
at com.unity3d.player.UnityPlayer$C$a.handleMessage(com.unity3d.player.UnityPlayer$C$a)
at android.os.Handler.dispatchMessage(android.os.Handler)
at android.os.Looper.loopOnce(android.os.Looper)
at android.os.Looper.loop(android.os.Looper)
at com.unity3d.player.UnityPlayer$C.run(com.unity3d.player.UnityPlayer$C)
at UnityEngine.AndroidJNISafe.CheckException(AndroidJNISafe.cs:24)
at UnityEngine.AndroidJNISafe.GetFieldID(AndroidJNISafe.cs:87)
at UnityEngine._AndroidJNIHelper.GetFieldID(AndroidJava.cs:1614)
at UnityEngine.AndroidJNIHelper.GetFieldID(AndroidJNI.bindings.cs:91)
at UnityEngine._AndroidJNIHelper.GetFieldID[ReturnType](AndroidJava.cs:1534)
at UnityEngine.AndroidJNIHelper.GetFieldID[FieldType](AndroidJNI.bindings.cs:198)
at UnityEngine.AndroidJavaObject._Get[FieldType](AndroidJava.cs:630)
at UnityEngine.AndroidJavaObject.Get[FieldType](AndroidJava.cs:345)
at UnityEngine.AndroidJNIHelper.CreateJavaProxy(AndroidJNI.bindings.cs:106)
Many Unity plugins use an AndroidJavaProxy
interface to callback to C# methods from Java. There are two proposed workarounds to this problem both with caveats:
- Launch Unity in a native process ie. call
openInNativeProcess()
withinonUnityCreated
within your flutter application, as this will use theOverrideUnityActivity
that does contain a reference formUnityPlayer
. However, the FlutterUnityWidget messaging system between Flutter and Unity will break (this is not documented). - Add
mUnityPlayer
toMainActivity
. This is difficult if yourMainActivity
is in java as theUnityPlayerUtils.kt
does not expose theunityPlayer
variable as a Jvm field.
To Reproduce Steps to reproduce the behavior:
- Create an empty Unity 2022.2.0 project, consume fuw-2022.2.0
- add an AndroidJavaProxy to the project
- initialise the proxy on a Method exposed to Flutter
- Create an empty Flutter 3.7.12 project, consume FUW 2022.2.0
- call the Method in Flutter using
sendMessage
- run the project and observe
Expected behavior
No error logs should be thrown when any Native code in unity attempts to access mUnityPlayer
. Unity assumes this reference is always available.
Unity (please complete the following information):
- Unity: 2022.2.0+
- FUW: 2022.2.0, 2022.3.0-alpha1
- Flutter: 3.7.12
- OS: Android
- Android Version: Any
This is the first i've seen of this, but apparently you already found someone on Stackoverflow having the same issue. Thanks for getting it into an issue here with all the included info
You mention
Unity 2022.1.7 to 2022.2.0+
Do you mean somewhere between these versions, or in all these versions? The stackoverflow post even mentions 2021.3.19.
The changelog for 2022.2.0 is the only one that I can find that contains something that looks like it might be the cause.
Android: Added: Classes AndroidJNI, AndroidJNIHelper, AndroidJavaObject, AndroidJavaClass, AndroidJavaProxy have new methods for more efficient low-level interop with Java. See API docs for full list.
The only other mentions of AndroidJavaProxy that I see are bugfixes, one applied in (2022.2.0a4, 2022.1.7, 2021.3.6, 2020.3.3 and 2019.4.39) and another in (2020.3.46 and 2022.2.4)
Do you have an example of any Unity plugins that you've used with FUW that run into this issue?
I help to somewhat maintain this project, but I'm not very experienced in native Android development. Any existing plugin that triggers this could simplify the reproduction.
Hi,
Thanks for getting back to me so quickly. Unity 2022.1.7 does not present this issue when used with fuw-2022.1.7+
. I have only experienced this issue in 2022.2.0+ and have not tested 2021.3.19. However, if 2021.3.0+ is on LTS then they may have applied this change. Checking UnityCsReference 2021.3.19 presents the same offending line with Permission.GetActivity().Get<AndroidJavaObject>("mUnityPlayer")
. Hence, unity have moved towards relying upon the mUnityPlayer
reference in some of their AndroidJNI codebase.
I don't currently have an example project at hand as the plugin I am using is a proprietary bluetooth plugin. However, a plugin is not required to reproduce this issue. Simply calling Permission.GetActivity().Get<AndroidJavaObject>("mUnityPlayer")
will cause the aforementioned error log to be printed in an empty unity project on 2022.2.0 exported to flutter.
Im pretty well versed in Android, Unity, Flutter and open to discuss further to help solve the issue. I have tested working around the issue by holding a reference mUnityPlayer
in MainActivity
retrieved from UnityPlayerUtils.kt
. This works but it's rather hacky in it's current form.
Did some more digging in the CsReference to find the scope of this.
The change was applied in
- 2020.3.46
- 2021.3.19
- 2022.2.4
With the offending line you mentioned
- return _AndroidJNIHelper.CreateJavaProxy(GCHandle.ToIntPtr(handle), proxy);
+ return _AndroidJNIHelper.CreateJavaProxy(Permission.GetActivity().Get<AndroidJavaObject>("mUnityPlayer").GetRawObject(), GCHandle.ToIntPtr(handle), proxy);
Unity 2023 received a different change.
- 2023.1.0b3
- return _AndroidJNIHelper.CreateJavaProxy(GCHandle.ToIntPtr(handle), proxy);
+ return _AndroidJNIHelper.CreateJavaProxy(AndroidApp.UnityPlayerRaw, GCHandle.ToIntPtr(handle), proxy);
However Unity 2023 breaks compatibiity with this plugin, which makes things harder to test.
I also see the same issue popping up in more places.
Interesting, I did happen to stumble across the Ad Mobile one but forgot to link this ticket to it. Im unable to locate the implementation of the extern method for Unity 2023.1.0b3 and am uncertain if it will present the same issue.
I suspect that the fix to this problem will be to add an mUnityPlayer
reference to the MainActivity
and add @JvmStatic
(for java access) to the unityPlayer
property in UnityPlayerUtils.kt
. This could then be used to populate mUnityPlayer
prior to launching Unity. The best place I can think of to perform this would be the Build script export to flutter action (ie. DoAndroidBuild). Implementations for Kotlin or Java should be added to introduce the correct syntax for mUnityPlayer
in MainActivity
depending upon the extension type.
"I have tested working around the issue by holding a reference mUnityPlayer in MainActivity retrieved from UnityPlayerUtils.kt. This works but it's rather hacky in it's current form."
Can you post the code for this? I tried but got nowhere. A hacky fix will do for now.. have to actually release something..
Fair, I can post the solution but think it can be cleanup quite a bit if it's implemented at the plugin level rather than the application level. For context, our MainActivity is in java so my solution will present some additional steps that kotlin applications can skip. These additional steps are to add kotlin/java mixin so that you can expose the unityPlayer
property in UnityPlayerUtils.kt
to java methods. For reference, our gradle files use new plugin block provided by Gradle 7.2.
In android/app/build.gradle
add the kotlin plugin, kotlin source sets, unity-classes and flutter-unity-widget native classes:
plugins {
+ id 'kotlin-android'
id "com.android.application"
}
android {
+ sourceSets {
+ main {
+ java.srcDirs += 'src/main/java'
+ kotlin.srcDirs += 'src/main/kotlin'
+ }
+ }
}
dependencies {
implementation project(':unityLibrary')
+ implementation(name: 'unity-classes', ext:'jar')
+ implementation project(':flutter_unity_widget')
}
Then you will need to add the following file android/app/src/main/kotlin/UnityUtils.kt
with:
+ package com.company.app
+
+ import com.unity3d.player.UnityPlayer
+ import com.xraph.plugin.flutter_unity_widget.UnityPlayerUtils;
+
+ class UnityUtils {
+ companion object {
+ @JvmStatic
+ fun getUnityPlayer(): UnityPlayer {
+ return UnityPlayerUtils.unityPlayer as UnityPlayer
+ }
+ }
+ }
This file exposes unityPlayer
to java with @JvmStatic
annotation.
Then add the following to android/src/main/java/MainActivity.java
:
+ import com.unity3d.player.UnityPlayer;
Public class MainActivity extends FlutterActivity {
+ public UnityPlayer mUnityPlayer; // referenced by Unity native
+ // expose to flutter with MethodChannel
+ void setupUnityPlayerReference(MethodChannel.Result result) {
+ mUnityPlayer = UnityUtils.getUnityPlayer();
+ result.success(true);
+ }
The last step is to call setupUnityPlayerReference()
using flutter MethodChannel in onUnityCreated()
callback within Flutter, ie:
void onUnityCreated(controller) async {
await controller.isReady();
+ await MethodChannelService.setupUnityPlayerReference();
I have not included the MethodChannel boilerplate call to setupUnityPlayerReference()
as this is plugin implementation dependent. I would also add that you should either add this method to iOS in AppDelegate.swift
and make it do nothing OR use Platform.isAndroid
within flutter to check if the setupUnityPlayerReference()
should be called or not. This will prevent breaking iOS.
You will need to ensure you have a android/settings.gradle
file with pluginManagement
block, ie:
pluginManagement {
repositories {
gradlePluginPortal()
jcenter()
google()
mavenCentral()
}
Or modify the solution for early Gradle versions by using apply plugin
instead of the plugin {
block.
Hope this workaround makes sense but feel free to pry for more information is required.
After some hacking and headscratching I got a kotlin solution similar to yours.
I found you have to define the object as java.lang.Object otherwise the native code simply can't find it, even though it's exported properly (checked with a class decompiler).
MainActivity.kt
import com.xraph.plugin.flutter_unity_widget.UnityPlayerUtils
import com.unity3d.player.UnityPlayer
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
...
@JvmField
var mUnityPlayer: java.lang.Object? = null
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "unity.hack").setMethodCallHandler {
call, result ->
if (call.method == "init") {
mUnityPlayer = UnityPlayerUtils.unityPlayer as java.lang.Object?
result.success(0);
}
}
}
main.dart
void onUnityCreated(UnityWidgetController? controller) async {
...
if(Platform.isAndroid) {
const MethodChannel('unity.hack').invokeMethod("init");
}
...
}
build.gradle
dependencies {
...
implementation(name: 'unity-classes', ext:'jar')
...
}
Type definition make sense as Kotlin is weird and wonderful when it comes to object decompilation. I know that Unity expects the object to be of type java.lang.Object
. Other than that, the Kotlin only solution looks great.
I think perhaps the Build script for Unity could handle adding the reference to MainActivity in DoAndroidBuild()
. Simple enough to add the right syntax for the reference based on the extension it finds on MainActivity. There may need to be search paths (ie. src/main/java/
and src/main/kotlin/
) and Im not sure if this need to be configurable for the developer.
The FlutterUnityWidget could then populate this reference once the Unity controller has been created, which Im hoping that this returns prior to any User Awake()
or Start()
calls occur.
I wonder if it might be easier just to make a FlutterUnityActivity base class.. then it's just changing one line of code in MainActivity and that can be documented, rather than have the DoAndroidBuild modify things outside its normal build path.
You would have to sub-class the FlutterUnityActivity in MainActivity. This may prevent other developers from sub-classing their own MainActivity implementations. However, it would make the assign operation of the mUnityPlayer reference cleaner and it also makes sense that there's an expectation to rely on a UnityActivity base-class.
I've just answered my own request for a known plugin that reproduces this. This triggered while looking for an ARFoundation issue, using the ARFoundation samples project.
I/Unity (24343): UnityEngine.XR.ARSubsystems.XRSessionSubsystem:Update(XRSessionUpdateParams) (at .\Library\PackageCache\[email protected]\Runtime\ARSubsystems\SessionSubsystem\XRSessionSubsystem.cs:128)
I/Unity (24343): UnityEngine.XR.ARFoundation.ARSession:Update() (at .\Library\PackageCache\[email protected]\Runtime\ARFoundation\ARSession.cs:416)
I/Unity (24343):
E/Unity (24343): AndroidJavaException: java.lang.NoSuchFieldError: no "Ljava/lang/Object;" field "mUnityPlayer" in class "Lcom/xraph/plugin/flutter_unity_widget_example/MainActivity;" or its superclasses
@Jinl21 @TonyHoyleRps Can you try this branch for a possbile fix on the plugin side?
It took quite some stackoverflow pages to figure out how Kotlin works, but ARFoundation works again on my end.
I'd love it if you could test this and possibly add suggetions for improvements.
Changes: I added the property in MainActivity like mentioned above, and assign it from the plugin using reflection.
MainActivity.kt
@JvmField
var mUnityPlayer: java.lang.Object? = null
UnityPlayerUtils.kt
val unityActivityProperty = activity!!::class.memberProperties.firstOrNull {it.name == "mUnityPlayer"}
if (unityActivityProperty !== null && unityActivityProperty is KMutableProperty<*>) {
(unityActivityProperty as KMutableProperty<*>)?.setter?.call(activity!!, (unityPlayer as java.lang.Object?))
}
TODO:
- Check if a Java MainActivity works.
- Check if this survives release builds
- Add docs for editing the MainActivity
- General testing for bugs etc.
It seems mUnityPlayer is optimised out in release builds, so it got through all my testing then failed when we tried to push it out.
Seems this works in build.cs (might not be build.cs any more, I'm using a modified version of fuw & I note there have been filename changes)..
proguardText += "\n-keepclassmembers class **.MainActivity { *; }";
'*' is too much really.. I couldn't work out how to successfully just add mUnityPlayer in that line though.
This proguard rule worked for me.
-keep class * extends io.flutter.embedding.android.FlutterActivity {
public java.lang.Object mUnityPlayer;
}
A java MainActivity seems to work as well with public Object mUnityPlayer;
, which won't need any import statements.
[Update]
Never mind the proguard rules.
Adding @Keep
in the activity seems to do the trick, which avoids possible issues with the build script being outdated.
package com.xraph.plugin.flutter_unity_widget_example
import io.flutter.embedding.android.FlutterActivity
import androidx.annotation.Keep;
class MainActivity: FlutterActivity() {
@Keep @JvmField
var mUnityPlayer: java.lang.Object? = null
}
@timbotimbo Happy to report that the fix works perfectly when including @Keep
annotation. In my scenario, I tested this by adding the following to my MainActivity.java
:
package com.example.app;
Public class MainActivity extends FlutterActivity {
/* note: set by reflections from FUW */
@Keep
public Object mUnityPlayer;
Tested for debug/release build variants. Seems just docs and general bug testing is left.
One interesting thing to note. Reflections is slow, if you don't have a view present in flutter while loading unity a white screen will appear for a few seconds.
@Jinl21
One interesting thing to note. Reflections is slow, if you don't have a view present in flutter while loading unity a white screen will appear for a few seconds.
Can you give an example of how you initialize unity that causes this?
Maybe moving the reflection call to be after the onReady callback might make a difference.
@timbotimbo, this is how im loading unity:
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: UnityWidget(
fullscreen: true,
hideStatus: true,
unloadOnDispose: false,
onUnityCreated: onUnityCreated,
onUnityMessage: onUnityMessage,
onUnitySceneLoaded: onUnitySceneLoaded,
useAndroidViewSurface: true,
),
);
}
with the following on create function:
void onUnityCreated(controller) async {
this._unityWidgetController = controller;
await _unityWidgetController.isReady();
_isExiting = false;
// unfreeze hack (https://github.com/juicycleff/flutter-unity-view-widget/issues/538)
if (Platform.isAndroid) {
_unityWidgetController.postMessage("GameLoader", "ReloadScene", "");
} else {
initializeGame();
}
}
void onUnitySceneLoaded(SceneLoaded? scene) async {
print('Received scene loaded from unity: ${scene?.name}');
print('Received scene loaded from unity buildIndex: ${scene?.buildIndex}');
if (Platform.isAndroid && scene?.buildIndex == 0 && !_isExiting) {
// unfreeze hack (https://github.com/juicycleff/flutter-unity-view-widget/issues/538)
_unityWidgetController.pause()?.then((_) {
_unityWidgetController.resume();
});
initializeGame();
}
}
I'm using the unfreeze hack posted in issue 538 to reload Unity instead of disposing/creating again as I recall there is an issue when disposing.
This method did not show a white screen for 3 seconds prior to the change onto this branch.
I think it's best to keep the refections call following unity creation as there may be use-cases of mUnityPlayer
in Awake()
methods.
FATAL EXCEPTION: main Process: com.xraph.plugin.flutter_unity_widget_example, PID: 31477 java.lang.Error: FATAL EXCEPTION [main] Unity version : 2022.3.3f1 Device model : OnePlus EB2101 Device fingerprint: OnePlus/OnePlusNordCE_IND/OnePlusNordCE:12/RKQ1.211119.001/R.202301101727:user/release-keys CPU supported ABI : [arm64-v8a, armeabi-v7a, armeabi] Build Type : Release Scripting Backend : IL2CPP Libs loaded from : lib/arm64 Strip Engine Code : true Caused by: java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback; at java.lang.Class.getDeclaredFields(Native Method) at kotlin.reflect.jvm.internal.impl.descriptors.runtime.structure.ReflectJavaClass.getFields(ReflectJavaClass.kt:87) at kotlin.reflect.jvm.internal.impl.descriptors.runtime.structure.ReflectJavaClass.getFields(ReflectJavaClass.kt:29) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.ClassDeclaredMemberIndex.
(DeclaredMemberIndex.kt:53) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaClassMemberScope.computeMemberIndex(LazyJavaClassMemberScope.kt:71) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaClassMemberScope.computeMemberIndex(LazyJavaClassMemberScope.kt:63) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope$declaredMemberIndex$1.invoke(LazyJavaScope.kt:72) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope$declaredMemberIndex$1.invoke(LazyJavaScope.kt:72) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:408) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:527) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaClassMemberScope.computePropertyNames(LazyJavaClassMemberScope.kt:861) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope$propertyNamesLazy$2.invoke(LazyJavaScope.kt:260) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope$propertyNamesLazy$2.invoke(LazyJavaScope.kt:260) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:408) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:527) at kotlin.reflect.jvm.internal.impl.storage.StorageKt.getValue(storage.kt:42) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope.getPropertyNamesLazy(LazyJavaScope.kt:260) at kotlin.reflect.jvm.internal.impl.load.java.lazy.descriptors.LazyJavaScope.getVariableNames(LazyJavaScope.kt:264) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$DeserializedClassMemberScope.getNonDeclaredVariableNames(DeserializedClassDescriptor.kt:348) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation$variableNames$2.invoke(DeserializedMemberScope.kt:262) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation$variableNames$2.invoke(DeserializedMemberScope.kt:261) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:408) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:527) at kotlin.reflect.jvm.internal.impl.storage.StorageKt.getValue(storage.kt:42) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation.getVariableNames(DeserializedMemberScope.kt:261) 2023-08-14 19:43:22.260 31477-31477 AndroidRuntime com....flutter_unity_widget_example E at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope$OptimizedImplementation.addFunctionsAndPropertiesTo(DeserializedMemberScope.kt:349) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedMemberScope.computeDescriptors(DeserializedMemberScope.kt:115) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$DeserializedClassMemberScope$allDescriptors$1.invoke(DeserializedClassDescriptor.kt:268) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$DeserializedClassMemberScope$allDescriptors$1.invoke(DeserializedClassDescriptor.kt:267) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedLazyValue.invoke(LockBasedStorageManager.java:408) at kotlin.reflect.jvm.internal.impl.storage.LockBasedStorageManager$LockBasedNotNullLazyValue.invoke(LockBasedStorageManager.java:527) at kotlin.reflect.jvm.internal.impl.serialization.deserialization.descriptors.DeserializedClassDescriptor$DeserializedClassMemberScope.getContributedDescriptors(DeserializedClassDescriptor.kt:278) at kotlin.reflect.jvm.internal.impl.resolve.scopes.ResolutionScope$DefaultImpls.getContributedDescriptors$default(ResolutionScope.kt:50) at kotlin.reflect.jvm.internal.KDeclarationContainerImpl.getMembers(KDeclarationContainerImpl.kt:56) at kotlin.reflect.jvm.internal.KClassImpl$Data$declaredNonStaticMembers$2.invoke(KClassImpl.kt:162) at kotlin.reflect.jvm.internal.KClassImpl$Data$declaredNonStaticMembers$2.invoke(KClassImpl.kt:162) at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:93) at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32) at kotlin.reflect.jvm.internal.KClassImpl$Data.getDeclaredNonStaticMembers(KClassImpl.kt:162) at kotlin.reflect.jvm.internal.KClassImpl$Data$allNonStaticMembers$2.invoke(KClassImpl.kt:171) at kotlin.reflect.jvm.internal.KClassImpl$Data$allNonStaticMembers$2.invoke(KClassImpl.kt:171) at kotlin.reflect.jvm.internal.ReflectProperties$LazySoftVal.invoke(ReflectProperties.java:93) at kotlin.reflect.jvm.internal.ReflectProperties$Val.getValue(ReflectProperties.java:32) at kotlin.reflect.jvm.internal.KClassImpl$Data.getAllNonStaticMembers(KClassImpl.kt:171) at kotlin.reflect.full.KClasses.getMemberProperties(KClasses.kt:148) at com.xraph.plugin.flutter_unity_widget.UnityPlayerUtils$Companion.createUnityPlayer(UnityPlayerUtils.kt:74) at com.xraph.plugin.flutter_unity_widget.FlutterUnityWidgetController.createPlayer(FlutterUnityWidgetController.kt:288) at com.xraph.plugin.flutter_unity_widget.FlutterUnityWidgetController. (FlutterUnityWidgetController.kt:70) at com.xraph.plugin.flutter_unity_widget.FlutterUnityWidgetBuilder.build(FlutterUnityWidgetBuilder.kt:16) at com.xraph.plugin.flutter_unity_widget.FlutterUnityWidgetFactory.create(FlutterUnityWidgetFactory.kt:34) at io.flutter.plugin.platform.PlatformViewsController.createPlatformView(PlatformViewsController.java:510) at io.flutter.plugin.platform.PlatformViewsController$1.createForTextureLayer(PlatformViewsController.java:191) at io.flutter.embedding.engine.systemchannels.PlatformViewsChannel$1.create(PlatformViewsChannel.java:128) at io.flutter.embedding.engine.systemchannels.PlatformViewsChannel$1.onMethodCall(PlatformViewsChannel.java:55) at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:258) at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:295) at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:322) at io.flutter.embedding.engine.dart.DartMessenger$$ExternalSyntheticLambda0.run(Unknown Source:12) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) 2023-08-14 19:43:22.260 31477-31477 AndroidRuntime com....flutter_unity_widget_example E at android.os.Looper.loopOnce(Looper.java:233) at android.os.Looper.loop(Looper.java:344) at android.app.ActivityThread.main(ActivityThread.java:8212) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:584) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1034) Caused by: java.lang.ClassNotFoundException: Didn't find class "android.window.OnBackInvokedCallback" on path: DexPathList[[zip file "/data/app/~~CfTw7WWSgtM9oKIHtpgJXA==/com.xraph.plugin.flutter_unity_widget_example-VB_YXcb5GijQ-t6z2hASZA==/base.apk"],nativeLibraryDirectories=[/data/app/~~CfTw7WWSgtM9oKIHtpgJXA==/com.xraph.plugin.flutter_unity_widget_example-VB_YXcb5GijQ-t6z2hASZA==/lib/arm64, /data/app/~~CfTw7WWSgtM9oKIHtpgJXA==/com.xraph.plugin.flutter_unity_widget_example-VB_YXcb5GijQ-t6z2hASZA==/base.apk!/lib/arm64-v8a, /system/lib64, /system_ext/lib64]] at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:259) at java.lang.ClassLoader.loadClass(ClassLoader.java:379) at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
@timbotimbo I am running into this error : Caused by: java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback; And not able to access the classmemberproperties with reflection from the main activity.
Did you encounter this ? I was getting the same error on my project, so i cloned your branch and still have the same problem. Can you please help debug ?
This branch is an alternative approach because I wasn't satisfied with the reflection performance.
This one is activity based like what was suggested before in this issue.
I defined both an Activity and an Interface: FlutterUnityActivity
and IFlutterUnityAcitivity
.
UnityPlayerUtils checks if the activity implements either of these and tries to set the unityplayer property.
People that use the default MainActivity can simply inherit it.
// MainActivity.kt
// Needs implementation project(':flutter_unity_widget') in app/build.gradle
+ import com.xraph.plugin.flutter_unity_widget.FlutterUnityActivity;
+ class MainActivity: FlutterUnityActivity() {
- class MainActivity: FlutterActivity() {
}
Anyone that already inherits a different activity can use the interface
// Needs implementation project(':flutter_unity_widget') in app/build.gradle
import com.xraph.plugin.flutter_unity_widget.IFlutterUnityActivity;
class MainActivity: CustomActivity(), IFlutterUnityActivity {
@JvmField
var mUnityPlayer: java.lang.Object? = null;
override fun setUnityPlayer(unityPlayer: java.lang.Object?) {
mUnityPlayer = unityPlayer;
}
}
Most users will only need the first one, which is a simple change to the MainActivity. Without reflection this also survives release builds without any need for keep annotations or proguard rules.
I only need to test if a Java MainActivity can inherit this without any problems.
@akhil-tlm-sg I haven't seen your error before and I can't reproduce it either. Maybe you can try this new branch instead.
@timbotimbo able to reproduce @akhil-tlm-sg bug when running on an emulator:
E/AndroidRuntime( 9420): FATAL EXCEPTION: main
E/AndroidRuntime( 9420): Process: com.company.app.dev, PID: 9420
E/AndroidRuntime( 9420): java.lang.Error: FATAL EXCEPTION [main]
E/AndroidRuntime( 9420): Unity version : 2022.2.21f1
E/AndroidRuntime( 9420): Device model : Google sdk_gphone64_arm64
E/AndroidRuntime( 9420): Device fingerprint: google/sdk_gphone64_arm64/emulator64_arm64:12/S2B1.211112.006/7934767:userdebug/dev-keys
E/AndroidRuntime( 9420): CPU supported ABI : [arm64-v8a]
E/AndroidRuntime( 9420): Build Type : Release
E/AndroidRuntime( 9420): Scripting Backend : IL2CPP
E/AndroidRuntime( 9420): Libs loaded from : lib/arm64
E/AndroidRuntime( 9420): Strip Engine Code : true
E/AndroidRuntime( 9420):
E/AndroidRuntime( 9420): Caused by: java.lang.NoClassDefFoundError: Failed resolution of: Landroid/window/OnBackInvokedCallback;
E/AndroidRuntime( 9420): at java.lang.Class.getDeclaredFields(Native Method)
E/AndroidRuntime( 9420): at kotlin.reflect.jvm.internal.impl.descriptors.runtime.structure.ReflectJavaClass.getFields(ReflectJavaClass.kt:87)
E/AndroidRuntime( 9420): at kotlin.reflect.jvm.internal.impl.descriptors.runtime.structure.ReflectJavaClass.getFields(ReflectJavaClass.kt:29)
//........
com.xraph.plugin.flutter_unity_widget.UnityPlayerUtils$Companion.createUnityPlayer(UnityPlayerUtils.kt:74)
I imagine using FlutterUnityActivity
instead of reflections will probably solve this bug. Will try and test either today or tomorrow.
@timbotimbo Can confirm that android_java_proxy_2
branch solves the reflections bug reported by @akhil-tlm-sg during my test on the emulator with Google sdk_gphone64_arm64
.
An update on some Unity changes.
They modified the same line again in some newer Unity versions.
2021.3.21
- return _AndroidJNIHelper.CreateJavaProxy(Permission.GetActivity().Get<AndroidJavaObject>("mUnityPlayer").GetRawObject(), GCHandle.ToIntPtr(handle), proxy);
+ return _AndroidJNIHelper.CreateJavaProxy(Common.GetActivity().Get<AndroidJavaObject>("mUnityPlayer").GetRawObject(), GCHandle.ToIntPtr(handle), proxy);
2022.3.19
- return _AndroidJNIHelper.CreateJavaProxy(Permission.GetActivity().Get<AndroidJavaObject>("mUnityPlayer").GetRawObject(), GCHandle.ToIntPtr(handle), proxy);
+ return _AndroidJNIHelper.CreateJavaProxy(AndroidApp.UnityPlayerRaw, GCHandle.ToIntPtr(handle), proxy);
I tried running ARFoundation on these versions and I no longer get the error.
In short this bug now affects:
- 2020.3.46+
- 2021.3.19 - 2021.3.20
- 2022.2.4 - 2022.3.18
@timbotimbo This bug fix should solve those refactors assuming in both cases the name of the object is mUnityPlayer
. We have have extensively tested this bug fix and been running it in production for approximately 6 months. It seems fairly stable. Curious to know the timeline to merge this fix to release?
I merged it today.
Although I have no clue when the next plugin update is released.
Because a lack of active contributers, I usually have to wait months for a PR to get approved.
This proguard rule worked for me.
-keep class * extends io.flutter.embedding.android.FlutterActivity { public java.lang.Object mUnityPlayer; }
A java MainActivity seems to work as well with
public Object mUnityPlayer;
, which won't need any import statements.[Update]
Never mind the proguard rules. Adding
@Keep
in the activity seems to do the trick, which avoids possible issues with the build script being outdated.package com.xraph.plugin.flutter_unity_widget_example import io.flutter.embedding.android.FlutterActivity import androidx.annotation.Keep; class MainActivity: FlutterActivity() { @Keep @JvmField var mUnityPlayer: java.lang.Object? = null }
Hey @timbotimbo I'm currently trying to implemented the updated solution in one of my projects. Can you explain how and where can I add this class to my current project?
@ArthurTelles
That quote looks like the first version that I didn't end uo using.
Can you check the readme in the current master branch of this repo?
Android setup step 3 should explain it.
Alternatively the MainActivity.kt in the example folder also implements it.
@ArthurTelles
That quote looks like the first version that I didn't end uo using.
Can you check the readme in the current master branch of this repo?
Android setup step 3 should explain it. Alternatively the MainActivity.kt in the example folder also implements it.
Hey @timbotimbo, thank you so much for the quick response!
I tried following the steps you suggested, but unfortunately, I'm encountering an error when running flutter run
.
...android/app/src/main/kotlin/com/example/flutter_unity/MainActivity.kt: (1, 46): Unresolved reference: FlutterUnityActivity
...
FAILURE: Build failed with an exception.
What went wrong:
Execution failed for task ':flutter_unity_widget:compileDebugKotlin'.
> A failure occurred while executing org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction
> Compilation error. See log for more details
do you have any further advice?
@ArthurTelles Double check you have implementation project(':flutter_unity_widget')
in your app's android/app/build.gradle
. Then make sure you have the latest ref for flutter_unity_widget
in pubspec.lock
when calling flutter pub get
. You would need to use master
as the ref in your pubspec.yaml
for flutter_unity_widget
. The class should be available by this point as it's in the latest ref of master: https://github.com/juicycleff/flutter-unity-view-widget/blob/master/android/src/main/kotlin/com/xraph/plugin/flutter_unity_widget/FlutterUnityActivity.kt
@Jinl21 Using the master as the ref did the trick! Thanks a lot for the help!
Hello i have created a project with flutter and unity. Added to github. After from other machine pulled and tried to run. It is not working. How can i run it from other computer. By the way in this computer doesnt have unity. Only have flutter