In order to simplify the operation, Android Studio has a built in class that allows us to perform these tasks in the background, without interrupting the user experience.
I'm talking about the AsyncTask class.
This class is the prefect tool to use whenever we need to perform background tasks. It is a simpler and more efficient way to run code in a separate thread.
Let's start by selecting an image from the web, any is fine. By typing "universe" in google, this is one of the pictures that came up:
And this is its address: https://i.ytimg.com/vi/uSJSPRQdLd8/maxresdefault.jpg.
In our project, first thing we need to do is to add internet permission in the manifest:
1 | <uses-permission android:name="android.permission.INTERNET"></uses-permission> |
This is necessary of course as we are going to need to access the web to connect to the image link.
Next we create our layout. Very simple, we will only need an ImageView and a ProgressBar, which will be used to show the download progress to the user.
This is the XML file of our MainActivity:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/imageView" android:layout_centerVertical="true" android:layout_centerHorizontal="true" /> <ProgressBar style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="20dp" android:id="@+id/progressBar" android:layout_centerVertical="true" android:layout_centerHorizontal="true" /> </RelativeLayout> |
We can now check the DownloadImage class, which is a child of AsyncTask.
This is the class responsible of downloading the content.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | public class DownloadImage extends AsyncTask<String, Integer, Bitmap> { Activity act; ProgressBar bar; ImageView image; public DownloadImage(Activity act) { this.act = act; image = (ImageView) act.findViewById(R.id.imageView); bar = (ProgressBar) act.findViewById(R.id.progressBar); } @Override protected void onPostExecute(Bitmap bitmap) { bar.setVisibility(View.GONE); image.setImageBitmap(bitmap); } @Override protected void onPreExecute() { Toast.makeText(act,"Downloading...",Toast.LENGTH_SHORT).show(); bar.setVisibility(View.VISIBLE); bar.setMax(100); } @Override protected void onProgressUpdate(Integer... values) { bar.setProgress(values[0]); } @Override protected Bitmap doInBackground(String... params) { Bitmap bmp = null; int count = 0; ByteArrayOutputStream baos = new ByteArrayOutputStream(8192); try { URL myUrl = new URL(params[0]); HttpURLConnection con = (HttpURLConnection) myUrl.openConnection(); con.setRequestProperty("Accept-Encoding", "identity"); //Allow to know file size through getContentLength con.connect(); byte[] b = new byte[1024]; InputStream in = con.getInputStream(); int total = con.getContentLength(); while((count = in.read(b))!= -1) { baos.write(b, 0, count); publishProgress(100*baos.toByteArray().length/total); } bmp = BitmapFactory.decodeByteArray(baos.toByteArray(),0,baos.toByteArray().length); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return bmp; } } |
First thing we notice is that the AsyncTask class uses generics. The three parameters assigned can be read as such: AsyncTask<Parameters,Progress,Result>.
- Parameters is what is sent to the task to work with. In my case, it's a String, as I will pass the link to the image, in String format.
- Progress is the type of variable used as parameters in the onProgressUpdate method. This value (or values) are usually passed to a PrograssBar or a ProgressDialog.
- Result: is the returned type of the task. This type will be the parameter of the method onPostExecute, as we will soon see.
When we extend this class we must implement at least one method: doInBackground(...).
Even though this appears to be a method like many others, it actually runs on a separate thread. Whatever code is between its brackets, it will not interfere with the UI thread. As you can see on line 38, this method accepts a parameter of type String, as I described previously when discussing generics. Also, we can see that this method is of type Bitmap as this is what we are going to return as our result.
Before exploring the funtion in details, let's have a quick look at the other two overridden methods.
The first one is onPreExecute(). This function is invoked immediately before the doInBackground and it runs on the main thread. Therefore, here we can manipulate UI elements. In my app, I simply display a Toast to let the user know that downloading has started.
The other method is onPostExecute(...). This one, not surprisingly, run immediately after the background thread has returned. The parameters passed in the method is whatever is returned by the doInBackground(...) method and, at this point, we can process the result. this method also runs on the UI thread. In our example, I made the progress bar disappear (line 26) and I set the bitmap to our ImageView.
Lastly, the onPublishProgress method, which simply takes the values passed in and applies them to our progress bar. This parameter is calculated in the doInBackground method and passed in invoking publishProgress(...).
Obviously, the doInBackground method is where the downloading occurs. To perform such a task I used a ByteArrayOutputStream, which will hold all the bytes of the image. In order to connect to the link, we first grab the String which is passed in as a parameter and create a URL with it (line 48).
.
Before exploring the funtion in details, let's have a quick look at the other two overridden methods.
The first one is onPreExecute(). This function is invoked immediately before the doInBackground and it runs on the main thread. Therefore, here we can manipulate UI elements. In my app, I simply display a Toast to let the user know that downloading has started.
The other method is onPostExecute(...). This one, not surprisingly, run immediately after the background thread has returned. The parameters passed in the method is whatever is returned by the doInBackground(...) method and, at this point, we can process the result. this method also runs on the UI thread. In our example, I made the progress bar disappear (line 26) and I set the bitmap to our ImageView.
Lastly, the onPublishProgress method, which simply takes the values passed in and applies them to our progress bar. This parameter is calculated in the doInBackground method and passed in invoking publishProgress(...).
Obviously, the doInBackground method is where the downloading occurs. To perform such a task I used a ByteArrayOutputStream, which will hold all the bytes of the image. In order to connect to the link, we first grab the String which is passed in as a parameter and create a URL with it (line 48).
.
The HttpUrlConnection is used to create the actual connection (line 49). The next line is used to allow us to know the size of the content we are linking to, in our case the image. This is going to be used soon to calculate the download progress.
After launching the connection, we create a byte array, which acts as our buffer and we get the InputStream. Also, we have a new variable, called total, which represents the content size.
Between line 58 and 63 there's a while loop which reads the content from the InputStream. We use the read(buffer) method of the input stream to get the first set of bytes which are converted into a int (count) and we check whether the value is -1: if that's the case, we have reached the end of the content. If not, we can simply write those bytes into our ByteArrayOutputStream.
Finally, on line 62, we invoke the method publishProgress(...) which will send the values passed in (an int in our case) to the onPublishProgress method we overrode earlier. The value passed in is nothing but a percentage of the bytes read to the total size of the content.
Once the loop has completed the task, we create a bitmap and return it.
Last thing we need to do start the whole process from the MainActivity class. To do so, we simply need 2 lines of code:
Place these lines in your OnCreate method of the activity and that is all. The execute method is used to invoke doInBackground. DO NOT call the method directly otherwise it will not run in a saparate thread as described before.
Below, a gif showing the result:
After launching the connection, we create a byte array, which acts as our buffer and we get the InputStream. Also, we have a new variable, called total, which represents the content size.
Between line 58 and 63 there's a while loop which reads the content from the InputStream. We use the read(buffer) method of the input stream to get the first set of bytes which are converted into a int (count) and we check whether the value is -1: if that's the case, we have reached the end of the content. If not, we can simply write those bytes into our ByteArrayOutputStream.
Finally, on line 62, we invoke the method publishProgress(...) which will send the values passed in (an int in our case) to the onPublishProgress method we overrode earlier. The value passed in is nothing but a percentage of the bytes read to the total size of the content.
Once the loop has completed the task, we create a bitmap and return it.
Last thing we need to do start the whole process from the MainActivity class. To do so, we simply need 2 lines of code:
1 2 | DownloadImage di = new DownloadImage(this); di.execute("https://i.ytimg.com/vi/uSJSPRQdLd8/maxresdefault.jpg"); |
Place these lines in your OnCreate method of the activity and that is all. The execute method is used to invoke doInBackground. DO NOT call the method directly otherwise it will not run in a saparate thread as described before.
Below, a gif showing the result:
Conclusion
Accessing web content is one of the most powerful things we can do in Android. Using AsyncTask greatly simplifies the process also allowing us to cancel ongoing tasks. When using this class, some important things to remember:
- doInBackground must be implemented
- doInBackground runs on a separate thread, no UI elements should be edited here
- onPostExecute and onPreExecute run on the main thread
- All the actions that should be performed one the task is done should be placed inside the onPostExecute method: placing them somewhere else in the activity class could interfere with the main thread giving you unwanted results.
No comments:
Post a Comment