Custom Activity in Unity

Goal: I want to trigger Android activity-level code in my Unity 3D application. Here’s a minimal example of doing so:

  1. Create a new Android project in Eclipse. We won’t be using many of the ADT plugin features. We mostly leverage the compiler and editor.
  2. Override UnityPlayerActivity with a custom activity:
    package org.twodee.sitstill;
    
    import android.os.Bundle;
    import android.widget.Toast;
    
    import com.unity3d.player.UnityPlayerActivity;
    
    public class CustomActivity extends UnityPlayerActivity {
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Toast.makeText(this, "onCreate", Toast.LENGTH_LONG).show();
      }
    }
  3. Add C:\Program Files (x86)\Unity\Editor\Data\PlaybackEngines\androidplayer\bin\classes.jar to the classpath. I did this in Eclipse via Project properties, Java Build Path, Libraries, Add External JARs.
  4. Export a JAR file containing the bytecode for the custom activity.
  5. Add the JAR file to the assets of the Unity project in folder Plugins/Android.
  6. Add a custom AndroidManifest.xml to point to your custom main activity:
    <?xml version="1.0" encoding="utf-8"?>
    <manifest
      xmlns:android="http://schemas.android.com/apk/res/android"
      package="org.twodee.sitstill">
      <application
        android:icon="@drawable/app_icon"
        android:label="@string/app_name">
        <activity
          android:name=".CustomActivity"
          android:configChanges="fontScale|keyboard|keyboardHidden|locale|mnc|mcc|navigation|orientation|screenLayout|screenSize|smallestScreenSize|uiMode|touchscreen"
          android:label="@string/app_name">
          <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>
        </activity>
      </application>
    </manifest>

When you build and run, your activity will get loaded instead of the Unity default.

Triggering Custom Activity Methods

Now suppose you have a custom method in your activity that you want called on some Unity event. Suppose this is your method:

public void startListening() {
  Toast.makeText(CustomActivity.this, "starting to listen", Toast.LENGTH_SHORT).show();
}

Let me be the first to warn you that this solution won’t work. Unity will call this from its own thread, not the UI thread under which activity code should generally be run. We can force execution on the UI thread with this approach:

public void startListening() {
  runOnUiThread(new Runnable() {
    @Override
    public void run() {
      Toast.makeText(CustomActivity.this, "starting to listen", Toast.LENGTH_SHORT).show();
    }
  });
}

On the Unity side, where we make the call, we’ve got to employ the JNI helper functions to get the current activity, which is stored as a static field in the UnityPlayer class. We can invoke methods on that current activity. We do so in a C# script with something like:

AndroidJavaClass jc = new AndroidJavaClass("com.unity3d.player.UnityPlayer"); 
AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject>("currentActivity"); 
jo.Call("startListening");

Good luck.

Comments

  1. Liveon says:

    Hi Chris,

    This is a great tutorial for beginners. Thanks for sharing. Unfortunately I didn’t get this tutorial to work (or other ones on the web), but I’m pretty sure the problems is on my end because the steps are very similar.

    Error log:
    JNI: Unable to find field id for ‘currentActivity’ (static)
    UnityEngine.AndroidJavaObject:GetStatic(String)
    QpsAndroidPlugin:Start() (at Assets/Standard Assets (Mobile)/Scripts/QpsAndroidPlugin.cs:11)
    JNI: Init’d AndroidJavaObject with null ptr!
    UnityEngine.AndroidJavaObject:GetStatic(String)
    QpsAndroidPlugin:Start() (at Assets/Standard Assets (Mobile)/Scripts/QpsAndroidPlugin.cs:11)
    JNI: Unable to find method id for ‘startListening’
    UnityEngine.AndroidJavaObject:Call(String, Object[])
    QpsAndroidPlugin:Start() (at Assets/Standard Assets (Mobile)/Scripts/QpsAndroidPlugin.cs:13)

    Have you ever come across this before?
    Do I have to install Android NDK before hand (I’m using eclipse + Android ADT)?
    Any help would be greatly appreciated.

    1. Chris Johnson says:

      To narrow the problem a bit, perhaps just try retrieving the currentActivity without any custom activity. This field belongs to the superclass, and therefore should be accessible without any of your extra code.

      I’d start there.

    2. Chris Johnson says:

      Also, in answer to your question, I did not install the Android NDK to get this to work.

  2. www says:

    I/Unity ( 9661):
    E/Unity ( 9661): getMethodID(“startListening”, “()V”) FAILED!
    D/dalvikvm( 9661): GetMethodID: method not found: Lcom/unity3d/player/UnityPlayerActivity;.startListening:()V
    I/Unity ( 9661): JNI: Unable to find method i

    Me too

    1. Chris Johnson says:

      What were your results when trying my suggestion to the first commenter?

      Another thing to check is that the package in which your custom class resides is identical to the package listed in the tag.

  3. Parvaz says:

    Thank you so much for this great pointer…I think now I can also apply onSharedPreferenceschanged listener under this runOnUiThread.

    again Thanks for the share!!

  4. Junaid says:

    Thank you so much…it finally worked with your help

  5. Fabs says:

    First Example which works right away….and I tried a lot. Even wanted to give up.
    Thanks a lot!

  6. Jessy says:

    Chris, I love you for this tutorial! Thank you!!

  7. Angie says:

    Chris,would you help me? i have class that have extends YouTubeBaseActivity which will be present in YouTubeAndroidPlayerApi.jar. How to make plugin when not extends activity?Thanks for your help

  8. Thank you for great article of android launcher.

Leave a Reply

Your email address will not be published. Required fields are marked *