19

I'm implementing an AsyncTask in Kotlin, and I need a WeakReference for the callback that runs in the onPostExecute() method. I set the listener reference before calling execute(), but once onPostExecute() is called, the value of WeakReference is null.

class PhotoRotationTask(uri: Uri, filePath: String, resolver: ContentResolver) : AsyncTask<Int, Int, Int>() {
    private var weakRef : WeakReference<OnBitmapProcessedListener>? = null

    var sourceUri : Uri
    var resolver : ContentResolver
    var destPath: String

    init {
        this.sourceUri = uri
        this.resolver = resolver
        this.destPath = filePath
    }

    fun setOnBitmapProcessedListener(listener: OnBitmapProcessedListener){
        weakRef = WeakReference(listener)
        Log.d("RotationTask", "set listener ${weakRef?.get() != null}") //This Log proves that weakRef is initialized before onPostExecute()
    }

    override fun doInBackground(vararg params: Int?): Int? {
        //Bitmap processing, weakRef is never called in this function
    }

    override fun onPostExecute(result: Int?) {
        Log.d("RotationTask", "result: $result") //This log proves that onPostExecute() is called eventually
        weakRef!!.get()?.onBitmapProcessed() //This implies that weakRef is not null, because app never crashes, but onBitmapProcessed is not called, so the reference is gone.
    }

}

The listener variable modifies my activity's UI, therefore it holds a reference to my activity. Activity is never recreated, my phone is still, never rotated or touched after AsyncTask starts. How is the WeakReference cleared??

gesuwall
  • 587
  • 2
  • 5
  • 15

2 Answers2

20

The problem is in the WeakReference and local variable that you pass as listener.

WeakReference is known not to keep an object from being garbage collected, so if there's no other reachable strong reference to it, it may be recycled at any moment once the method referencing it through local variable finishes. And this is exactly what happens in your case since the weak reference becomes null.

The solution is to store a strong reference to the object that is passed as listener somewhere in the calling code (as it uses the activity, the activity itself may store it in a property, so that the listener's lifetime would match that of the activity).

For example, declare a property

lateinit var currentListener: OnBitmapProcessedListener

in the activity code, then store the listener you create in that property:

val task = PhotoRotationTask(uri, filePath, resolver)

task.setOnBitmapProcessedListener(object : OnBitmapProcessedListener {
         // here goes the implementation
     }.apply { currentListener = this } // note this line
)

If multiple tasks and listeners are possible, then take care of storing all the listeners.

Community
  • 1
  • 1
hotkey
  • 140,743
  • 39
  • 371
  • 326
  • 3
    What's the purpose of having a weakReference when something it stored need to be a StrongReference? Wouldn't this prevent the object from garbage collected? (i.e. the purpose of weakReference is so that, the object it contain could be free when needed). – Elye Jul 18 '16 at 07:21
  • I am not an expert but if I have to hold a strong reference to properly use a weak reference of the same object, why not just have a manually nullable object, so I can decide when the reference can be cleared. – rgv Oct 18 '18 at 15:02
  • Usually listener interface is implemented by activity class, and activity instance is being passed to async task. Activity's reference is being kept by system - at least for the time it is not destroyed. By keeping its reference in async task as WeakReference you don't risk leaking activity references. From op's description, listener is implemented using separate class which keeps reference to Activity - at least its not clear from the original description. – marcinj Nov 22 '18 at 18:40
1

You need to hold a strong reference to the OnBitmapProcessedListener somewhere else to ensure the GC doesn't clear WeakReference.

Mike Buhot
  • 4,790
  • 20
  • 31