Android Plugin Development in Unity
Android Plugin Development in Unity
Written on
May 21, 2016
This post is a quick guide on getting started with developing a plugin in Unity which will allow you to interface and call commands from an Android project, therefore giving you access to native functionality that might not be available in Unity’s standard library.
Creating a plugin in Android Studio
Create a new empty project - plugin will be stored as individual modules within this project. Create a new module by going to to File > New > New Module… > Android Library. Then, create a new empty class:
package com.test.unitylibrary; public class PluginActivity { }
We’ll add some test methods and see how to call them from Unity soon.
For Unity to recognize your plugin, you’ll need to compile it to a .aar file. Click on the “Gradle” tab on the right-most side of the window, click on your module > Tasks > build > assembleRelease. This will generate a .aar file in [YourProjectName][YourModuleName]\build\outputs\aar. Move this file inside the Assets\Plugins\Android folder of your Unity Project. We can now access this plugin from Unity.
You’ll need to repeat the building procedure every time you make a change to the plugin in Android Studio.
Creating plugin objects
In Unity, you can create a representation of your Android plugin by creating a new AndroidJavaClass / AndroidJavaObject object and passing your package name and class name:
androidJavaObject plugin = new AndroidJavaObject ("com.test.unitylibrary.PluginActivity");`
Let’s say you have those two methods in your class to print test messages:
public static void showTestMessageStatic() { Log.i("Unity", "Hi, this is done in Android Static"); } public void showTestMessage() { Log.i("Unity", "Hi, this is done in Android"); }
You can call methods from your plugin class with
plugin.CallStatic("showTestMessageStatic"); plugin.Call("showTestMessage");
Bear in mind you can’t call non-static methods from a androidJavaObject object.
Difference between AndroidJavaClass and AndroidJavaObject
AndroidJavaClass only calls an instance of an activity (it does not create it). AndroidJavaObject creates a new object class (basically like calling new Class.class). Therefore if you need to call static methods only it is recommended to use AndroidJavaClass. If, however, you need to call a non-static method or have the necessity to create the class (to set its context, or use functionality like getSystemService for example) you’ll have to use AndroidJavaObject. Note that when a AndroidJavaObject is initialized, its constructor will be called (and not OnCreate()!). You can still call static methods from AndroidJavaObject.
Calling methods with context
Some Android operations need to operate within a context.
A context object is the context of current state of the application/object. It lets newly-created objects understand what has been going on. Typically you call it to get information regarding another part of your program (activity and package/application).
For example, if we were to start a new service or activity, we’d have to do so from a context (usually the activity you’re calling the method from). In Unity programs, we’ll pass the activity generated by UnityPlayer. You can access it like this:
AndroidJavaClass unityPlayer = new AndroidJavaClass ("com.unity3d.player.UnityPlayer"); AndroidJavaObject activity = unityPlayer.GetStatic<AndroidJavaObject> ("currentActivity");
Then you can pass the activity as a context to the necessary methods. Let’s say you’ve got a method on your plugin class which sets the context later used by other methods:
private Context context; public void setContext(Context context) { this.context = context; } public void startPluginService() { context.startService(new Intent(context, PluginService.class)); }
From Unity, you’d call:
plugin.Call("setContext", activity); plugin.Call("startPluginService");
To successfully start the service. If you were to call the startDownloadService method without setting its context first, you’d get a java.lang.NullPointerException when the method tries to access the necessary context.
Calling methods on the UI thread
Typically when you write a traditional Android app there is only one main thread, and everything that deals with the UI must run on the main thread. Unity runs its own thread to handle its processing. It doesn’t usurp the Android main thread created by the Android OS when launching the app. If you want to do anything in the Android UI outside of Unity, for example showing a toast or a dialog, you’ll have to run it in the main thread.
For example, take a method in your Android plugin which shows a Toast:
public void showToast() { Toast.makeText(context, "this is my Toast message!", Toast.LENGTH_LONG).show(); }
You can run this method on the main thread by calling runOnUiThread from the main Unity activity and pass the method you want to call as a Runnable.
AndroidJavaClass unityPlayer = new AndroidJavaClass ("com.unity3d.player.UnityPlayer"); AndroidJavaObject activity = unityPlayer.GetStatic<AndroidJavaObject> ("currentActivity"); AndroidJavaObject plugin = new AndroidJavaObject ("com.test.unitylibrary.PluginActivity"); plugin.Call ("setContext", activity); activity.Call("runOnUiThread", new AndroidJavaRunnable(() => { plugin.Call("testToast"); }));
Extending UnityPlayerActivity
If we need more advanced form of communication between our plugin and Unity, for example invoking callbacks from Unity objects, we need to make our plugin class an extension of UnityPlayerActivity.
First we need to find the Unity Java classes sources. They are located into a classes.jar file found in the installation folder (usually C:\Program Files\Unity\Editor\Data (on Windows) or /Applications/Unity (on Mac)) in a sub-folder called PlaybackEngines/AndroidPlayer/Variations/mono or il2cpp/Development or Release/Classes/.
Then, we need to tell Android Studio to compile the classes.jar file alongside our plugin. You can do so by moving the classes.jar file to the \libs folder of our plugin project folder. By not doing so, the UnityPlayerActivity won’t be found / recognized when extending our class.
The classes.jar file, however, is included into the Unity Android build by default, and Unity will fail to build the application when finding duplicate classes.
We can fix this with a little hack: create and empty module in your project (call it something like “unitylibrary”), and then put the classes.jar file into its /libs folder. Then, in the build.gradle file of the original plugin module, add the dependency to the empty unitylibrary module like so:
dependencies { compile project(':unitylibrary') ... }
Plain Text
This way, the UnityPlayerActivity class definition will be recognized in your code, but the classes.jar won’t be packed inside your plugin.
Bear in mind that even if your main class in the plugin extends an Activity, it doesn’t actually gets created - OnCreate() is not called, and any method which would normally work within the context of the activity has to be called from the context of the Unity application’s UnityPlayerActivity which we pass from our scripts: not context.registerReceiver() instead of registerReceiver()context.startActivity() instead of startActivity() and so on…
Unity Callbacks from the Plugin
Once the class extends UnityPlayerActivity, we can call the static method UnityPlayer.UnitySendMessage(gameObject, methodName, message), where - gameObject is the name of the GameObject in your Unity scene to communicate with - methodName is the function to call on that GameObject - data is the data (String) to pass to this function
Therefore, let’s make a test function in our Android class like:
public static void callbackToUnityMethod(String gameObject, String methodName) { UnityPlayer.UnitySendMessage(gameObject, methodName, "Hello from Android"); }
And then, in a DownloadController GameObject in Unity, write those methods:
public void AskForCallback() { AndroidJavaClass plugin = new AndroidJavaClass ("com.test.unitylibrary.PluginActivity"); plugin.CallStatic("callbackToUnityMethod", "DownloadController", "CallbackMessage"); } private void CallbackMessage(string s) { Debug.Log ("A little bird told me: " + s); }
When you call the AskForCallback function, the Android plugin will call the UnityPlayer.UnitySendMessage function, invoking the CallbackMessage function in DownloadController. If everything worked fine, you’ll see the “A little bird told me: Hello from Android” message in the Log.
From my experience, I noticed that if you create a AndroidJavaObject of a class that extends UnityPlayerActivity, you’ll have to run the declaration on the UI thread: