How to - AAR + Unity
May 11, 2020 - Monday evening
I want to replace the default Unity launcher with my own to kick off some tasks before Unity loads, as well as start up a service to make some api calls on exiting. To do this, I decided on creating an Android library and including it into my Unity build as an AAR.
Unity supports Kotlin files as assets, so you could also just create your MainActivity.kt
file and drop it into Assets
and override with an AndroidManifest.xml
- but you'd be missing Android Studio's intellisense, and you'd have to rebuild the Unity app to see your changes on device. If you don't need to change too much, this is definitely the easiest path.
The best way I could find to include an AAR into Unity is to publish the library module to maven local, and add it as a dependency by overriding the default gradle templates. If you plan to include an AAR with no dependencies, you could also just drag and drop the AAR file like Unity docs say. AAR's don't come with their dependencies - though you could include them as JAR's and manually update them whenever they change. Publishing and adding as a dependency generates the pom.xml
file for you and pulls in those transitive dependencies automatically when Unity runs the gradle build.
🔢 To include an Android library as an AAR, by the numbers
Export your Unity project and import with Android Studio
Create your Android library module
Copy
unity-classes.jar
from theunityLibrary
module's/libs
folder into your module's/libs
, then include it as acompileOnly
dependencyIf you're using Kotlin, you may need to add a Kotlin dependency to the root
build.gradle
. To usemaven-publish
as in the next step, you'll also need to use Gradle 3.6+allprojects { buildscript { ext { kotlin_version = '1.3.61' } ... dependencies { classpath 'com.android.tools.build:gradle:3.6.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } }
Add to your module
build.gradle
(you can also override other properties)apply plugin: 'maven-publish' version = "1.0.0" group = "com.example.customplayer" ... afterEvaluate { publishing { publications { release(MavenPublication) { from components.release } } } }
Add your module as an implementation dependency to the launcher module and run
??? (development goes here)
Run the publish release to maven local gradle task
Make sure you're on Unity version 2019.3.7f1 and up. Go to
project settings -> player -> publishing
and enable:- custom main manifest (if you're replacing the launcher activity)
- main gradle template
- base gradle template
These options should create some files in
Assets/Plugins/Android
Add to
baseProjectTemplate.gradle
allprojects { repositories { ... mavenLocal() } }
Add to
mainTemplate.gradle
dependencies { ... implementation ("com.example.customplayer:customplayer:1.0.0@aar") { transitive=true } }
Unity will overwrite your
settings.gradle
when you export to the same folder, you can add asettingsTemplate.gradle
to replace the default one. AddingINCLUDES
as mentioned in docs will break the build as it doesn't get replaced. You can checkunityRoot/Temp/gradleOut/settings.gradle
to see what's produced and add your module to it. This works:include ':launcher', ':unityLibrary', ':customplayer'
If you're replacing the Unity launcher activity, modify
AndroidManifest.xml
inAssets/Plugins/Android
with your activity's class name.<manifest ...> <application> <activity android:name="com.example.customplayer.CustomPlayer" .../> ... </application> </manifest>
During development:
To see Unity changes as you're working in Android Studio - export the Unity project to the Android project folder again.
To build the Unity app with your Android library updates - run the publish to maven local task again.
That's it!
🔥 Now for a hot Kotlin tip
As someone new to Kotlin, concurrency felt very complicated, especially coming from languages with simple primitives like async / await. You have to generate scopes and manage coroutine structures manually. The Kotlin language guide starts off with GlobalScope
as the way to go, but don't stop there as the internet says this is bad to actually use - especially in an Android application.
It took a bit of searching and trial, but what I found to be the simplest way to just make an API call from an Activity is this (using kotlinx-coroutines
):
class CustomPlayer :
UnityPlayerActivity(),
CoroutineScope by CoroutineScope(Dispatchers.Default) {
val queue: RequestQueue by lazy {
Volley.newRequestQueue(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
launch {
val googleAsAString = getGoogle()
Log.i("CUSTOM_PLAYER", "Here's google: $googleAsAString")
}
}
suspend fun getGoogle() = suspendCoroutine<String> {
val req = StringRequest(
Request.Method.GET,
"https://www.google.com",
Response.Listener {
result -> it.resumeWith(Result.success(result))
},
Response.ErrorListener {
error -> it.resumeWith(Result.failure(error))
})
queue.add(req)
}
}
Lines 3, 12 and 18 get you async. It's another reason why Android development needs to be in Android Studio - without the await
(come on Kotlin, you save one keyword!) you'll need the editor's icon to help you figure out where execution pauses (hint, line 13).
Other than my struggling with concurrency a bit, Kotlin has been great so far. I wish Volley had some documentation, an online API reference would be really useful - well, Kotlin documentation in general could use some TLC (though this is true of almost all documentation, everywhere).
It feels nicely expressive and safe, but also like there's a large sprinkling of magic throughout. If your last argument is a callback, you can add the lambda outside of the parameter list. Why? Why not. If you use $
in a string, it will stringify the variable - but only up to the first dot. I don't feel like I 'get it' yet, but it's definitely a lot nicer to write than Java. Not that Java's bad, especially the modern stuff.
Of course, everything pales in comparison to F# (lolol). We all have our preferences!