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:
- One
AsyncTask
is initiated (having the implicit reference to the Activity
).
- In the meantime, before the end of the
AsyncTask
, to Activity
is destroyed. Generating a new Activity
.
- 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:
- http://developer.android.com/guide/components/loaders.html
- http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html
Raphael, I don’t recommend calling the method
get
ofAsyncTask
within theonCreate
. The methodget
will block theMain 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 methodonPostExecute
to update the UI.– Wakim
How would that be in this case? I need the content returned by
System.APIRequest(APIAddress)
can be accessible.– Rafael Alexandre
Like you made her
APIConnect
as an inner class ofActivity
and non-static, theAPIConnect
implicitly has a reference to theActivity
, so just call the methods that update the UI there in the methodonPostExecute
. If theAPIConnect
whether external or static, would have to use some standard to upgrade, whether usingObserver
or with a reference to theActivity
.– Wakim