Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Android Plugin JNI Question

Discussion in 'Android' started by Joshua_Falkner, Aug 15, 2011.

  1. Joshua_Falkner

    Joshua_Falkner

    Joined:
    Feb 19, 2010
    Posts:
    45
    EDIT: I solved my issue and have written a tutorial on how to write a custom plugin and access the Android Compass here: http://randomactsofdev.wordpress.com/2011/08/19/accessing-the-android-compass-through-unity-3d/ </EDIT>

    I'm trying to write a custom plugin to access the Compass and I'm hitting a wall. First off, I'm not a C(++) programmer so I am trying to leave building a lib.so out of this. I'm trying to do this only with the AndroidJNI functions in Unity, and I think I'm pretty close, but I still can't get it working.

    My main issue is that if I pull the x, y, or z cords from the compass from Unity it always comes back as zero. If I build an app directly from my java code it works correctly. I'm thinking that I'm somehow not connecting to the correct Activity when using this in Unity. If someone could point out what I'm doing wrong I would appreciate it.

    First - here's my jar file. If I run this directly (not as a plugin in Unity) it works great. (sorry for the code flood)

    Code (csharp):
    1.  
    2. package com.noyoushutupgames.compass;
    3.  
    4. import android.content.Context;
    5. import android.hardware.Sensor;
    6. import android.hardware.SensorEvent;
    7. import android.hardware.SensorEventListener;
    8. import android.hardware.SensorManager;
    9. import android.os.Bundle;
    10. import android.util.Config;
    11. import android.util.Log;
    12. import android.app.Activity;
    13.  
    14.  
    15. public class CompassActivity extends Activity {
    16.     private static final String TAG = "Compass";
    17.  
    18.     private SensorManager mSensorManager;
    19.     private Sensor mSensor;
    20.    
    21.     static public float xmag;
    22.     static public float ymag;
    23.     static public float zmag;
    24.    
    25.     private final SensorEventListener mListener = new SensorEventListener() {
    26.         public void onSensorChanged(SensorEvent event) {
    27.             if (Config.DEBUG) Log.d(TAG,
    28.                     "sensorChanged (" + event.values[0] + ", " + event.values[1] + ", " + event.values[2] + ")");
    29.            
    30.             xmag = event.values[0];
    31.             Log.i(TAG, "xmag = " + xmag);
    32.             ymag = event.values[1];
    33.             Log.i(TAG, "ymag = " + xmag);
    34.             zmag = mValues[2];
    35.             Log.i(TAG, "zmag = " + xmag);
    36.         }
    37.  
    38.         public void onAccuracyChanged(Sensor sensor, int accuracy) {
    39.         }
    40.     };
    41.  
    42.     @Override
    43.     protected void onCreate(Bundle icicle) {
    44.         Log.i(TAG, "Here I am and it is exciting - TWJ");
    45.         super.onCreate(icicle);
    46.         mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
    47.         mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
    48.         //mView = new SampleView(this);
    49.         //setContentView(mView);           
    50.     }
    51.  
    52.     @Override
    53.     protected void onResume()
    54.     {
    55.         if (Config.DEBUG) Log.d(TAG, "onResume");
    56.         super.onResume();
    57.  
    58.         mSensorManager.registerListener(mListener, mSensor,
    59.                 SensorManager.SENSOR_DELAY_GAME);
    60.     }
    61.  
    62.     @Override
    63.     protected void onStop()
    64.     {
    65.         if (Config.DEBUG) Log.d(TAG, "onStop");
    66.         mSensorManager.unregisterListener(mListener);
    67.         super.onStop();
    68.     }
    69.  
    70.      
    71.        
    72.     public static float getX() {
    73.         return xmag;
    74.     }
    75.    
    76.     public static float getY() {
    77.         return ymag;
    78.     }
    79.    
    80.     public static float getZ() {
    81.         return zmag;
    82.     }
    83.  
    84. }
    85.  
    Here's the CS I'm using
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System;
    5.  
    6. public class CompassJNI : MonoBehaviour {
    7.     float   xValue;
    8.     float   yValue;
    9.     float   zValue;
    10.  
    11.     // Use this for initialization
    12.     void Start () {    
    13.         AndroidJNI.AttachCurrentThread();
    14.     }
    15.    
    16.     void Update() {
    17.         xValue = pullX();
    18.         yValue = pullY();
    19.         zValue = pullZ();
    20.        
    21.         Debug.Log("Compass values are " + xValue.ToString() + "," + yValue.ToString() + "," + zValue.ToString());
    22.     }
    23.    
    24.     void OnGUI() {
    25.         GUI.Label(new Rect(Screen.width / 2, Screen.height / 2, 100,100), "xmag = " + xValue.ToString());
    26.     }
    27.    
    28.     public static float pullX() {
    29.         using (AndroidJavaClass cls_UnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) {
    30.            
    31.             using (AndroidJavaObject obj_Activity = cls_UnityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) {
    32.                
    33.                 AndroidJavaClass classHandle = new AndroidJavaClass("com.noyoushutupgames.compass.CompassActivity");               
    34.                 return classHandle.Call<float>("getX");
    35.                
    36.             }
    37.         }
    38.     }
    39.    
    40.     public static float pullY() {
    41.         using (AndroidJavaClass cls_UnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) {
    42.            
    43.             using (AndroidJavaObject obj_Activity = cls_UnityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) {
    44.                
    45.                 AndroidJavaClass classHandle = new AndroidJavaClass("com.noyoushutupgames.compass.CompassActivity");
    46.                 return classHandle.Call<float>("getY");
    47.                
    48.             }
    49.         }
    50.     }
    51.    
    52.     public static float pullZ() {
    53.         using (AndroidJavaClass cls_UnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) {
    54.            
    55.             using (AndroidJavaObject obj_Activity = cls_UnityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) {
    56.                
    57.                 AndroidJavaClass classHandle = new AndroidJavaClass("com.noyoushutupgames.compass.CompassActivity");
    58.                 return classHandle.Call<float>("getZ");
    59.                
    60.             }
    61.         }
    62.     }  
    63. }
    64.  
    I see a lot of JNI thread questions end up never answered, so here's hoping someone out there can show me the error of my ways/code =)
     
    Last edited: Aug 19, 2011
  2. DanTreble

    DanTreble

    Joined:
    Aug 31, 2010
    Posts:
    590
    Ok, the best thing to do it scour the logs. I think I can see something though

    You get a pointer/instance of the current activity, which is good
    Code (csharp):
    1. using (AndroidJavaObject obj_Activity = cls_UnityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) {
    but then you don't use that to call your method, you call it on a empty class handle which doesn't know about the instance you got before.
    Code (csharp):
    1.                 AndroidJavaClass classHandle = new AndroidJavaClass("com.noyoushutupgames.compass.CompassActivity");               
    2.                 return classHandle.Call<float>("getX");
    What I think you can do, is treat obj_Activity as if it were if type com.noyoushutupgames.compass.CompassActivity, because you know it is. If you're not sure, there is a way of dynamic casting it to check. You should be able to call getX straight on your obj_Activity instance.
     
  3. DanTreble

    DanTreble

    Joined:
    Aug 31, 2010
    Posts:
    590
    My preferred way of doing it is to have a java class that doesn't inherit from the main activity, but has an Init function that takes a reference to the main activity

    Code (csharp):
    1. public class CoarseLocation
    2. {
    3.     static Location lastKnownLocation = null;
    4.    
    5.     static public void Init(final Activity currentActivity)
    6.     {
    7.         LocationManager locationManager = (LocationManager) currentActivity.getSystemService(Context.LOCATION_SERVICE);
    8.    
    9.         if  (locationManager != null)
    10.         {
    11.             lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
    12.         }
    13.     }
    14.    
    15.     static public float GetLatitude()
    16.     {
    17.         return lastKnownLocation != null  ? (float)lastKnownLocation.getLatitude() : 0.0f;
    18.     }
    19.    
    20.     static public float GetLongitude()
    21.     {
    22.         return lastKnownLocation != null  ? (float)lastKnownLocation.getLongitude() : 0.0f;
    23.     }
    24.    
    25.     static public float GetAccuracy()
    26.     {
    27.         return lastKnownLocation != null  ? (float)lastKnownLocation.getAccuracy() : 0.0f;
    28.     }

    Then you can call Init and grab it's data like so
    Code (csharp):
    1. using (AndroidJavaClass cls_UnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
    2.         {
    3.             using (AndroidJavaObject obj_Activity = cls_UnityPlayer.GetStatic<AndroidJavaObject>("currentActivity"))
    4.             {
    5.                 AndroidJavaClass cls_CoarseLocation = new AndroidJavaClass("com.DefiantDev.CoarseLocation");
    6.  
    7.                 cls_CoarseLocation.CallStatic("Init", obj_Activity);
    8.  
    9.                 latitude = cls_CoarseLocation.CallStatic<float>("GetLatitude");
    10.                 longitude = cls_CoarseLocation.CallStatic<float>("GetLongitude");
    11.                 accuracy = cls_CoarseLocation.CallStatic<float>("GetAccuracy");
    12.                 float time = cls_CoarseLocation.CallStatic<float>("GetTime");
     
  4. Joshua_Falkner

    Joshua_Falkner

    Joined:
    Feb 19, 2010
    Posts:
    45
    Thanks for the response DTreble. I re-worked the code a bit to employ the init method like you suggested but I'm still stuck in the same place. I re-written the java to both extend Activity as well as implement SensorEventListener and still come up with zero values from the Magnetic field sensor. Going through the logging I'm not getting any errors or anything, so here is what I *think* is happening.

    It appears that any function/method in my .jar doesn't run until I call it manually from Unity. The most important method here is onSensorChanged() but this is never run. (I can tell b/c none of the tracing shows up in logcat) Is there some layer of protection or some permission I need to enable so that the Magnetic Field Sensor can run OnSensorChanged? I saw another thread ask questions about the NativeActivity and ProxyActivity added in 3.4, so I'm wondering if that has anything to do with it.

    Also, here's the updated code for anyone playing along at home!
    Java:
    Code (csharp):
    1.  
    2. package com.noyoushutupgames.compass;
    3.  
    4. import android.content.Context;
    5. import android.hardware.Sensor;
    6. import android.hardware.SensorEvent;
    7. import android.hardware.SensorEventListener;
    8. import android.hardware.SensorManager;
    9. import android.os.Bundle;
    10. import android.util.Config;
    11. import android.util.Log;
    12. import android.app.Activity;
    13.  
    14.  
    15. public class CompassActivity implements SensorEventListener {  
    16.     private static final String TAG = "Compass";
    17.    
    18.     private static SensorManager mSensorManager;
    19.     private static Sensor mSensor;
    20.     private static Activity mActivity;
    21.    
    22.     static public float xmag;
    23.     static public float ymag;
    24.     static public float zmag;
    25.    
    26.     public void onSensorChanged(SensorEvent event) {
    27.         Log.i(TAG, "The Sensor has changed.");
    28.         xmag = event.values[0];
    29.         Log.i(TAG, "xmag = " + xmag);
    30.         ymag = event.values[1];
    31.         Log.i(TAG, "ymag = " + xmag);
    32.         zmag = event.values[2];
    33.         Log.i(TAG, "zmag = " + xmag);
    34.     }
    35.    
    36.     static public void Init(final Activity currentActivity) {
    37.         Log.i(TAG, "Initialized!");
    38.         mActivity = currentActivity;
    39.         mSensorManager = (SensorManager) mActivity.getSystemService(Context.SENSOR_SERVICE);
    40.         mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
    41.     }
    42.    
    43.     public static float getX() {
    44.         Log.i(TAG, "Returning X from inside the jar! " + xmag);
    45.         return xmag;
    46.     }
    47.    
    48.     public static float getY() {
    49.         Log.i(TAG, "Returning X from inside the jar! " + xmag);
    50.         return ymag;
    51.     }
    52.    
    53.     public static float getZ() {
    54.         Log.i(TAG, "Returning X from inside the jar! " + xmag);
    55.         return zmag;
    56.     }
    57. }
    58.  
    And here's the CS

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System;
    5.  
    6. public class CompassJNI : MonoBehaviour {
    7.     static float    xValue;
    8.     static float    yValue;
    9.     static float    zValue;
    10.  
    11.     // Use this for initialization
    12.     void Start () {    
    13.         AndroidJNI.AttachCurrentThread();
    14.     }
    15.    
    16.     void Update() {
    17.         using (AndroidJavaClass cls_UnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer")) {
    18.            
    19.             using (AndroidJavaObject obj_Activity = cls_UnityPlayer.GetStatic<AndroidJavaObject>("currentActivity")) {
    20.                
    21.                 AndroidJavaClass cls_CompassActivity = new AndroidJavaClass("com.noyoushutupgames.compass.CompassActivity");
    22.                
    23.                 cls_CompassActivity.CallStatic("Init", obj_Activity);
    24.                
    25.                 xValue = cls_CompassActivity.CallStatic<float>("getX");
    26.                 yValue = cls_CompassActivity.CallStatic<float>("getY");
    27.                 zValue = cls_CompassActivity.CallStatic<float>("getZ");
    28.  
    29.             }
    30.         }
    31.        
    32.         Debug.Log("Compass values are " + xValue.ToString() + "," + yValue.ToString() + "," + zValue.ToString());
    33.     }
    34.    
    35.     void OnGUI() {
    36.         GUI.Label(new Rect(Screen.width / 2, Screen.height / 2, 100,100), "xmag = " + xValue.ToString());
    37.     }
    38. }
    39.  
    40.  
     
  5. Joshua_Falkner

    Joshua_Falkner

    Joined:
    Feb 19, 2010
    Posts:
    45
    I was hoping that someone from UT could chime in here and give me their thoughts...
     
  6. BLiTZWiNG

    BLiTZWiNG

    Joined:
    Feb 9, 2010
    Posts:
    15
    I'm trying to d othe same thng, following some of Joshua's notes on answers.unity.com

    I tried a quick test yesterday in my 12 hours of hell, to simply write a Java class that returned a hardcoded value, and even that failed. I have to wonder whether it's possible to talk to java directly from unity. I am quite a java n00b, though I learnt a lot yesterday.

    Would appreciate anyone with knowledge stepping in!
     
  7. BLiTZWiNG

    BLiTZWiNG

    Joined:
    Feb 9, 2010
    Posts:
    15
    I have to say, I am having a very wierd time trying to get an app that just reads the sensors running even in the emulator. It seems to have crashed but I don't know enough about java to access or create logs of what is going on.
    Note that in my code below I have commented out all the code that changes the label based on data revieved. If I uncomment all the registering of the listeners, the default text shows up in my label. WTH is going on?

    Code (csharp):
    1. package com.example.androidtest;
    2.  
    3. import android.app.Activity;
    4. import android.content.Context;
    5. import android.hardware.Sensor;
    6. import android.hardware.SensorEvent;
    7. import android.hardware.SensorEventListener;
    8. import android.hardware.SensorManager;
    9. import android.os.Bundle;
    10. import android.widget.TextView;
    11.  
    12. public class AndroidTestActivity extends Activity
    13. {
    14.     SensorManager mSensorManager;
    15.     Sensor mSensor;
    16.     TextView tv;
    17.  
    18.     /** Called when the activity is first created. */
    19.     @Override
    20.     public void onCreate(Bundle savedInstanceState)
    21.     {
    22.     super.onCreate(savedInstanceState);
    23.     setContentView(R.layout.main);
    24.  
    25.     tv = (TextView) findViewById(R.id.labelTest);
    26.     tv.setText(R.string.hello);
    27.  
    28.     mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
    29.     mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    30.     }
    31.  
    32.     private final SensorEventListener mListener = new SensorEventListener()
    33.     {
    34.  
    35.     public void onAccuracyChanged(Sensor sensor, int accuracy)
    36.     {
    37.         // TODO Auto-generated method stub
    38.  
    39.     }
    40.  
    41.     public void onSensorChanged(SensorEvent event)
    42.     {
    43.         if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD)
    44.         {
    45.         //tv.setText("blah"); //String.format("Mag %f %f %f", event.values[0], event.values[1], event.values[2]));
    46.         }
    47.         if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
    48.         {
    49.         //tv.setText("crappy"); //String.format("Acc %f %f %f", event.values[0], event.values[1], event.values[2]));
    50.         }
    51.        
    52.         //tv.setText("but something is happening...");
    53.     }
    54.     };
    55.  
    56.     @Override
    57.     protected void onResume()
    58.     {
    59.     super.onResume();
    60.     mSensorManager.registerListener(mListener, mSensor, SensorManager.SENSOR_DELAY_GAME);
    61.     }
    62.  
    63.     @Override
    64.     protected void onStop()
    65.     {
    66.     mSensorManager.unregisterListener(mListener);
    67.     super.onStop();
    68.     }
    69.  
    70. }
     
  8. Joshua_Falkner

    Joshua_Falkner

    Joined:
    Feb 19, 2010
    Posts:
    45
    Hey BLiTZWiNG, if you compile the java in my first post and run it directly you'll see the correct event.values in the LogCat output. As for getting the SensorManager to update the event.values as a .jar in a Unity plug-in I'm still stumped.

    I'm about to just open a bug report as I've done everything correctly according to the (limited) documentation and I can't seem to get anyone from UT to shed some light in this thread.
     
  9. lyh1

    lyh1

    Joined:
    Jun 30, 2010
    Posts:
    92
    How about your mainfest.xml, does it setup properly?
     
  10. Joshua_Falkner

    Joshua_Falkner

    Joined:
    Feb 19, 2010
    Posts:
    45
    I'm pretty sure my manifest.xml is set up properly. If I declare a variable and give it a value in my .jar file I can pull those results successfully via AndroidJNI calls. I can run any method and get the output successfully... but for some reason the SensorEventListener isn't getting updated from the hardware Magnetic Field Sensor.

    I'm thinking that there maybe something new in Unity 3.4 with their additions of the AndroidNativeJNI and AndroidProxyJNI that is blocking the SensorEventListener from being updated, but that's just a guess.
     
  11. Joshua_Falkner

    Joshua_Falkner

    Joined:
    Feb 19, 2010
    Posts:
    45
    Just to update the thread, I finally figured it all out and got it working. The problem was I was using the wrong classes.jar from a previous version of Unity (probably 3.2, but not sure since I had copied it to a different location).

    BLiTZWiNG - I'm going to write up step by step instructions on my blog later tonight (I'm just too freaking exhausted at the moment) and I'll post the link.

    EDIT: Here's the link http://randomactsofdev.wordpress.com/2011/08/19/accessing-the-android-compass-through-unity-3d/
     
    Last edited: Aug 19, 2011
  12. BLiTZWiNG

    BLiTZWiNG

    Joined:
    Feb 9, 2010
    Posts:
    15
    Wow Josh, I stumbled upon your blog in my desperate searching and followed it as much as I could (and posted as well). I decided to come back here to see if you had progressed, not realising the blog was yours!

    I have progressed in that my code doesn't crash any more. I have no idea how the manifests work or where they're meant to be located.

    It's interesting you say that the sensor data doesn't get updated, I mentioned that in my comment on your blog, I don't think it will happen because you create the class, register the event, read a value then destroy it presumably before the system even has a chance to fire an event at it. I have created my classes in Start(), and are just trying to read them in Update(), but even this test method: public float Test() { return 5.678f; } returns 0, so I am doing something wrong somewhere else.

    I'm close to my wits end with this. I feel like I'm close to getting it working but I just don't know what is wrong, and I have no idea how to debug log anything.
     
  13. lazygunn

    lazygunn

    Joined:
    Jul 24, 2011
    Posts:
    2,749
    I'm just kind of barging in but do you mean simply
    Code (csharp):
    1. Debug.Log( stufftologgoesinhere);
    ?

    Thats Unity's way, or do you mean maybe running 'adb -logcat' from your SDK for android specific logging

    Im just learning this stuff myself so apologies if this 'help' is wrong
     
  14. BLiTZWiNG

    BLiTZWiNG

    Joined:
    Feb 9, 2010
    Posts:
    15
    Debug.Log will show up in logcat as I discovered, under the Unity tag. Be wary though, if it fires every frame, logcat will go beserk.
     
  15. Joshua_Falkner

    Joshua_Falkner

    Joined:
    Feb 19, 2010
    Posts:
    45
    Hey Blitzwing - saw that you got everything sorted out, and I'm really glad if I was able to help get you there! Sorry for my delay in response, I took a much needed break after getting all this working. Let me know when you finish your game/project. Would love to see what you've come up with!
     
  16. Miyavi

    Miyavi

    Joined:
    Mar 10, 2012
    Posts:
    58
    Hey, I'm trying to implement this code, just to have it working so I can access from Unity to Android native code.
    I actually used your walkthough in your page, Joshua, but I'm having some problems while compiling the .java into a .jar and such.

    I have Android SDK installed, I have JDK installed and configured: the classpath and path system variables are set, the bootclasspath is not, and can't find how to properly set it up.

    Anyway, while I use the first line, which I guess is to set the classpath and bootclasspath, this is what happens:


    I've also tried to set Program Files (x86) between quotation marks, yet the very same thing happens.

    I've got every other file properly set up, and in its place, so I just need to compile this, in order to be able to compile the game to test the results out.

    Would you lend me giving me some help on this?

    Thanks a lot,

    Miyavi.

    EDIT:
    This might be easier to implement for what I want to do, right?
     
    Last edited: Aug 3, 2012