Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android - RxJava vs AsyncTask to prevent getActivity() memory leak

How does using RxJava (or RxAndroid,etc) instead of AsyncTask in Android help prevent a context leak? In AsyncTask, if you execute it and user leaves the app, then the activity context can be null and the app can crash. I have heard that RxJava can help prevent this type of crash when doing threading. I also heard that it can do better error handling then the doInBackground method of AsyncTask (which handles errors badly). Most of the time I just return null (for example) in doInBackground if anything fails, but I've read that RxJava can return the exact error and not leak. Can anyone give an example?

Here is a small demo of a crash in AsyncTask if the user leaves the app while it's trying to report results to UI:

     @SuppressWarnings("unused")
private class GetTask extends AsyncTask<Void, Void, Void> {         

    @Override
    protected void onPostExecute(String result) {         
        pd = new ProgressDialog(getActivity().getApplicationContext());//can crash right here
        pd.setTitle("Grabbing Track!");
        pd.setMessage("Please wait...");
        pd.setCancelable(false);
        pd.setIndeterminate(true);
        pd.show();
    }}

And here is a doInBackground method call that does not send out errors that are useful:

@Override 
protected String doInBackground(String... params) {
    String myIntAsString = 1/0 + ""; //this should give an error (how do we report it to the caller??
                                  //or if we are parsing json and it fails, how do we report it to the caller cleanly. Can RxJava help?
}
like image 833
j2emanue Avatar asked Feb 04 '15 15:02

j2emanue


2 Answers

I think the good thing about RxJava is if you have a bunch of tasks you can put them in a sequence such that you know when one finishes and the next is about to start. in a AsyncTask if you have more then one running, you have NO guarantee which task will complete first and then you have to do alot of error checking if you care about order. So RxJava allows you to sequence calls.

In regards to memory leaks we can have a AsyncTask as an inner class of an activity. Now since its tied to the activity when the activity is destroyed that context is still hanging around and wont be garbage collected, thats the memory leak part.

Here is where RxJava can help. if any errors occur at all then we can call the subscribers onError method. The subcriber can look like this:

    public Observable<JsonObject> get_A_NetworkCall() {
    // Do your network call...but return an observable when done
}

Subscription subscription = get_A_NetworkCall()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(new Observer<jsonResponse>() {

        @Override
        public void onCompleted() {
             // Update UI
        }

        @Override
        public void onError() {
             // show error on UI
        }

        @Override
        public void onNext(JsonObject response) {
             // Handle result of jsonResponse 
        }
});

or something similar - this is psuedocode . The point is you can report the errors more cleanly and switch threads in one line. Here we are reporting on androids main thread but doing the work on a new thread. After we are done in our activities onDestroy method we can simply unsubscribe to the observable and it kills it and prevents any memory leaks that we encounter with AsyncTask. This to me should be the replacement for any asyncTasks.

like image 129
j2emanue Avatar answered Oct 19 '22 20:10

j2emanue


I use a combination of two things. First, RxAndroid is really helpful: https://github.com/ReactiveX/RxAndroid

You can use AppObservable.bindActivity on it to bind an observable such that its output is observed on the main thread, and if the activity is scheduled to be destroyed, messages will not be forwarded. You still have to manage the pause/resume lifecycle though. For that, I use a composite subscription like this (pseudojava coming up):

public class MyActivity extends Activity {
  private final CompositeSubscription subscriptions = new CompositeSubscription();

  @Override
  public void onResume() {
    super.onResume();
    subscriptions.add(AppObservable.bindActivity(this, myObservable)
        .subscribe());
    subscriptions.add(AppObservable.bindActivity(this, myOtherObservable)
        .subscribe());
  }

  @Override
  public void onPause() {
    subscriptions.clear();
    super.onPause();
  }

}

Obviously you'd want to do more in subscribe if you want to do something with the data, but the important thing is to collect the returned Subscription instances and add them to the CompositeSubscription. When it clears them out, it will unsubscribe them as well.

Using these 'two weird tricks' should keep things from coming back to the Activity when it is an inoperative state.

like image 5
lopar Avatar answered Oct 19 '22 19:10

lopar