Introduction
In an Android app, when we need to interact with external resources that may potentially take time such as to fetch data from external API or database, we would like the main UI to remain interactive and block the UI thread from functioning while long-running processes are active. Also note that by default, network task is not allowed to run in UI thread in Android.
If the main thread is used in fetching external data, then the main UI will not remain interactive while the data is being fetched and may show abnormal behavior if the data fetching process encounters an exception. In this case, the android's asynchronous task becomes handy especially for updating the portion of the UI using background threads.
An asynchronous task is one of the several ways to offload the work of main thread to some background thread. While AsyncTask
is not the only option, it is simple and quite a common option.
To get started, I would like to visit Google's developer's page that contains information on the AsyncTask
at https://developer.android.com/reference/android/os/AsyncTask to review some content related to implementing AsyncTask
s.
The AsyncTask
class is an abstract
class. The implementation usually is the subclass of the class that runs on UI thread. The implementation of the AsyncTask
, i.e., the subclass, will override at least one method and often two methods.
When an asynchronous task is executed, the task goes through 4 steps as described in the Android Developers page at https://developer.android.com/reference/android/os/AsyncTask:
onPreExecute
, invoked on the UI thread before the task is executed. This step is used to set up the task, for instance by making the spinner visible in the user interface. doInBackground(Params...
, invoked on the background thread immediately after finishes executing. This step is used to perform background computation that can take a long time. The parameters of the asynchronous task are passed to this step. The result of the computation must be returned by this step and will be passed back to the last step. This step can also be used to publish one or more units of progress. These values are published on the UI thread, in the steponProgressUpdate(Progress...)
onProgressUpdate(Progress...
, invoked on the UI thread after a call to publishProgress(Progress
... step. The timing of the execution is undefined. This method is used to display any form of progress in the user interface while the background computation is still executing. For instance, it can be used to animate a progress bar or show logs in a text field. onPostExecute(Result)
, invoked on the UI thread after the background computation finishes. The result of the background computation is passed to this step as a parameter.
I am going to go over the code to illustrate the working mechanism. The code is from the capstone project that I did for my Android Nano-degree Program at Udacity. The full code is available at https://github.com/benktesh/Capstone-Project. In this demo, I am using a lightweight version of the code as shown in the code block which is available below:
package benktesh.smartstock;
import android.app.ActivityOptions;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;
import android.widget.Toast;
import java.util.ArrayList;
import benktesh.smartstock.Model.Stock;
import benktesh.smartstock.UI.CommonUIHelper;
import benktesh.smartstock.UI.StockDetailActivity;
import benktesh.smartstock.Utils.MarketAdapter;
import benktesh.smartstock.Utils.NetworkUtilities;
import benktesh.smartstock.Utils.PortfolioAdapter;
import benktesh.smartstock.Utils.SmartStockConstant;
public class MainActivity extends AppCompatActivity implements
MarketAdapter.ListItemClickListener, PortfolioAdapter.ListItemClickListener {
private static final String TAG = MainActivity.class.getSimpleName();
CommonUIHelper mCommonUIHelper;
ArrayList<Stock> mMarketData;
private Toast mToast;
private MarketAdapter mAdapter;
private RecyclerView mMarketRV;
private ProgressBar spinner;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
spinner = findViewById(R.id.progressbar);
FloatingActionButton fab = findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent Email = new Intent(Intent.ACTION_SEND);
Email.setType(getString(R.string.label_emailtype));
Email.putExtra(Intent.EXTRA_EMAIL,
new String[]{getString
(R.string.label_developer_contat_email)});
Email.putExtra(Intent.EXTRA_SUBJECT,
R.string.label_feedback_subject);
Email.putExtra(Intent.EXTRA_TEXT,
getString(R.string.label_address_developer) + "");
startActivity(Intent.createChooser(Email, getString(R.string.label_send_feedback)));
}
});
if (mCommonUIHelper == null) {
mCommonUIHelper = new CommonUIHelper(this);
}
mMarketRV = findViewById(R.id.rv_market_summary);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
mMarketRV.setLayoutManager(layoutManager);
mMarketRV.setHasFixedSize(true);
mAdapter = new MarketAdapter(mMarketData, this);
mMarketRV.setAdapter(mAdapter);
LoadView();
}
private void LoadView() {
Log.d(TAG, "Getting Market Data Async");
new NetworkQueryTask().execute(SmartStockConstant.QueryMarket);
}
private void MakeToast(String msg) {
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
}
public boolean onCreateOptionsMenu(Menu menu) {
return mCommonUIHelper.ConfigureSearchFromMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (mCommonUIHelper.MakeMenu(item)) return true;
return super.onOptionsItemSelected(item);
}
@Override
public void onListItemClick(Stock data) {
if (mToast != null) {
mToast.cancel();
}
Intent intent = new Intent(this.getApplicationContext(), StockDetailActivity.class);
intent.putExtra(SmartStockConstant.ParcelableStock, data);
Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(this).toBundle();
startActivity(intent, bundle);
}
class NetworkQueryTask extends AsyncTask<String, Integer, ArrayList<Stock>> {
private String query;
@Override
protected void onPreExecute() {
if (spinner != null) {
spinner.setVisibility(View.VISIBLE);
}
}
@Override
protected ArrayList<Stock> doInBackground(String... params) {
query = params[0];
ArrayList<Stock> searchResults = null;
try {
searchResults = NetworkUtilities.getStockData(getApplicationContext(), query);
for (int i = 0; i <= 100; i = i + 25) {
Thread.sleep(500);
publishProgress(i);
}
} catch (Exception e) {
Log.e(TAG, e.toString());
}
return searchResults;
}
@Override
protected void onProgressUpdate(Integer... progress) {
super.onProgressUpdate(progress);
Toast.makeText(getApplicationContext(), "Progress: " +
progress[0] + "(%)", Toast.LENGTH_SHORT).show();
}
@Override
protected void onPostExecute(ArrayList<Stock> searchResults) {
super.onPostExecute(searchResults);
if (searchResults != null && searchResults.size() != 0) {
mAdapter.resetData(searchResults);
}
if (spinner.getVisibility() == View.VISIBLE) {
spinner.setVisibility(View.GONE);
}
}
}
}
The class NetworkQueryTask
implemented as a subclass of MainActivity
and the subclass extends the Android's AsyncTask abstract
class. The subclass can be defined as below:
private class NetworkQueryTask extends AsyncTask<T1, T2, T3> {...}
The T1
, T2
, and T3
are datatypes of parameters and each of them has some specific meaning.
The task defined above can be executed as below:
new NetworkAsyncTask().execute(param1);
The param1
is of the same type as the type of T1
.
The MainActivity
runs on UI thread. The 'onCreate(..)
' method does setting up the UI. The setting up involves creating adapters for recycler view and so on and finally calls a LoadView
(). The LoadView
() method executes AsyncTask
to fetch the data from the network and update the adapter of the view.
In doing so, we create a subclass NetworkQueryTask
that extends from AsyncTask
. The class takes three parameters, string
, Void
and ArrayList<Stock>
. A stock is a simple class to store information about Stock
. As soon as the process starts, we want the spinner to be visible that we can do in the doInBackground
(..) method.
In the task above, the three parameters denote the type of input parameters used for doInBackground(T1 param1)
, onProgressUpdate(T2 param2)
and onPostExecute(T3 param3)
. When the doInBackground
) step finishes execution, the param3
will be the output of the doInBackground
step and will become an input to the onPostExecute(param3)
method.
The subclass in general overrides at least one method, most often, doInBackground(..)
method and also a second method, i.e., onPostExecute()
. The onProgressUpdate
and onPreExecute()
methods are optional and can be skipped. Thus, if there is nothing to be done regarding the progress update, then there is no need for overriding onProgressUpdate
and then param2
can be of type Void
in the class definition itself. For example, suppose there is a need to pass a string
parameter to doInBackground
() and no need for an onProgressUpdate()
method and the onPostExecute()
method takes a string
parameter, then the class definition would look like below:
private class NetworkQueryTask extends AsyncTask<String, Void, String> {...}
Therefore, we can say that the three parameters respectively represent input for doInBackground
, input to onProgressUpdate()
, and input to onPostExecute
. The parameter type for output of doInBackground
is the same as the input to onPostExectute()
. Also, if the async
task is to function as fire and forget such as trigger something, then all the parameters can be void
. For example, the definition of the subclass, in this case, would look like the following:
private class NetworkQueryTask extends AsyncTask<Void, Void, Void> {...}
and the above class is executed as below:
new NetworkAsyncTask().execute();
AsyncTask
s are not aware of the other activities in the app and therefore must be handled correctly when the activity is destroyed. Therefore, AsycnTask
is not suitable for long running operations because if the app is in the background and when Android terminates the app that called the AsyncTask
, the AsyncTask
does not get killed and we have to manage the process of what to do with the results of the AsyncTask
. Thus, AsyncTask
s are useful in fetching data that is not long running. There are other alternatives to AyscTask
which are IntentServices
, Loader
, and JobScheduler
and many Java-based implementations.
A youtube video on with code demonstration is available at https://youtu.be/Yxg_janIavw.