What's the correct way to implement AsyncTask? static or non static nested class?

21,600

Solution 1

In general I would recommend the static implementation (though both are acceptable).

The Google approach will require less code but your asynctask will be tightly coupled with your actitivy (which means not easily reusable). But sometimes this approach is more readable.

With the CommonsGuy approach, it will require more effort (and more code) to decouple activity and asynctask, but in the end you will have a more modular, more reusable code.

Solution 2

There's no single "correct" way of implementing AsyncTask. But here are my two cents:

This class is intended to perform "light" work in the context of an Activity. That's why it has the methods onPreExecute, onProgressUpdate, onPostExecute running in the UI thread, so that they can access fields and update GUI quickly. Any task that might take a longer time to complete and it is not meant to update a specific activity should be moved to a Service.

Those methods are mostly used to update the GUI. As the GUI is something related to the Activity instance (the fields are likely declared as private member variables), it is more convenient to implement the AsyncTask as a non-static nested class. It is also the most natural way in my opinion.

In case the task is going to be reused in other activities, I think it should be allowed to have its own class. To be honest, I'm no fan of static nested classes, especially inside views. If it is a class it means it is conceptually different than the activity. And if it is static it means it is not related to this concrete instance of the activity. But as they are nested, those classes are visually inside the parent class making it harder to read, and can go unnoticed in the project package explorer as it only shows files. And despite being less coupled than inner classes, this is not really that useful: if the class changes, you have to merge/commit the whole parent file to the version control. If you where to reuse it, then you'll have to access it as Parent.Nested everywhere. So in order to not to couple other activities to the Parent class, you probably would like to refactor it and extract the nested class to its own file.

So for me the question would be Inner Class vs Top-Level Class.

Solution 3

The linked article already says it

This does emphasize, though, that you want doInBackground() of your AsyncTask to be completely decoupled from the Activity. If you only touch your Activity on the main application thread, your AsyncTask can survive the orientation change intact.

Don't touch the Activity (e.g. its members) from the AsyncTask, which is in line with Static Nested Classes

Static Nested Classes
As with class methods and variables, a static nested class is associated with its outer class. And like static class methods, a static nested class cannot refer directly to instance variables or methods defined in its enclosing class — it can use them only through an object reference.

Although, the examples from Android, AsyncTask reference and Using AsyncTask are still using non-static nested classes.

And according to this Static nested class in Java, why?, I would first go with the static inner class and resort to the non-static version only, if it is really necessary.

Solution 4

I found out that a non-static nested Asynctask UI update is faster when you need to update your UI frequently by calling runOnUiThread in onProgressUpdate. For example, when you have to append lines to a TextView.

non-static:
    @Override
    protected void onProgressUpdate(String... values) {
        runOnUiThread(() -> {
            TextView tv_results = findViewById(R.id.tv_results);
            tv_results.append(values[0] + "\n");
        });
    }

It's 1000x faster than implementing listeners for a static AsyncTask. I might be wrong, but that was my experience.

static:
        @Override
        protected void onProgressUpdate(String... values) {
            OnTaskStringUpdatedListener.OnTaskStringUpdated(task, values[0]);
        }
Share:
21,600
roxrook
Author by

roxrook

C++ is fun!

Updated on September 19, 2020

