In this blog, we will look at a very useful and important feature provided by Android – AsyncTask and more importantly how AsyncTasks have been put to use for various functionalities throughout the PSLab Android Project
What are Threads?
Threads are basically paths of sequential execution within a process. In a way, threads are lightweight processes. A process may contain more than one threads and all these threads are executed in parallel. Such a method is called “Multithreading”. Multithreading is very useful when some long tasks need to be executed in the background while other tasks continue to execute in the foreground.
Android has the main UI thread which works continuously and interacts with a user to display text, images, listen for click and touch, receive keyboard inputs and many more. This thread needs to run without any interruption to have a seamless user experience.
When AsyncTask comes into the picture?
AsyncTask enables proper and easy use of the UI thread. This class allows you to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.
In PSLab Android application, we communicate with PSLab hardware through I/O(USB) interface. We connect the PSLab board with the mobile and request and wait for data such as voltage values and signal samples and once the data is received we display it as per requirements. Now clearly we can’t run this whole process on the main thread because it might take a long time to finish and because of that other UI tasks would be delayed which eventually degrade the user experience. So, to overcome this situation, we use AsyncTasks to handle communication with PSLab hardware.
Methods of AsyncTask
AsyncTask is an Abstract class and must be subclassed to use. Following are the methods of the AsyncTask:
- onPreExecute()
- Used to set up the class before the actual execution
- doInBackground(Params…)
- This method must be overridden to use AsyncTask. This method contains the main part of the task to be executed. Like the network call etc.
- The result from this method is passed as a parameter to onPostExecute() method
- onProgressUpdate(Progress…)
- This method is used to display the progress of the AsyncTask
- onPostExecute(Result)
- Called when the task is finished and receives the results from the doInBackground() method
There are 3 generic types passed to the definition of the AsyncTask while inheriting. The three types in order are
- Params: Used to pass some parameters to doInBackground(Params…) method of the Task
- Progress: Defines the units in which the progress needs to be displayed/
- Result : Defines the data type to be returned from onInBackground() and receive as a parameter in the onPostExecute(Result) method
Example of the usage of the AsyncClass is as under :
private class SampleTask extends AsyncTask<Params, Progress, Result> { @Override protected Result doInBackground(Params... params) { // The main code goes here return result; } @Override protected void onProgressUpdate(Progress... progress) { // display the progress } @Override protected void onPostExecute(Result result) { // display the result } }
We can create an instance of this class as under and execute it.
SampleTask sampleTask = new SampleTask(); sampleTask.execute(params)
We can cancel a running class by calling the task.cancel() function
sampleTask.cancel()
AsyncTask in PSLab Android Application
As mentioned earlier some task which takes a lot of time, can’t be executed on the main thread. Hence in such cases AsyncTask is used. We will look into some examples where AsyncTask has been put to use in PSLab Android Application
Delete All Logs:
In the DataLoggerActivity, user has an option to delete all the logs that have been saved on the local storage. Now there might be a lot number of log files that needs to be deleted. Hence it is better to use AsyncTask for these. The code snippet for this is below,
private class DeleteAllTask extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... voids) { Realm realm = Realm.getDefaultInstance(); for (SensorDataBlock data : realm.where(SensorDataBlock.class) .findAll()) { File logDirectory = new File( Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + CSVLogger.CSV_DIRECTORY + File.separator + data.getSensorType() + File.separator + CSVLogger.FILE_NAME_FORMAT.format(data.getBlock()) + ".csv"); logDirectory.delete(); realm.beginTransaction(); realm.where(SensorDataBlock.class) .equalTo("block", data.getBlock()) .findFirst().deleteFromRealm(); realm.commitTransaction(); } realm.close(); return null; } @Override protected void onPostExecute(Void aVoid) { deleteAllProgressBar.setVisibility(View.GONE); if (LocalDataLog.with().getAllSensorBlocks().size() <= 0) { blankView.setVisibility(View.VISIBLE); } } }
As can be seen, we look for all the stored logs, and then delete each file one after another in doInBackground(). Once all the files are deleted, onPostExecute() is called, where we make the progress bar disappear. So, this how AsyncTask is used to implement deleteAllFiles feature.
Capture Task and Fourier Transform Output of Signals in Oscilloscope.
To display the generated signal in the oscilloscope, we call captureTraces() and fetchTraces functions from the ScienceLab class. Now, both these functions communicate with the PSLab Board, request for data, receives the data, manipulates it into the desired format and then display the signal on the Oscilloscope screen. Now clearly we can’t afford to run such a process on the main thread. So we use AsyncTask to handle it.
In the Oscilloscope, there is a feature to see the fourier transform output of the signal generated by the oscilloscope. Now to generate the Fourier Transform Output of the signal, we use the Fast Fourier Transform method. The time complexity of FFT (Fast Fourier Transform) is O(Nlog(N)), where N is the number of samples of the input signal. Now even if FFT is fast, we can risk to run this function on the main Thread. So once again we get help from AsyncTask.
Both of these functionalities are included in same AsyncTask Class called captureTask A snippet for this task can be seen below,
public class CaptureTask extends AsyncTask<String, Void, Void> { private ArrayList<ArrayList<Entry>> entries = new ArrayList<>(); private ArrayList<ArrayList<Entry>> curveFitEntries = new ArrayList<>(); private Integer noOfChannels; private String[] paramsChannels; private String channel; @Override protected Void doInBackground(String... channels) { paramsChannels = channels; noOfChannels = channels.length; try { double[] xData; double[] yData; ArrayList<String[]> yDataString = new ArrayList<>(); String[] xDataString = null; maxAmp = 0; for (int i = 0; i < noOfChannels; i++) { entries.add(new ArrayList<>()); channel = channels[i]; HashMap<String, double[]> data; if (triggerChannel.equals(channel)) scienceLab.configureTrigger(channelIndexMap.get(channel), channel, trigger, null, null); scienceLab.captureTraces(1, samples, timeGap, channel, isTriggerSelected, null); data = scienceLab.fetchTrace(1);
In this part of the capture Task class, we use the captureTrace() and fetchTrace() function to get the signal samples and then store them into the data variable. Below is the part where we use call the fft() for the input signal.
if (isFourierTransformSelected) { Complex[] yComplex = new Complex[yData.length]; for (int j = 0; j < yData.length; j++) { yComplex[j] = Complex.valueOf(yData[j]); } fftOut = fft(yComplex); }
This is a very simple part where we just call the Fast Fourier Transfer function is the user has selected to see the fourier transform output. The implementation of the Fourier function can be seen below,
public Complex[] fft(Complex[] input) { Complex[] x = input; int n = x.length; if (n == 1) return new Complex[]{x[0]}; // if only single element, return as it is if (n % 2 != 0) { x = Arrays.copyOfRange(x, 0, x.length - 1); //No of samples should be even for this function to run, so i case of odd samples we remove the last element. This doesn’t affect the output significantly } Complex[] halfArray = new Complex[n / 2]; for (int k = 0; k < n / 2; k++) { halfArray[k] = x[2 * k]; // Array of input terms at even places } Complex[] q = fft(halfArray); // recursive call for even terms for (int k = 0; k < n / 2; k++) { halfArray[k] = x[2 * k + 1]; // Array of terms at odd places } Complex[] r = fft(halfArray); // recursive call for odd terms Complex[] y = new Complex[n]; // Array of final output for (int k = 0; k < n / 2; k++) { double kth = -2 * k * Math.PI / n; Complex wk = new Complex(Math.cos(kth), Math.sin(kth)); // “kernel” for kth term is the output (based on nth root of unity) if (r[k] == null) { r[k] = new Complex(1); // exception handling } if (q[k] == null) { q[k] = new Complex(1); // exception handling } y[k] = q[k].add(wk.multiply(r[k])); // kth term will be addition of odd and even terms y[k + n / 2] = q[k].subtract(wk.multiply(r[k])); // (k + n/2)th term will be subtraction of odd and even terms } return y; // rsultant array }
This is a classic implementation of Fast Fourier Transform. We divide the samples of input into odd and even placed terms and call the same function recursively until there is only one term left. After that we use nth (n being the number of samples) complex root of unity, we combine the results of odd termed fft() and even termed fft() to get the final output. Since at each iteration we are breaking the input into half it will run for O(logN) time and to merge the odd and even termed output we run a loop in each iteration on the O(N). So the total complexity would be O(NlogN), and since it might take longer to compute the fourier transform for large input we require it to be inside the AsyncTask and not on the main thread.
There are many other functionalities throughout the app, where AsyncTask has been used. In a nutshell, AsyncTask is a very useful method to handle longer tasks off the main thread.
Resources
- https://developer.android.com/reference/android/os/AsyncTask
- PSLab Oscilloscope
- Fast Fourier Transform
Tags: PSLab, Android, GSoC 19, AsyncTask, Threading