As I work on a toy project in unity I have came across the challenge of integrating it with android. My project is targetting android, however it doesn’t really need to use any android features so my goal was to bypass JNI and go straight to the native system as much as possible.
My inital goal is just to simple get a scene loaded onto an android emulator but so far I’ve had no luck. The apk seems to be loading, the application icon shows on the android emulator but starting up the unity app just crashes the emulator, more than this the logs don’t really give much to go on.
A few logs of note from my app are
2024-08-02 20:37:39.356 10480-10480 IL2CPP com.io.emu.Emu I JNI_OnLoad
...
2024-08-02 20:37:48.296 10480-10529 Unity com.io.emu.Emu I Company Name: io.emu
2024-08-02 20:37:48.296 10480-10529 Unity com.io.emu.Emu I Product Name: Emu
2024-08-02 20:37:48.424 10480-10529 platform com.io.emu.Emu E Failed to open rendernode: No such file or directory
...
2024-08-02 20:38:29.917 10480-10619 goldfish_vulkan com.io.emu.Emu D ensureSyncDeviceFd: created sync device for current Vulkan process: 135
I include the last log simply because it is the last log, doesn’t mean much to me, so none of these logs really mean much to me except that my app is being recognised in some way. The error mentions having trouble finding some directory or file (no idea what one). An important point is that none of my logs are actually showing, that is when I manually log something in my code, so all this seems to be related to unity setup.
The logs outside my namespace end with these
2024-08-02 20:38:29.837 9307-10013 WorkSourceUtil com.google.android.gms E Could not find package: com.google.android.gms.westworld
2024-08-02 20:38:29.857 532-944 ProcessStats system_server W Tracking association SourceState{1a4117d com.google.android.gms.persistent/10130 BFgs #17943} whose proc state 4 is better than process ProcessState{ba155bd com.google.android.gms/10130 pkg=com.google.android.gms (sub)} proc state 5 (1156 skipped)
2024-08-02 20:38:29.917 10480-10619 goldfish_vulkan com.io.emu.Emu D ensureSyncDeviceFd: created sync device for current Vulkan process: 135
Again nothing that means a lot to me and nothing I would expect to crash the app. I am using NLog with a custom logging config, the config itself is being loaded into the assets folder in the apk
// On Android, load config from the StreamingAssets folder
configPath = Path.Combine(Application.streamingAssetsPath, "NLog.config");
Debug.Log($"LoggingManager: Loading NLog config from StreamingAssets at {configPath}.");
using (UnityWebRequest www = UnityWebRequest.Get(configPath))
{
yield return www.SendWebRequest();
if (www.result != UnityWebRequest.Result.Success)
{
Debug.LogError($"LoggingManager: Failed to load NLog configuration from {configPath}: {www.error}");
throw new System.Exception($"Failed to load NLog configuration from {configPath}: {www.error}");
}
Debug.Log("LoggingManager: NLog config loaded successfully from StreamingAssets.");
LoadNLogConfigFromText(www.downloadHandler.text);
}
Even if there is some issue loading NLog config I am also logging using unity’s own logging method which is also not showing in logcat.
My plan now is to try and load a much simpler unity apk onto the emulator. This project is large and complex with native code, VR and a lot of scenes, third-party libraries etc so it might be challenging to debug. it also takes a long time to build the apk. I am loading this as a streaming asset.
I created a basic unity project with a single cube and camera, set it up to build android. I ran a pixel 3 emulator but it kept crashing with something gms related, so instead I tried running it in headless mode with this
emulator -avd Pixel_3a_API_34 -no-window -gpu off
I needed to use gpu off to disable hardware acceleration, maybe because I am using a laptop with an incompatible gpu or something, to save time rebuilding from unity each time I built it once. I loaded my apk then checked the package from the list with these commands
adb install "Build\build.apk"
adb shell pm list packages
I could then find my package from the list and run it like this
adb shell monkey -p com.ryan.AndroidDebug -c android.intent.category.LAUNCHER 1
This seems to be an improvement as it didn’t crash the emulator like previous times and can be seen loading in the emulator logs
I0803 22:38:28.125173 11932 VkDecoderGlobalState.cpp:447] Creating Vulkan instance for engine: Unity
And I can see something going on in logcat
2024-08-03 22:38:25.432 7166-7202 Unity com.ryan.AndroidDebug I SystemInfo CPU = x86-64 SSE3 SSE4.1 SSE4.2 AVX, Cores = 4, Memory = 1973mb
2024-08-03 22:38:25.432 7166-7202 Unity com.ryan.AndroidDebug I ApplicationInfo 'com.ryan.AndroidDebug', Version '0.1', Min API Level '32', Target API Level '32'
2024-08-03 22:38:25.432 7166-7202 Unity com.ryan.AndroidDebug I Built from '2023.2/staging' branch, Version '2023.2.3f1 (21747dafc6ee)', Build type 'Development', Scripting Backend 'il2cpp', CPU 'x86_64', Stripping 'Enabled'
2024-08-03 22:38:25.433 7166-7202 Unity com.ryan.AndroidDebug I Device Model 'Google sdk_gphone64_x86_64', OS 'Android OS 14 (API 34)'
...
2024-08-03 22:38:25.719 7166-7202 Unity com.ryan.AndroidDebug I Company Name: ryan
2024-08-03 22:38:25.719 7166-7202 Unity com.ryan.AndroidDebug I Product Name: Android Debug
...
2024-08-03 22:38:25.814 7166-7202 platform com.ryan.AndroidDebug E Failed to open rendernode: No such file or directory
...
2024-08-03 22:38:25.995 7166-7202 EGL_emulation com.ryan.AndroidDebug E eglCreateContext: EGL_BAD_CONFIG: no ES 3.1 support
2024-08-03 22:38:25.995 7166-7202 EGL_emulation com.ryan.AndroidDebug E tid 7202: eglCreateContext(1828): error 0x3005 (EGL_BAD_CONFIG)
2024-08-03 22:38:25.995 7166-7202 Unity com.ryan.AndroidDebug D [EGL] ES3.1 not supported
2024-08-03 22:38:25.995 7166-7202 Unity com.ryan.AndroidDebug D [EGL] Request: ES 3.1 RGB 000 0/0
2024-08-03 22:38:25.995 7166-7202 Unity com.ryan.AndroidDebug D [EGL] Request: ES 3.0 RGB 000 0/0
2024-08-03 22:38:25.996 7166-7202 EGL_emulation com.ryan.AndroidDebug E [getAttribValue] Bad attribute idx
2024-08-03 22:38:25.996 7166-7202 EGL_emulation com.ryan.AndroidDebug E tid 7202: eglGetConfigAttrib(1277): error 0x3004 (EGL_BAD_ATTRIBUTE)
...
2024-08-03 22:38:27.547 531-557 ActivityTaskManager system_server I Fully drawn com.ryan.AndroidDebug/com.unity3d.player.UnityPlayerActivity: +3s683ms
Some familiar error logs are there like in my original app so maybe it was a false alarm. The last log seems promising as it has drawn the unity activity ‘UnityPlayerActivity’ which is the entrypoint to the app.
I then stopped the app
adb shell am force-stop com.ryan.AndroidDebug
The logs didn’t look pretty but it is confirmation it was running I guess and just didn’t stop gracefully since I forced it
2024-08-03 22:38:27.547 531-557 ActivityTaskManager system_server I Fully drawn com.ryan.AndroidDebug/com.unity3d.player.UnityPlayerActivity: +3s683ms
2024-08-03 23:02:38.020 531-944 ActivityTaskManager system_server W Force removing ActivityRecord{34c3e14 u0 com.ryan.AndroidDebug/com.unity3d.player.UnityPlayerActivity t8 f}}: app died, no saved state
2024-08-03 23:02:38.029 881-924 WindowManagerShell com.android.systemui V Transition requested: android.os.BinderProxy@e4708fa TransitionRequestInfo { type = CLOSE, triggerTask = TaskInfo{userId=0 taskId=8 displayId=0 isRunning=false baseIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.ryan.AndroidDebug/com.unity3d.player.UnityPlayerActivity } baseActivity=null topActivity=null origActivity=null realActivity=ComponentInfo{com.ryan.AndroidDebug/com.unity3d.player.UnityPlayerActivity} numActivities=0 lastActiveTime=462248 supportsMultiWindow=false resizeMode=0 isResizeable=false minWidth=-1 minHeight=-1 defaultMinSize=220 token=WCT{android.window.IWindowContainerToken$Stub$Proxy@4572eab} topActivityType=1 pictureInPictureParams=null shouldDockBigOverlays=false launchIntoPipHostTaskId=-1 lastParentTaskIdBeforePip=-1 displayCutoutSafeInsets=null topActivityInfo=null launchCookies=[] positionInParent=Point(0, 0) parentTaskId=-1 isFocused=true isVisible=true isVisibleRequested=true isSleeping=false topActivityInSizeCompat=false topActivityEligibleForLetterboxEducation= false topActivityLetterboxed= false isFromDoubleTap= false topActivityLetterboxVerticalPosition= -1 topActivityLetterboxHorizontalPosition= -1 topActivityLetterboxWidth=-1 topActivityLetterboxHeight=-1 locusId=null displayAreaFeatureId=1 cameraCompatControlState=hidden}, remoteTransition = null, displayChange = null }
2024-08-03 23:02:38.051 531-2457 WindowManager system_server I WIN DEATH: Window{d42eaed u0 com.ryan.AndroidDebug/com.unity3d.player.UnityPlayerActivity}
2024-08-03 23:02:38.052 531-2457 InputManager-JNI system_server W Input channel object 'd42eaed com.ryan.AndroidDebug/com.unity3d.player.UnityPlayerActivity (client)' was disposed without first being removed with the input manager!
2024-08-03 23:02:38.549 531-557 WindowManager system_server V Sent Transition #9 createdAt=08-03 23:02:38.020 via request=TransitionRequestInfo { type = CLOSE, triggerTask = TaskInfo{userId=0 taskId=8 displayId=0 isRunning=false baseIntent=Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 cmp=com.ryan.AndroidDebug/com.unity3d.player.UnityPlayerActivity } baseActivity=null topActivity=null origActivity=null realActivity=ComponentInfo{com.ryan.AndroidDebug/com.unity3d.player.UnityPlayerActivity} numActivities=0 lastActiveTime=462248 supportsMultiWindow=false resizeMode=0 isResizeable=false minWidth=-1 minHeight=-1 defaultMinSize=220 token=WCT{RemoteToken{5f7f0e5 Task{9d8b6bd #8 type=standard A=10190:com.ryan.AndroidDebug}}} topActivityType=1 pictureInPictureParams=null shouldDockBigOverlays=false launchIntoPipHostTaskId=-1 lastParentTaskIdBeforePip=-1 displayCutoutSafeInsets=null topActivityInfo=null launchCookies=[] positionInParent=Point(0, 0) parentTaskId=-1 isFocused=true isVisible=true isVisibleRequested=true isSleeping=false topActivityInSizeCompat=false topActivityEligibleForLetterboxEducation= false topActivityLetterboxed= false isFromDoubleTap= false topActivityLetterboxVerticalPosition= -1 topActivityLetterboxHorizontalPosition= -1 topActivityLetterboxWidth=-1 topActivityLetterboxHeight=-1 locusId=null displayAreaFeatureId=1 cameraCompatControlState=hidden}, remoteTransition = null, displayChange = null }
2024-08-03 23:02:38.849 531-945 WindowManager system_server W Exception thrown during dispatchAppVisibility Window{d42eaed u0 com.ryan.AndroidDebug/com.unity3d.player.UnityPlayerActivity EXITING}
android.os.DeadObjectException
Adding a simple log statement with unity has indeed logged in logcat after attaching to the cube
2024-08-04 14:43:54.343 5115-5152 Unity com.ryan.AndroidDebug I I CUBE HAVE INITIALIZED
I recreated this scene in the original project – it didn’t work to begin with, I then removed all scenes from the build even though they were not checked and so shouldn’t be included. It then worked, so then I tried adding my custom components to this scene and everything started logging.
In summary I don’t think my scene was even starting, my understanding was the first scene in the list would get initialized with the app and only checked scenes would be included.
I discovered I can use the unity “patch and run” feature instead of “build and run” for android, this is much faster. I tested this by swapping out a dll to fix some native code issue and it did pick up the changes.
The challenge I am facing now is to get some logging working for android, I am using a logging delegate in x86_64 windows architectures which seems to work. In android I am doing this in native code
#define LOG_TAG "PdfiumLib"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
...
typedef void (*LogCallbackDelegate)(const char*);
static LogCallbackDelegate g_logCallback = NULL;
...
void SetLogCallback(LogCallbackDelegate callback)
{
g_logCallback = callback;
}
void LogMessage(const char* file, const char* function, int line, const char* message) {
if (g_logCallback) {
char buffer[1024];
snprintf(buffer, sizeof(buffer), "[%s:%d %s] %s", file, line, function, message);
g_logCallback(buffer);
} else {
LOGI("[%s:%d %s] %s", file, line, function, message);
}
}
The goal is to give me some consistency between my unity logging and the native logging, maybe there are better solutions, but for now this will do if it works.
In the c# code we have
[DllImport(EmuNativePluginDLLName, CallingConvention = DefaultCallingConvention)]
private static extern void SetLogCallback(LogCallbackDelegate logCallback);
...
SetLogCallback(NativeLoggingDelegate);
...
private static void NativeLoggingDelegate(string message)
{
EmuLogger.Info(message);
}
This essentially creates a logging callback from the native code to my unity app so that I can handle it with the custom NLog config. The current error we are getting is
2024-08-04 20:35:56.748 12300-12352 Unity com.io.emu.Emu E NotSupportedException: To marshal a managed method, please add an attribute named 'MonoPInvokeCallback' to the method definition. The method we're attempting to marshal is
This was fixed by adding an annotation which I think tells unity to use MonoPInvoke, I am not sure what this means so I need to read up a bit on managed vs unmanaged functions
[MonoPInvokeCallback(typeof(LogCallbackDelegate))]
private static void NativeLoggingDelegate(string message)
{
EmuLogger.Info(message);
}
I made a few changes to be able to log at different levels and to improve the logging format, it was initially logging the full file path, so I added this to log only the file name, not ideal but not sure of a better way yet.
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__)
This gives a more readable log with ALMOST all the info I need
2024-08-09 17:23:37.823 4494-4533 Unity com.io.emu.Emu I 2024-08-09 17:23:37.8235 INFO (NativeLoggingDelegate:53) [main.cpp:136 GetRenderedImage] Attempting to render image from PDF document.
I say almost because it tells me the log originated in the c# code from “NativeLoggingDelegate:53” which isn’t very useful so I will want it to show the file and line number of the code that is calling the delegate. This should give me the entry point of the c# code and the line of the cpp code.
The second issue is this error
2024-08-09 17:23:31.616 4494-4533 Unity com.io.emu.Emu E AndroidJavaException: java.lang.ClassNotFoundException: com.emu.emuandroidplugin.MainActivity
java.lang.ClassNotFoundException: com.emu.emuandroidplugin.MainActivity
at java.lang.Class.classForName(Native Method)
at java.lang.Class.forName(Class.java:536)
at com.unity3d.player.UnityPlayerForActivityOrService.nativeRender(Native Method)
at com.unity3d.player.S.handleMessage(Unknown Source:140)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loopOnce(Looper.java:205)
at android.os.Looper.loop(Looper.java:294)
at com.unity3d.player.V.run(Unknown Source:24)
I guess my native code is a simple native library and doesn’t really need to use android activities, but android seems to still expect one. After some checking I decided to ignore this error for now and look into it in more detail on a real device rather than a simulator.

Leave a Reply