Comments

  • roxrook
    roxrook almost 4 years

    The "Login" from Android examples implemented AsyncTask as a non-static inner class. However, according to Commonsguys, this class should be static and use weak-reference to the outside activity see this.

    So what is the correct way to implement AsyncTask? Static or non-static?

    Commonsguy Implementation
    https://github.com/commonsguy/cw-android/tree/master/Rotation/RotationAsync/

    Log in example from Google

    package com.example.asynctaskdemo;
    
    import android.animation.Animator;
    import android.animation.AnimatorListenerAdapter;
    import android.annotation.TargetApi;
    import android.app.Activity;
    import android.os.AsyncTask;
    import android.os.Build;
    import android.os.Bundle;
    import android.text.TextUtils;
    import android.view.KeyEvent;
    import android.view.Menu;
    import android.view.View;
    import android.view.inputmethod.EditorInfo;
    import android.widget.EditText;
    import android.widget.TextView;
    
    /**
     * Activity which displays a login screen to the user, offering registration as
     * well.
     */
    public class LoginActivity extends Activity {
        /**
         * A dummy authentication store containing known user names and passwords.
         * TODO: remove after connecting to a real authentication system.
         */
        private static final String[] DUMMY_CREDENTIALS = new String[] { "[email protected]:hello", "[email protected]:world" };
    
        /**
         * The default email to populate the email field with.
         */
        public static final String EXTRA_EMAIL = "com.example.android.authenticatordemo.extra.EMAIL";
    
        /**
         * Keep track of the login task to ensure we can cancel it if requested.
         */
        private UserLoginTask mAuthTask = null;
    
        // Values for email and password at the time of the login attempt.
        private String mEmail;
        private String mPassword;
    
        // UI references.
        private EditText mEmailView;
        private EditText mPasswordView;
        private View mLoginFormView;
        private View mLoginStatusView;
        private TextView mLoginStatusMessageView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            setContentView(R.layout.activity_login);
    
            // Set up the login form.
            mEmail = getIntent().getStringExtra(EXTRA_EMAIL);
            mEmailView = (EditText) findViewById(R.id.email);
            mEmailView.setText(mEmail);
    
            mPasswordView = (EditText) findViewById(R.id.password);
            mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
                @Override
                public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
                    if (id == R.id.login || id == EditorInfo.IME_NULL) {
                        attemptLogin();
                        return true;
                    }
                    return false;
                }
            });
    
            mLoginFormView = findViewById(R.id.login_form);
            mLoginStatusView = findViewById(R.id.login_status);
            mLoginStatusMessageView = (TextView) findViewById(R.id.login_status_message);
    
            findViewById(R.id.sign_in_button).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    attemptLogin();
                }
            });
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            super.onCreateOptionsMenu(menu);
            getMenuInflater().inflate(R.menu.activity_login, menu);
            return true;
        }
    
        /**
         * Attempts to sign in or register the account specified by the login form.
         * If there are form errors (invalid email, missing fields, etc.), the
         * errors are presented and no actual login attempt is made.
         */
        public void attemptLogin() {
            if (mAuthTask != null) {
                return;
            }
    
            // Reset errors.
            mEmailView.setError(null);
            mPasswordView.setError(null);
    
            // Store values at the time of the login attempt.
            mEmail = mEmailView.getText().toString();
            mPassword = mPasswordView.getText().toString();
    
            boolean cancel = false;
            View focusView = null;
    
            // Check for a valid password.
            if (TextUtils.isEmpty(mPassword)) {
                mPasswordView.setError(getString(R.string.error_field_required));
                focusView = mPasswordView;
                cancel = true;
            }
            else if (mPassword.length() < 4) {
                mPasswordView.setError(getString(R.string.error_invalid_password));
                focusView = mPasswordView;
                cancel = true;
            }
    
            // Check for a valid email address.
            if (TextUtils.isEmpty(mEmail)) {
                mEmailView.setError(getString(R.string.error_field_required));
                focusView = mEmailView;
                cancel = true;
            }
            else if (!mEmail.contains("@")) {
                mEmailView.setError(getString(R.string.error_invalid_email));
                focusView = mEmailView;
                cancel = true;
            }
    
            if (cancel) {
                // There was an error; don't attempt login and focus the first
                // form field with an error.
                focusView.requestFocus();
            }
            else {
                // Show a progress spinner, and kick off a background task to
                // perform the user login attempt.
                mLoginStatusMessageView.setText(R.string.login_progress_signing_in);
                showProgress(true);
                mAuthTask = new UserLoginTask();
                mAuthTask.execute((Void) null);
            }
        }
    
        /**
         * Shows the progress UI and hides the login form.
         */
        @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
        private void showProgress(final boolean show) {
            // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
            // for very easy animations. If available, use these APIs to fade-in
            // the progress spinner.
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
                int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
    
                mLoginStatusView.setVisibility(View.VISIBLE);
                mLoginStatusView.animate().setDuration(shortAnimTime).alpha(show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
                    }
                });
    
                mLoginFormView.setVisibility(View.VISIBLE);
                mLoginFormView.animate().setDuration(shortAnimTime).alpha(show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
                    }
                });
            }
            else {
                // The ViewPropertyAnimator APIs are not available, so simply show
                // and hide the relevant UI components.
                mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
                mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
            }
        }
    
        /**
         * Represents an asynchronous login/registration task used to authenticate
         * the user.
         */
        public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {
            @Override
            protected Boolean doInBackground(Void... params) {
                // TODO: attempt authentication against a network service.
    
                try {
                    // Simulate network access.
                    Thread.sleep(2000);
                }
                catch (InterruptedException e) {
                    return false;
                }
    
                for (String credential : DUMMY_CREDENTIALS) {
                    String[] pieces = credential.split(":");
                    if (pieces[0].equals(mEmail)) {
                        // Account exists, return true if the password matches.
                        return pieces[1].equals(mPassword);
                    }
                }
    
                // TODO: register the new account here.
                return true;
            }
    
            @Override
            protected void onPostExecute(final Boolean success) {
                mAuthTask = null;
                showProgress(false);
    
                if (success) {
                    finish();
                }
                else {
                    mPasswordView.setError(getString(R.string.error_incorrect_password));
                    mPasswordView.requestFocus();
                }
            }
    
            @Override
            protected void onCancelled() {
                mAuthTask = null;
                showProgress(false);
            }
        }
    }
    

    If it depends on a specific situation, then with ListView items (text + plus Bitmap) loaded from the internet using HttpClient, how should I implement my AsyncTask?

  • roxrook
    roxrook over 11 years
    From my understanding, a non-static nested class keep a reference to it outside class. If the user suddenly cancels the current activity (hit the back button) while there are several tasks are still queued in the thread pools. Then will this approach (non-static) potentially create memory leakage since the GC won't be able to reclaim the memory for that activity. Am I understanding this correctly? Btw, thanks a lot.
  • Mister Smith
    Mister Smith over 11 years
    @Chan AsyncTasks are prone to leakage, both inner and static nested ones. If the device changes configuration and the activity is recreated, it's easy to forget cancelling the old task and then it's left running in the bg.
  • roxrook
    roxrook over 11 years
    @MisterSmith: Thanks. So is there any other alternative method?
  • Hanif
    Hanif about 5 years
    onProgressUpdate runs only in UI thread. what is the need for runOnUiThread()?