简体   繁体   中英

Handle AsyncTask if the task completes while the Activity is in the background

I've been using AsyncTasks for a while however, I've recently encountered a scenario where I'm unsure of how to handle correctly. Since I thought it would be a somewhat common scenario I decided to ask the question here.

So, I'm trying to use an AsyncTask to make a simple call to sign a user in to the app. After the call completes, if it succeeds, the user should be taken to another activity. This logic is simple. The problem arrises when the user navigates away from the app before the sign in call returns. In such a case, what should I do in onPostExecute() ?

What I've seen some apps do is they continue with the call anyways, as long as the activity is still around, and will launch the next activity. However this creates a weird experience where the user navigates away from the app, then several seconds later, the app just pops back up in their face. Of course, I would like to avoid doing this.

Update Example code:

public class ExampleActivity extends Activity {
    private boolean mIsPaused;
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        Button btnSignIn = (Button) findViewById(R.id.btn_sign_in);
        btnSignIn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                new SignInTask(ExampleActivity.this).execute();
            }
        });
        ...
    }

    @Override
    protected void onPause() {
        super.onPause();
        mIsPaused = true;
    }

    @Override
    protected void onResume() {
        super.onResume();
        mIsPaused = false;
    }

    private boolean isPaused() {
        return mIsPaused;
    }

    ...
    private static class SignInTask extends AsyncTask<Void, Void, SomeResult> {

        private final WeakReference<ExampleActivity> mAct;

        public SignInTask(ExampleActivity act) {
            mAct = new WeakReference<ExampleActivity>(act);
        }

        @Override
        protected SomeResult doInBackground(Void... params) {
            return mApi.signIn(creds);
        }

        @Override
        protected void onPostExecute(SomeResult result) {
            if (result.getCode() == OK) {
                ExampleActivity act = mAct.get();
                if (act != null) {
                    if (act.isPaused()) {
                        // do something
                    } else {
                        startActivity(new Intent(act, NextActivity.class));
                    }
                } else {
                    // do something
                }
            }

        }
    }
}

使您的AsyncTask类成为静态内部类。

Pretty interesting problem... Going with what you've started by using booleans, you could save the response the Activity receives to the SharedPreferences in the event it is paused, or continue processing normally if it is not. If the Activity later resumes (or is recreated), check whether or not there is a saved response and handle accordingly. I was thinking something along the lines of:

import org.json.JSONObject;

import android.app.Activity;
import android.os.Bundle;

public class TaskActivity extends Activity {

    private static final String KEY_RESPONSE_JSON = "returned_response";

    private boolean paused = false;

    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        // don't setup here, wait for onPostResume() to figure out what to do
    }

    @Override
    public void onPostResume(){
        super.onPostResume();
        paused = false;

        if(isSavedResponseAvailable()) processResponse(getSavedResponse());
        else setup();
    }

    @Override
    public void onPause(){
        paused = true;
        super.onPause();
    }

    private void setup(){ 
        // normal setup
    }

    public void onReceiveResponse(JSONObject response){
        if(paused) setSavedResponse(response);
        else processResponse(response); 
    }

    private void processResponse(JSONObject response){
        // Continue with processing as if they never left

        getSharedPreferences(this.getClass().getName(), 0).edit().clear().commit(); // Clear everything so re-entering won't parse old data
    }   

    private boolean isSavedResponseAvailable(){
        return getSavedResponse() != null;
    }

    private JSONObject getSavedResponse(){
        try{
            return new JSONObject(getSharedPreferences(this.getClass().getName(), 0).getString(KEY_RESPONSE_JSON, ""));
        }
        catch(Exception e){ }
        return null;
    }

    private void setSavedResponse(JSONObject response){
        getSharedPreferences(this.getClass().getName(), 0).edit().putString(KEY_RESPONSE_JSON, response.toString()).commit();
    }
}

Clearly that's assuming your response from the task is JSON, but there's no reason you couldn't extend that to save the data individually and rebuild the necessary response object from the saved preference data.

As far as clean approaches go, though... I give this about a 3/10, but I can't think of anything better (well, other than making the TaskActivity abstract and forcing implementations to override setup(), processResponse(), isResponseAvailable(), getSavedResponse(), and setSavedResponse() , but that would only be mildly better for like a 4/10)

I would suggest putting a try/catch statement in the post execute - as far as I know what would happen in this situation is that you would get some kind of Window Manager exception.

What I would STRONGLY recommend, however, is stopping any async tasks (with the cancel method) on the onPause method, meaning that you won't interrupt them.

http://developer.android.com/reference/android/os/AsyncTask.html#cancel(boolean)

public final boolean cancel (boolean mayInterruptIfRunning)

Added in API level 3 Attempts to cancel execution of this task. This attempt will fail if the task has already completed, already been cancelled, or could not be cancelled for some other reason. If successful, and this task has not started when cancel is called, this task should never run. If the task has already started, then the mayInterruptIfRunning parameter determines whether the thread executing this task should be interrupted in an attempt to stop the task.

Calling this method will result in onCancelled(Object) being invoked on the UI thread after doInBackground(Object[]) returns. Calling this method guarantees that onPostExecute(Object) is never invoked. After invoking this method, you should check the value returned by isCancelled() periodically from doInBackground(Object[]) to finish the task as early as possible.

Parameters mayInterruptIfRunning
true if the thread executing this task should be interrupted; otherwise, in-progress tasks are allowed to complete. Returns false if the task could not be cancelled, typically because it has already completed normally; true otherwise See Also isCancelled() onCancelled(Object)

boolean isRunning; //set it to true in onResume, and false in onStop
boolean isWaiting; // set it to true in onPostExecute, if "isRunning" is false

check in onResume whether isWaiting is true, if yes, take user to another screen.

   Use the cancel() of AsynchTask class onBackPress() of Activty class


 public class ExampleActivity extends Activity {
private boolean mIsPaused;
SignInTask singleTaskObj;
...
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    Button btnSignIn = (Button) findViewById(R.id.btn_sign_in);
    btnSignIn.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
           singleTaskObj =  new SignInTask(ExampleActivity.this).execute();
        }
    });
    ...
}

@Override
protected void onPause() {
    super.onPause();
    mIsPaused = true;
}

@Override
protected void onResume() {
    super.onResume();
    mIsPaused = false;
}

protected void onBackPressed()
{
 singleTaskObj.cancel();
}

private boolean isPaused() {
    return mIsPaused;
}

...
private static class SignInTask extends AsyncTask<Void, Void, SomeResult> {

    private final WeakReference<ExampleActivity> mAct;

    public SignInTask(ExampleActivity act) {
        mAct = new WeakReference<ExampleActivity>(act);
    }

    @Override
    protected SomeResult doInBackground(Void... params) {
        return mApi.signIn(creds);
    }

    @Override
    protected void onPostExecute(SomeResult result) {
        if (result.getCode() == OK) {
            ExampleActivity act = mAct.get();
            if (act != null) {
                if (act.isPaused()) {
                    // do something
                } else {
                    startActivity(new Intent(act, NextActivity.class));
                }
            } else {
                // do something
            }
        }

    }
}
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM