How to get the result of an asynchronous task on Android?

Asked

Viewed 1,222 times

3

I am trying to redeem the value of my API using .get() but it’s always falling in Exception, I believe I’m not doing it properly.

private static String APIAddress = "http://10.0.2.2/APIs/LOGINServer/server.php";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        String response = null;
        try {
            response = new APIConnect().execute(APIAddress).get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        Log.i("SERVER RESPONSE", response);
    }

    public class APIConnect extends AsyncTask<String, String, String> {

        @Override
        protected void onPreExecute() {}

        @Override
        protected String doInBackground(String... params) {
            String content;

            content = System.APIRequest(APIAddress);
            Log.i("HTTP Server", content);

            return content;
        }

        @Override
        protected void onPostExecute(String result) {
            super.onPostExecute(result);
        }
    }
  • Raphael, I don’t recommend calling the method get of AsyncTask within the onCreate. The method get will block the Main Thread waiting for the result, which will cause a bad user experience if this task takes long. This way will lock your app, with that black screen, until the end of your task. I recommend using the method onPostExecute to update the UI.

  • How would that be in this case? I need the content returned by System.APIRequest(APIAddress) can be accessible.

  • 1

    Like you made her APIConnect as an inner class of Activity and non-static, the APIConnect implicitly has a reference to the Activity, so just call the methods that update the UI there in the method onPostExecute. If the APIConnect whether external or static, would have to use some standard to upgrade, whether using Observer or with a reference to the Activity.

1 answer

6


It is not recommended (although it is possible) to call the method get of AsyncTask within the Main Thread, because in addition to blocking a task that should be asynchronous it causes a bad user experience.

With the Main Thread blocked in the onCreate, the user will be seeing that black screen until the task ends, with the risk of having a ANR.

You can see more details about this in my reply: How to use ksoap2 library.


The best way I consider for this kind of use is by using Loaders.

The Loaders appeared in API 11 (Android 3.0) as an adaptation of the AsyncTask for the life cycle both of Fragments how much of Activity.

For versions prior to API 11, you can use Support Library v4 that it makes the compatibility, simply extend FragmentActivity.

That means that the Loader is highly related to the life cycle of the Activity or of Fragment, and its management is done automatically by LoaderManager.

One important detail to consider using is that the first time you create the Loader it will run the processing. But in case the Activity will be destroyed, no matter if processing is over or not, it will always update the Activity correct. This means that on Monday Activity the LoaderManager will reuse the Loader previous avoiding unnecessary processing.

To use a Loader, I will consider using the Support Library, but the calls are similar.

Class APIConnectLoader

public class APIConnectLoader extends AsyncTaskLoader<String> {

    String mResult;
    String mAPIAddress;

    public APIConnectLoader(Context context, String APIAddress) {
        super(context);
        mAPIAddress = APIAddress;
    }

    /****************************************************/
    /** (1) A task that performs the asynchronous load **/
    /****************************************************/
    @Override
    public String loadInBackground() {
        return System.APIRequest(mAPIAddress);
    }

    /********************************************************/
    /** (2) Deliver the results to the registered listener **/
    /********************************************************/
    @Override
    public void deliverResult(String data) {
        if(isReset()) {
            releaseResources(data);
            return;
        }

        String oldData = mResult;
        mResult = data;

        if(isStarted()) {
            super.deliverResult(data);
        }

        if(oldData != null && oldData != data) {
            releaseResources(oldData);
        }
    }

    /*********************************************************/
    /** (3) Implement the Loader’s state-dependent behavior **/
    /*********************************************************/
    @Override
    protected void onStartLoading() {
        if(mResult != null) {
            deliverResult(mResult);
        }

        if (takeContentChanged() || mResult == null) {
            // When the observer detects a change, it should call onContentChanged()
            // on the Loader, which will cause the next call to takeContentChanged()
            // to return true. If this is ever the case (or if the current data is
            // null), we force a new load.
            forceLoad();
        }
    }

    @Override
    public void stopLoading() {
        // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    @Override
    public void onCanceled(String data) {
        releaseResources(data);
    }

    @Override
    protected void onReset() {
        super.onReset();

        onStopLoading();

        releaseResources(mResult);
        mResult = null;
    }

    @Override
    protected void onStopLoading() {
        cancelLoad();
    }

    protected void releaseResources(String data) {
        // For a simple List, there is nothing to do. For something like a Cursor, we
        // would close it in this method. All resources associated with the Loader
        // should be released here.
    }

    public void refresh() {
        mResult = null;
        onContentChanged();
    }
}

Class MainActivity

public class MainActivity extends FragmentActivity implements LoaderManager.LoaderCallbacks<String> {

