Unity native plugin bugs: threading, not the calls
A practical guide to Unity native plugins on Android and iOS, arguing that the hard part is not the native calls but threading, build configuration and release stripping. It covers a single C# interface with an editor fake, marshalling callbacks to the main thread, and CI checks for both platforms.
Key points
- Hide the native API behind one C# interface and provide an editor fake implementation.
- Marshal native callbacks into a ConcurrentQueue and drain it on the main thread in Update.
- Prefer AndroidJavaProxy over UnitySendMessage for callbacks, since the compiler can check the interface.
- Add iOS frameworks and Info.plist keys in PostProcessBuild scripts, not by hand in Xcode.
- Protect reflection-reached types with link.xml or R8 keep rules, and test the real release build.
- Test pause and resume on real devices, since mobile OSes can suspend the app at any time.
Calling native code from Unity looks simple on paper, but the failures usually appear later, when the plugin ships. The author points to three common sources: native callbacks that arrive off the main thread, release builds where IL2CPP stripping removes code that is only reached by name, and Android build issues such as Gradle conflicts and manifest merging.
His suggested structure keeps platform code behind one C# interface, with separate Android and iOS implementations and an editor fake so the feature can run and be tested in the Editor. Callbacks should be pushed into a thread-safe queue and drained on the main thread in Update. Build settings such as frameworks and Info.plist keys belong in post-build scripts, release stripping needs link.xml or keep rules, and CI should build both platforms from clean state. The original post goes deeper into each step.
Real-world use
- A Unity mobile game that needs a platform API such as network state callbacks or sharing.
- A clinical or AR app built on a headset vendor SDK that ships only as .aar and .xcframework.
- A team upgrading Unity on a live project and checking that native plugins still work in release builds.
Why it matters
Native plugin bugs often come from threading, build settings and release stripping rather than the native call itself. Routing callbacks to the main thread and testing real release builds on devices prevents the failures that only show up after shipping.