Search Unity

AndroidJavaObject: any way to clone for separate Dispose management?

Discussion in 'Android' started by JessHolle, Sep 10, 2020.

  1. JessHolle

    JessHolle

    Joined:
    Jul 9, 2019
    Posts:
    10
    I have run into cases where I need to have multiple AndroidJavaObject's referring to the same underlying Java object -- so as to have separately controlled Dispose lifespans.

    Currently I am simply not calling Dispose in such cases, but that's not really right. Rather separate bodies of code need their own separately disposable reference so each can dispose of its reference at the right time.

    On a related note, I've discovered that AndroidJavaProxy can call Dispose() on the result of its methods if that result is an AndroidJavaObject. That can obviously be disastrous if this AndroidJavaObject is one that is used elsewhere in C#. As a workaround I'm using a subclass of AndroidJavaObject to disable/ignore explicit Dispose() calls until I'm ready for the object to be disposed. But once again some means of cloning the AndroidJavaObject to get separate Dispose management is the real answer.

    Am I missing something here? I see nothing in AndroidJavaObject to this effect.
     
  2. JessHolle

    JessHolle

    Joined:
    Jul 9, 2019
    Posts:
    10
    For now I'm going to introduce my own subclass of AndroidJavaObject that exposes an IncrementReferenceCount() method and decrements the reference count upon any explicit Dispose() until this drops to 0 at which point it will actually dispose.

    Seems like the Unity API should provide a solution in this area -- either via a clone or reference counting.
     
  3. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,060
    Every AndroidJavaObject is a separate reference to Java object, event if they point to the same Java object. Disposing one only releases one reference, so Java object will not be GCed while there is at least one AndroidJavaObject referering to it.

    Not true, at least in the latest Unity. The only disposing I see is for the AndroidJavaObject that gets converted into it's C# equivalent prior to invoking C# method. What makes you think it does dispose return value?
     
  4. JessHolle

    JessHolle

    Joined:
    Jul 9, 2019
    Posts:
    10
    Right. But if I call a Java API, e.g. via AndroidJavaObject, and get an AndroidJavaObject I now need to pass it to different bodies of code that each will be ready to Dispose of this object at a different time. That's why I wanted a "clone" to have multiple AndroidJavaObjects referring to the same Java object but each able to Dispose() independently. I assume garbage collection will eventually dispose anyway, but I'm looking to Dispose of references earlier than that -- but without introducing additional objects to manage this, i.e. I just want to pass around AndroidJavaObjects, not some special structure around them.

    My attempt to introduce a subclass of AndroidJavaObject also has issues due to the lack of any ability to clone an AndroidJavaObject. It only helps if I'm creating constructing the AndroidJavaObject subclass instance myself :-(

    As for what's calling Dispose() within the AndroidJavaProxy, I'll add more logging so I can get the stack again.
     
  5. JessHolle

    JessHolle

    Joined:
    Jul 9, 2019
    Posts:
    10
    So, as far as the stack trace where Dispose is called on the AndroidJavaObject returned by my AndroidJavaProxy object, the following is the relevant stack trace snippet.

    at UnityEngine.AndroidJavaObject.Dispose () <0x00000 + 0xffffffff> 0 in <00000000000000000000000000000000>:0
    at UnityEngine._AndroidJNIHelper.InvokeJavaProxyMethod (UnityEngine.AndroidJavaProxy proxy, System.IntPtr jmethodName, System.IntPtr jargs) <0x00000 + 0xffffffff> 0 in <00000000000000000000000000000000>:0
     
  6. JessHolle

    JessHolle

    Joined:
    Jul 9, 2019
    Posts:
    10
    P.S. I am quite familiar with managing JNI myself in C++ and global jobjects, etc. I assume an AndroidJavaObject simply holds a global jobject. I further assume that what I'm looking for simply requires cloning an AndroidJavaObject so that we have multiple AndroidJavaObject's each with its own global jobject pointing to the same Java object.
     
  7. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,060
    Ah, yes, the return value is indeed disposed.
    As for managing object the simplest way is probably to wrap with your own class containing counter in it.
     
  8. JessHolle

    JessHolle

    Joined:
    Jul 9, 2019
    Posts:
    10
    That works fine in the case where I do "new MyAndroidJavaObjectSubclass(...)" construction.

    If I get an object back from Java via "someAndroidJavaObject.Call<AndroidJavaObject>(...)", for instance, then this does not work so nicely. A clone or copy constructor for AndroidJavaObject is sorely needed for such cases -- again unless I am missing something.
     
  9. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,060
    I mean writing domething like this:
    Code (CSharp):
    1. class JavaObject
    2. {
    3.     public AndroidJavaObject javaObj;
    4.     int refCount;
    5.  
    6.     public void Retain()
    7.     {
    8.         ++refCount;
    9.     }
    10.  
    11.     public void Release()
    12.     {
    13.         if (--refCount == 0)
    14.         {
    15.             javaObj.Dispose();
    16.             javaObj = null;
    17.         }
    18.     }
    19.  
    20.     ...
    21. }
     
  10. JessHolle

    JessHolle

    Joined:
    Jul 9, 2019
    Posts:
    10
    Yes, one can write a wrapper like that -- and then one has to pass JavaObject everywhere rather than AndroidJavaObject to compensate for AndroidJavaObject's failings here.

    And it doesn't help in the AndroidJavaProxy case where one is returning an AndroidJavaObject -- as that will call Dispose on the actual object. For that you need an AndroidJavaObject subclass that embeds reference counting. At that point you then have 2 solutions to the same issue that are incompatible from a call signature perspective.

    AndroidJavaObject needs to be fixed.
     
  11. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,060
    Sounds like valid use cases, especially proxy disposing the returned value.
    Can you report a bug for that?
     
  12. JessHolle

    JessHolle

    Joined:
    Jul 9, 2019
    Posts:
    10
    While not as efficient as an actual AndroidJavaObject copy-constructor would be (which should just be able to internally call JNI's NewGlobalRef function), I did belatedly arrive at a work-alike static factory method that can be used instead:
    Code (CSharp):
    1. private static AndroidJavaClass objectsClass;  // we never dispose of this ref to a core Java class
    2.  
    3. /// <summary>
    4. /// Produce a separately disposable reference to a given Java object.  Calling thread must be attached
    5. /// to JVM.
    6. /// </summary>
    7. /// <remarks>
    8. /// AndroidJavaObjects should be explicitly disposed of as soon as it is known that one is done using
    9. /// them -- so that the Java object can be garbage collected as needed by the JVM without waiting for
    10. /// the AndroidJavaObject in question to be implicitly disposed as part of C# garbage collection.
    11. /// In cases, however, the same Java object is used by disparate bodies of C# code that only know when
    12. /// they are done using the AndroidJavaObject, not when all C# code is done using that AndroidJavaObject.
    13. /// Worse, if one returns an AndroidJavaObject from an AndroidJavaProxy method then AndroidJavaProxy
    14. /// automatically calls Dispose() on that AndroidJavaObject outside the developer's visibility or
    15. /// control.  What is needed is an ability to easily produce multiple, separately disposable
    16. /// AndroidJavaObjects referencing the same Java object.  Unfortunately AndroidJavaObject does not
    17. /// provide a copy constructor or equivalent, so this static factory serves as a workaround for this
    18. /// API gap.  Internally it works by calling java.util.Objects.requireNonNull(Object), passing the
    19. /// incoming object and returning an AndroidJavaObject reference to the result.
    20. /// </remarks>
    21. /// <param name="androidJavaObject"></param>
    22. /// <returns>
    23. /// a separately disposable reference to the incoming AndroidJavaObject's Java object or null if called
    24. /// with a null input
    25. /// </returns>
    26. public static AndroidJavaObject NewAndroidJavaObject(AndroidJavaObject androidJavaObject)
    27. {
    28.     if (androidJavaObject == null)
    29.     {
    30.         return null;
    31.     }
    32.     // allow harmless race here in initializing 'objectsClass'
    33.     var _objectsClass = objectsClass;
    34.     if (_objectsClass == null)
    35.     {
    36.         objectsClass = _objectsClass = new AndroidJavaClass("java.util.Objects");
    37.     }
    38.     return _objectsClass.CallStatic<AndroidJavaObject>("requireNonNull", androidJavaObject);
    39. }
     
unityunity