    private static String APIAddress = "http://10.0.2.2/APIs/LOGINServer/server.php";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Inicia o Loader, ou recupera o Loader anterior caso exista
        // O LoaderManager eh quem ira verificar a existencia de um Loader
        // anterior
        getSupportLoaderManager().initLoader(ID_DO_LOADER, null, this);
        // Se nao usar o Support Library use o getLoaderManager ao inves
        // do getSupportLoaderManager
    }

    @Override
    public Loader<String> onCreateLoader(int id, Bundle args) {
        // Instancia o AsyncTaskLoader
        return new APIConnectLoader(APIAddress);
    }

    @Override
    public void onLoadFinished(Loader<String> loader, String data) {
        // Atualizar UI de acordo com o resultado (data)
    }

    @Override
    public void onLoaderReset(Loader<String> loader) {
        // Nao precisa fazer nada no caso de uma String,
        // Se fosse um cursor, teria que limpar recursos
        // referentes ao cursor anterior
    }
}

Like you made her APIConnect as an inner class of Activity and non-static, the APIConnect implicitly has a reference to the Activity, so just call the methods that update the UI there in the method onPostExecute.

If the APIConnect whether external or static, would have to use some standard to upgrade, whether using Observer or with a reference to the Activity.

In your case, a sketch would be:

public class APIConnect extends AsyncTask<String, String, String> {

    @Override
    protected void onPreExecute() {}

    @Override
    protected String doInBackground(String... params) {
        String content;

        content = System.APIRequest(APIAddress);
        Log.i("HTTP Server", content);

        return content;
    }

    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);

        // Nesse momento podemos atualizar a UI,
        // porque esse código esta sendo executado
        // na Main Thread.
        setTextInActivity(result);
        // result é o valor de content do doInBackground
    }
}

The method setTextInActivity may be stated in its Activity, that the APIConnect will have access.


Using a Inner Class to AsyncTask has an implicit reference to the Activity, what is bad thinking about the life cycle of Activity, which causes a Memory Leak and given that the Activity is a very large object, can cause long-term problems.

The Memory Leak is caused as follows:

  1. One AsyncTask is initiated (having the implicit reference to the Activity).
  2. In the meantime, before the end of the AsyncTask, to Activity is destroyed. Generating a new Activity.
  3. While the AsyncTask not end, the Activity destroyed will not be collected by Garbage Collector, keeping a heavy and unnecessary object in memory. And furthermore, when the AsyncTask finish, the old Activity is who will be updated, can cause several errors, since it has already been destroyed.

A simple solution would be to create a subclass of AsyncTask external and use the standard Observer to update the UI. Remembering to cancel the AsyncTask and remove the reference from Listener when the Activity is destroyed.

Class APIConnect

public class APIConnect extends AsyncTask<String, String, String> {

    private APIConnectListener mListener;
    // doInBackground continua o mesmo.

    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);

        if(mListener != null) {
            mListener.updateUI(result);
        }
    }

    @Override
    protected void onCancelled () {
        // Cancelar tudo que estiver fazendo.
        // Remover a referência para o Listener, a fim de evitar memory leak
        mListener = null;
    }

    // Getter e Setter do Listener

    // Definicao da interface Observer
    public static interface APIConnectListener {
        public void updateUI(String result);
    }
}

Class Activity

public class MainActivity extends Activity implements APIConnect.APIConnectListener {

    private static String APIAddress = "http://10.0.2.2/APIs/LOGINServer/server.php";
    APIConnect mAPIConnect;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mAPIConnect = new APIConnect();
        mAPIConnect.setAPIConnectListener(this);

        mAPIConnect.execute(APIAddress);
    }

    @Override
    public void onDestroy() {
        // Cancela a AsyncTask e limpa a referência
        mAPIConnect.cancel();
        mAPIConnect = null;
    }

    @Override
    public void updateUI(String result) {
        // Atualiza a UI com o resultado da APIConnect
    }
}

References:

  1. http://developer.android.com/guide/components/loaders.html
  2. http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html
  • String result = new APIConnect().execute() This way I access the result value?

  • Yes, the get will return the same as the method doInBackground. But this will lock your interface while running.

  • You could let me know if keeping my Apiconnect class out of the internal Activity class and static is a better way?

  • 1

    @Raphaelcastro, yes, keep out is better (I believe the best reason is to avoid Memory Leaks) and only needs to use one Listener to notify the Activity that the processing has ended. I can include this in my reply shortly.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.