How do I get results from an asynchronous task on Android?

3

I'm trying to retrieve the value of my API using .get() but it's always falling into Exception, I believe I'm not doing it correctly.

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);
        }
    }
    
asked by anonymous 26.10.2014 / 02:02

1 answer

6

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

With Main Thread locked in onCreate , the user will see that black screen until the task finishes, with the risk of having an ANR .

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

The best way I consider this type of usage is to use Loaders .

Loaders appeared in API 11 (Android 3.0) as an adaptation of the AsyncTask API to the lifecycle of both Fragments and Activity .

For versions prior to API 11, you can use the Support Library v4 as it does the compatibility by simply extending FragmentActivity .

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

An important detail to consider using is that the first time you create Loader it will perform the processing. But if Activity is destroyed, it does not matter if the processing is finished or not, it will always update to Activity correct. This means that in the second Activity the LoaderManager will reuse the Loader previous avoiding unnecessary processing.

To use Loader , I'll consider using Support Library , but 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
    }
}

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

If the APIConnect was external or static, you would have to use some default to update, either using Observer or saving a reference to Activity .

In your case, an outline 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 setTextInActivity method can be declared in its Activity , that APIConnect will have access.

Using a Inner Class a AsyncTask has an implicit reference to Activity , which is bad considering the Activity lifecycle, which causes a Memory Leak and since Activity is a very large object, it can cause problems in the long run.

Memory Leak is caused as follows:

  • A AsyncTask is started (having the implicit reference to Activity ).
  • In the meantime, before the end of AsyncTask , Activity is destroyed. Generating a new Activity .
  • As long as AsyncTask does not finish,% destroyed% will not be collected by Activity , keeping a heavy and unnecessary object in memory. Also, when Garbage Collector finishes, the old AsyncTask is who will be updated, which can cause several errors, since it has already been destroyed.
  • A simple solution would be to create a subclass of the Activity external and use the AsyncTask pattern to update the Observer . Remember to cancel UI and remove the AsyncTask reference when Listener is destroyed.

    Class Activity

    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 APIConnect

    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:

  • link
  • link
  • 26.10.2014 / 02:15