How to prevent Context leak in AsyncTask used by JobService

2.3k views Asked by At

I need to do some background work which requires a Context in a JobService (I'm using the Firebase JobDispatcher one because we support api 16+) I've read a lot of articles about the JobService and AsyncTasks but I'm unable to find any good articles on how to combine them if you need a Context.

My JobService

import com.firebase.jobdispatcher.JobParameters;
import com.firebase.jobdispatcher.JobService;

public class AsyncJobService extends JobService {

    @Override
    public boolean onStartJob(JobParameters job) {
        new AsyncWork(this, job).execute();
        return true;
    }

    @Override
    public boolean onStopJob(JobParameters job) {
        return false;
    }
}

My AsyncTask

import android.os.AsyncTask;
import com.firebase.jobdispatcher.JobParameters;
import com.firebase.jobdispatcher.JobService;

class AsyncWork extends AsyncTask<Void, Void, Void> {

    private JobService jobService;

    private JobParameters job;

    AsyncWork(JobService jobService, JobParameters job) {
        this.jobService = jobService;
        this.job = job;
    }

    @Override
    protected Void doInBackground(Void... voids) {
        // some work that needs context
        return null;
    }

    @Override
    protected void onPostExecute(Void aVoid) {
        super.onPostExecute(aVoid);
        // some work that needs context
        jobService.jobFinished(job, false);
    }
}

This gives a warning that the jobService property in the AsyncWork class is leaking a context object. I understand why this is the case if you pass an Activity or Fragment but this is a JobService which should exist untill I call jobFinished(). Am I doing something wrong or can I ignore the warning?

2

There are 2 answers

5
G. Blake Meike On

You cannot ignore the warning. Because AsyncWork holds the reference to the Context, the Context cannot be GCed until the task is complete: the Context memory leaks. There are two solutions:

  1. Use a context that is long-lived, anyway, the Application context.
  2. Tie the life of the async task to the life of the context to which it hold a reference: cancel it in onPause
1
AmrDeveloper On

To handle leaking you need to use WeakRefernce class with JobService Object

class AsyncWork extends AsyncTask<Void, Void, Void> {

  private WeakReference<JobService> jobServiceWeakReference;
  private JobParameters job;

  AsyncWork(JobService jobService, JobParameters job) {
      this.jobServiceWeakReference = new WeakReference<>(jobService);
      this.job = job;
  }

  @Override
  protected Void doInBackground(Void... voids) {
      // some work that needs context
        return null;
    }

  @Override
  protected void onPostExecute(Void aVoid) {
      super.onPostExecute(aVoid);
      // some work that needs context
      jobServiceWeakReference.get().jobFinished(job, false);
  }
}