Previous  | Next  | Home

Web Data Streams


 

Large amounts of useful (and not so useful) data are available on the Web in the form of structured text such as XML and HTML, or even plain text, and in binary formats such as bitmapped images. An Android device will typically have system and 3rd-party utilities such as browsers and media viewers installed to deal with many of these automatically if you click on an appropriate link in a browser. However, Android has classes that allow us to access and manipulate these data streams directly from within an application, giving the programmer complete control over how the data are retrieved, used, and displayed.


A common mode of data access is through HTTP connections. As discussed in the post Using Android's HTTP Clients by Jesse Wilson on Sept. 29, 2011, in the Android Developer's Blog, Android originally included two general HTTP clients: HttpURLConnection and Apache HTTP Client.
  • The Apache clients have large and flexible APIs, and are stable with few bugs. However, the size of the API makes it difficult to improve within the Android environment without breaking compatibility.

  • HttpURLConnection is a lightweight HTTP client without as many features as the Apache clients, but its focused API has proven more amenable to improvement for mobile computing.
The Apache clients were initially more stable but ongoing Android development focused on HttpURLConnection and the Apache API has since been deprecated. Hence all discussion here will use the HttpURLConnection client. (The Apache API is still accessible by appropriate inclusions in the build files but for current and future Android development their use is strongly discouraged.)

For simplicity, our examples will emphasize HTTP connections but, in a similar way, it is possible to made a secure Web connection to an HTTPS server. HttpsURLConnection is a subclass of HttpURLConnection that defines an HttpURLConnection for the HTTPS protocol. For practical applications you will want to use HTTPS connections where security is important.

In this project we shall give various examples of how to access and manipulate text, structured text, and binary data available on the Web from an Android application using HttpURLConnection.

 

Creating the Project in Android Studio

Following the general procedure in Creating a New Project, either choose Start a new Android Studio project from the Android Studio homepage, or from the Android Studio interface choose File > New > New Project. Fill out the fields in the resulting screens as follows,


Application Name: WebDataStreams
Company Domain:< YourNamespace >
Package Name: <YourNamespace> . webdatastreams
Project Location: <ProjectPath> WebDataStreams
Target Devices: Phone and Tablet; Min SDK API 15
Add an Activity: Empty Activity
Activity Name: MainActivity (check the Generate Layout File box)
Layout Name: activity_main

where you should substitute your namespace for <YourNamespace> (com.lightcone in my case) and <ProjectPath> is the path to the directory where you will store this Android Studio Project (/home/guidry/StudioProjects/ in my case). If you have chosen to use version control for your projects, go ahead and commit this project to version control.

 

Filling out the Code

In this project we will use Intents to launch three new classes from the main class MainActivity.java that demonstrate some basic techniques in dealing with web data streams:

Because all of these classes will involve network accesses that potentially could block the UI thread, we shall also illustrate systematically how to put the network access on a background thread by subclassing AsyncTask, and how to display a task bar indicating to the user that work is being done while waiting for the network response. We begin by filling out all the XML, Java, and Manifest files that will be needed, and then will see them in action and explain how they work.

 

The XML Files

Edit colors.xml so that it reads


<?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#3F51B5</color> <color name="colorPrimaryDark">#303F9F</color> <color name="colorAccent">#FF4081</color> <color name="black">#000000</color> </resources>

Edit styles.xml so that it reads


<resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> <!--A material design style for indeterminate spinner--> <style name="indeterminateMaterialProgress" parent="Theme.AppCompat.Light"> </style> </resources>

Edit res/values/strings.xml to define some strings that we will use:


<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">WebDataStreams</string> <string name="action_settings">Settings</string> <string name="check_label">Check Network Status</string> <string name="GET_label">GET Access Example</string> <string name="bitmap_label">Bitmap Example</string> <string name="main_title">Web Data Streams</string> <string name="netstatus">NETWORK STATUS</string> <string name="bitDescription">Image of Mars</string> </resources>

Edit res/layout/activity_main.xml to lay out a screen of three buttons:


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/LinearLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" > <Button android:id="@+id/check_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/check_label" /> <Button android:id="@+id/GET_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/GET_label" /> <Button android:id="@+id/bitmap_button" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/bitmap_label" /> </LinearLayout>

Create the file res/layout/checknetstatus.xml and edit it to read


<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/RelativeLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <ProgressBar android:id="@+id/network_bar" style="@style/indeterminateMaterialProgress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" /> <TextView android:id="@+id/TextView1" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:textSize="16sp" android:textColor="@color/black" android:text="@string/netstatus" > <requestFocus /> </TextView> </RelativeLayout>

Create the file res/layout/getexample.xml and edit it to read


<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/RelativeLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <ProgressBar android:id="@+id/GET_bar" style="@style/indeterminateMaterialProgress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" /> </RelativeLayout>

Create the file res/layout/bitmapexample.xml and edit it to read


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/RelativeLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:background="@color/black"> <ProgressBar android:id="@+id/progress_bar" style="@style/indeterminateMaterialProgress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" /> <ImageView android:id="@+id/image" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_below="@+id/progress_bar" android:layout_centerHorizontal="true" android:layout_centerInParent="true" android:layout_gravity="center" android:contentDescription="@string/bitDescription" android:paddingTop="20dp" /> </RelativeLayout>

Now we shall create the Java files that will be required.

 

The Java Class Files

Edit the class file MainActivity.java to give


package <YourNamespace>.webdatastreams; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.content.Intent; import android.view.View; public class MainActivity extends AppCompatActivity implements android.view.View.OnClickListener { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Click listeners for all buttons View checkButton = findViewById(R.id.check_button); checkButton.setOnClickListener(this); View getButton = findViewById(R.id.GET_button); getButton.setOnClickListener(this); View bitmapButton = findViewById(R.id.bitmap_button); bitmapButton.setOnClickListener(this); } // Process the button clicks @Override public void onClick(View v) { switch (v.getId()) { case R.id.check_button: Intent i = new Intent(this, CheckNetStatus.class); startActivity(i); break; case R.id.GET_button: Intent j = new Intent(this, GETexample.class); startActivity(j); break; case R.id.bitmap_button: Intent m = new Intent(this, BitmapExample.class); startActivity(m); break; } } }

Create the class file CheckNetStatus.java and edit it to read


package <YourNamespace>.webdatastreams; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.util.Enumeration; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.AsyncTask; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView; public class CheckNetStatus extends AppCompatActivity { private static final String TAG = "WEBSTREAM"; private static final String URL = ""; private static TextView tv; public Bundle netstat; private ProgressBar progressBar; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.checknetstatus); tv = (TextView) findViewById(R.id.TextView1); // We will do the network accesses for the check on a background thread by subclassing // AsyncTask. // Create a progress spinner progressBar = (ProgressBar) findViewById(R.id.network_bar); // Execute on a background thread new BackgroundLoad().execute(URL); } // Method to return network and server connections status public Bundle networkStatus() { Bundle netStatus = new Bundle(); Boolean wifiConnected = null; Boolean phoneConnected = null; String ipNumber; NetworkInfo netInfo; // Get a connectivity manager instance ConnectivityManager conman = (ConnectivityManager) getSystemService( Context.CONNECTIVITY_SERVICE); netInfo = conman.getActiveNetworkInfo(); // Guard against netInfo=null (no network detected) if(netInfo != null) { // Check wifi status if (netInfo.getType() == ConnectivityManager.TYPE_WIFI) { wifiConnected = netInfo.isConnected(); Log.i(TAG, "Network Type: " + netInfo.getTypeName()); } // Check telephony status if (netInfo.getType() == ConnectivityManager.TYPE_MOBILE) { phoneConnected = netInfo.isConnected(); } } ipNumber = getLocalIpAddress(); // Add network status variable values to the Bundle netStatus if (wifiConnected != null) netStatus.putBoolean("wifiConnected", wifiConnected); if (phoneConnected != null) netStatus.putBoolean("phoneConnected", phoneConnected); if (ipNumber != null) netStatus.putString("ipNumber", ipNumber); return netStatus; } // Method to find local ip address of the device on wifi or mobile. // See http://chandan-tech.blogspot.com/2010/12/finding-ip-address-of-your-android.html // and http://stackoverflow.com/questions/32141785/android-api-23-inetaddressutils-replacement public static String getLocalIpAddress() { try { for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) { NetworkInterface intf = (NetworkInterface) en.nextElement(); for (Enumeration enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) { InetAddress inetAddress = (InetAddress) enumIpAddr.nextElement(); if (!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) { return inetAddress.getHostAddress(); } } } } catch (Exception ex) { } return null; } /** * Use AsyncTask to perform the network checks on a background thread. The three * argument types inside < > are (1) a type for the input parameters (String in this case), * (2) a type for published progress during the background task (String in this case, * since we will illustrate by publishing a short string), and (3) a type * for the object returned from the background task (in this case it is a Bundle). If one of * the argument types is not needed, specify it by Void. For example, if our task used one or * more String input parameters but did not publish progress and did not return anything * the pattern would be <String, Void, Void>, where String implies an array of input strings. * It is convenient to implement AsyncTask as an inner class (as we do here) because this * gives it access to the fields and methods of the enclosing class. */ private class BackgroundLoad extends AsyncTask<String, String, Bundle> { // Executes the task on a background thread. Note: since this is a background // thread, we are strictly forbidden to touch any views on the main UI thread from this // method. @Override protected Bundle doInBackground(String... url) { Log.i(TAG, "---doInBackground---"); // The notation String... url means that the input parameters are an array of // strings. However, we are only passing one argument in the // new BackgroundLoad().execute(URL) statement above, so we use only // url[0] in the following. // Publish progress. This will cause onProgressUpdate to run on the main UI thread, // with the argument of publishProgress passed to it. publishProgress("\n\nStarting background thread\n"); return networkStatus(); } // Executes on the main UI thread before the thread run by doInBackground. Since // it executes on the main UI thread we are free to interact with views on the main // thread from this method. protected void onPreExecute() { Log.i(TAG, "\n---onPreExecute---"); } // Override onProgressUpdate to publish progress while the background thread is running. // This runs on the main UI thread after the publishProgress method is invoked in // doInBackground. Here we do something fairly trivial by sending to the screen a // message indicating that we have started processing on the background // thread. A more common real-life application would be to use say // onProgressUpdate(Integer ... prog) to update a progress bar with the fraction of // the job completed. Note that since this method runs on the main UI thread it can // interact directly with the views there. protected void onProgressUpdate(String... progress) { Log.i(TAG, "---onProgressUpdate---"); tv.append(progress[0]); } // Executed after the thread run by doInBackground has returned. The Bundle data // passed are the Bundle returned by doInBackground. This method executes on // the main UI thread so we are free to interact with views on the main thread from // here. @Override protected void onPostExecute(Bundle data) { // Stop the progress dialog progressBar.setVisibility(View.GONE); Log.i(TAG, "---onPostExecute---"); // Process and display results in the Bundle returned from the background thread, // extracting information from the Bundle as a string displayed to screen. netstat = data; String net1 = " phone connected = " + netstat.getBoolean("phoneConnected"); String net2 = " wifi connected = " + netstat.getBoolean("wifiConnected"); String net3 = " IP = " + netstat.getString("ipNumber"); String netString = net1 + "\n" + net2 + "\n" + net3; Log.i(TAG, netString); tv.append("\n" + netString); } } }

Create the class file GETexample.java and edit it to read


package <YourNamespace>.webdatastreams; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import android.support.v7.app.AppCompatActivity; import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.View; import android.webkit.WebView; import android.widget.ProgressBar; public class GETexample extends AppCompatActivity { private static final String TAG = "WEBSTREAM"; // Range for random integer generator private static final int num = 10; // Number of random integers private static final int lower = 0; // Lower limit of interval private static final int upper = 100; // Upper limit of interval // This returns num random integers in the interval lower to upper String url = "https://www.random.org/integers/?num=" + num + "&min=" + lower + "&max=" + upper + "&col=1&base=10&format=plain&rnd=new"; private String getURL = url; private String searchString = ""; ProgressBar progressBar; Context context = this; // Store this context for webview invoked later from inner class @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.getexample); // Execute the GET request on a background thread progressBar = (ProgressBar) findViewById(R.id.GET_bar); try { new BackgroundLoad().execute(getURL, URLEncoder.encode(searchString, "UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } // Example of using HttpURLConnection for a GET request. The string getURL // is assumed to give the full url with the appended data payload (with the data // entries URLEncoded where necessary). For example, // https://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=butterfly // The result of the web request is returned as a string by this method. public String getRequest(String getURL) { URL url = null; String result = null; try { url = new URL(getURL); } catch (MalformedURLException e) { e.printStackTrace(); } HttpURLConnection urlConnection = null; try { urlConnection = (HttpURLConnection) url.openConnection(); } catch (IOException e) { e.printStackTrace(); } try { InputStream in = null; try { in = new BufferedInputStream(urlConnection.getInputStream()); } catch (IOException e) { e.printStackTrace(); } result = readStream(in); } finally { // Disconnecting releases resources held by connection so they can be closed or reused. if (urlConnection != null) { urlConnection.disconnect(); } } return result; } // Reader for the GET response stream private String readStream(InputStream is) { // Begin reading the GET input stream line by line Log.i(TAG, "\n\nBegin reading GET input stream"); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr, 8192); // 2nd arg is buffer size String total = ""; try { String test; while (true) { test = br.readLine(); if (test == null) break; // readLine() returns null if no more lines Log.i(TAG, test); total += test + " "; } isr.close(); is.close(); br.close(); } catch (IOException e) { e.printStackTrace(); } Log.i(TAG, "\n\nThat is all"); Log.i(TAG, "\n test=" + total); return total; } // Use AsyncTask to perform the web download on a background thread. The three // argument types inside the < > are (1) a type for the input parameters (Strings in this case), // (2) a type for any published progress during the background task (Void in this case, because // we aren't going to publish progress since the task should be very short), and (3) a type // for the object returned from the background task (in this case it is type String). private class BackgroundLoad extends AsyncTask<String, Void, String> { // Executes the task on a background thread @Override protected String doInBackground(String... params) { // The notation String... params means that the input parameters are an array of // strings. In new BackgroundLoad().execute(getURL, searchString) above we are // passing two arguments, so params[0] will correspond to getURL and and params[1] // will correspond to the search string. String GETResponseString = null; String address = null; try { address = params[0] + URLEncoder.encode(params[1], "UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } Log.i(TAG, "Search =" + params[1]); Log.i(TAG, "Address = " + address); GETResponseString = getRequest(address); return GETResponseString; } // Executes before the thread run by doInBackground protected void onPreExecute() { } // Executed after the thread run by doInBackground has returned. The variable s // passed is the string value returned by doInBackground. @Override protected void onPostExecute(String s) { // Stop the progress dialog progressBar.setVisibility(View.GONE); Log.i(TAG, "Thread finished. Displaying content as webview."); // Display the response in a webview WebView wv = new WebView(context); setContentView(wv); // Display the result in an html format s = "<p>" + num + " integers chosen randomly between 0 and 100:</p>" + "<center><h3><pre>" + s + "</pre></h3></center>"; s += "Source: <em>https://www.random.org</em>"; wv.loadData(s, "text/html", "utf-8"); } } }

Create the class file BitmapExample.java and edit it to read


package <YourNamespace>.webdatastreams; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.ImageView; import android.widget.ProgressBar; public class BitmapExample extends Activity { public static final String TAG = "WEBSTREAM"; ImageView imageview; // Alternative source: "http://heritage.stsci.edu/2001/24/mars/0124b.jpg"; // Mars private static final String URL = "http://heritage.stsci.edu/1999/01/images/9901b.jpg"; // Ring Nebula private ProgressBar progressBar; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.bitmapexample); progressBar = (ProgressBar) findViewById(R.id.progress_bar); // Execute on a background thread using AsyncTask new BackgroundLoad().execute(URL); } // Method to load bitmap from web private Bitmap loadBitmap(String url) { Bitmap image = null; InputStream inStream = null; try { inStream = openHttpConnection(url); image = BitmapFactory.decodeStream(inStream); inStream.close(); } catch (IOException e) { e.printStackTrace(); } return image; } // Method adapted from example in "Beginning Android Application Development", W-M. Lee, // pp. 284-287. private InputStream openHttpConnection(String urlString) throws IOException { int responseCode = -1; InputStream inStream = null; URL url = new URL(urlString); URLConnection uconn = url.openConnection(); if (!(uconn instanceof HttpURLConnection)) { throw new IOException("Not a valid HTTP connection"); } try { HttpURLConnection httpConn = (HttpURLConnection) uconn; httpConn.setRequestMethod("GET"); httpConn.connect(); responseCode = httpConn.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { inStream = httpConn.getInputStream(); } } catch (Exception e) { throw new IOException("Connection Error"); } return inStream; } /** * Subclass AsyncTask to download image on a background thread. The three argument types * <Params, Progress, Result> are (1) a type for the input parameters (String in this case), (2) a * type for any published progress during the background task (String in this case), and (3) a type * for the objects returned from the background task (if any; in this case it is a Bitmap). Each of * these three arguments stands for an array of the corresponding type, so it is possible * to pass multiple arguments of each kind. If one of the argument types is not needed, specify * it by Void. For example, if our task used one or more String input parameters but did not * publish progress and did not return anything the pattern would be <String, Void, Void>, where * String implies an array of input strings. It is convenient to implement AsyncTask as an inner * class (as we do here) because this gives it access to the fields and methods of the * enclosing class. Must be launched from the UI thread and may only be invoked once. Use * new BackgroundLoad().execute(arg); * to launch it, where arg is the String argument passed to AsyncTask. This constructor returns * itself (this), so you could also retain a reference to the AsyncTask object by invoking instead * AsyncTask<String, String, Bitmap> at = new BackgroundLoad().execute(arg); * for the present example. */ private class BackgroundLoad extends AsyncTask<String, String, Bitmap> { // Executes a task on a background thread that is managed automatically by the system. // Note: since this is a background thread, we are strictly forbidden to touch any views on // the main UI thread directly from this method. @Override protected Bitmap doInBackground(String... url) { // The notation String... url means that the input parameters are an array of // strings (in principle), but we are only passing one argument in the // new BackgroundLoad().execute(URL) statement above, so we use only // url[0] in the following. // Optional: publish progress. This will cause onProgressUpdate to run on the main UI thread, // with the argument of publishProgress passed to it. publishProgress("Starting image load on background thread\nURL=" + url[0]); // Retrieve the bitmap from the specified url Bitmap bitmap = loadBitmap(url[0]); return bitmap; } // Executes on the main UI thread before the thread run by doInBackground does. Since // it executes on the main UI thread we are free to interact with views on the main // thread from this method. This is the place to do any setup required before the task on // the background thread runs. In this example, we use it to launch a progress dialog // that will indicate to the user that work is being done while the background task is running. @Override protected void onPreExecute() { } // Override onProgressUpdate to publish progress while the background thread is still running. // This runs on main UI thread after publishProgress method invoked on background thread in // doInBackground. Here we do something fairly trivial by sending to the logcat stream a // message indicating that we have started processing on the background thread and // the url it is using. A more common real-life application of this method would be to use say // onProgressUpdate(Integer ... prog) to update a progress bar with the fraction of // the job completed. (Then you would have to change the AsyncTask parameter pattern for this // example to <String, Integer, Bitmap> and change the publishProgress method in // doInBackground accordingly.) Note that this method runs on the main UI thread so it is // permitted to interact directly with the views there. @Override protected void onProgressUpdate(String... progress) { Log.i(TAG, "---onProgressUpdate---"); Log.i(TAG, progress[0]); } // Executed after the thread run by doInBackground has returned. The variable bitmap // passed is the bitmap returned by doInBackground. This method executes on // the main UI thread, so we are free to interact with views on the main thread from // here. @Override protected void onPostExecute(Bitmap bitmap) { // Stop the progress dialog progressBar.setVisibility(View.GONE); Log.i(TAG, "Thread finished. Displaying image."); // Display the retrieved image as an ImageView imageview = (ImageView) findViewById(R.id.image); imageview.setImageBitmap(bitmap); } } }

The Manifest File

Finally, edit the AndroidManifest.xml file to give


<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.lightcone.webdatastreams"> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".CheckNetStatus" android:label="CheckNetStatus"> </activity> <activity android:name=".GETexample" android:label="GETexample"> </activity> <activity android:name=".BitmapExample" android:label="BitmapExample"> </activity> </application> </manifest>

where added lines are indicated in red.

 

Trying it Out

Running the application for an internet-connected emulator or device should give the screen in the following image.



Clicking on the first and second buttons should display screens similar to the following two images,



which illustrate querying the network status and retrieving information using the GET method, respectively. Clicking on the third button should display a screen similar to the following one



which illustrates retrieving a bitmap over the network. We now describe how the classes we have defined function to produce this output.

 

How it Works

The entry screen is defined by the class MainActivity and the associated layout file activity_main.xml. It just displays three buttons and uses Intents to cause the action of each button to launch an activity defined by a new class, in a manner that should be familiar from many previous examples. Let us now sumarize the functionality of these classes

 

The Class CheckNetStatus

The class CheckNetStatus queries the network for status. The onCreate method defines a TextView that will display the results on the screen, and then launches an execution thread using the execute() method of AsyncTask class where the work will be done.

The method networkStatus returns a Bundle of name-value pairs characterizing the status of the network for the device. The key object in this method is an instance of the NetworkInfo class that describes the status of a network interface. Within networkStatus

  1. The ConnectivityManager class answers queries about the state of network connectivity. An instance conman is obtained by invoking the getSystemServices(String) method of Context with the class constant Context.CONNECTIVITY_SERVICE as argument.

  2. Then the getActiveNetworkInfo() method of ConnectivityManager is invoked to create an instance netInfo of NetworkInfo. (The caller must hold the permission ACCESS_NETWORK_STATE to use this method, which is why we have requested it in the Manifest file.)

  3. The NetworkInfo instance netInfo holds basic information about the connectivity for WiFi and mobile connections, respectively, and the NetworkInfo method getType() is used to extract it.

  4. The method getLocalIpAddress() implements a set of Java networking tools that are then used to extract the current IP address of the device (in a manner that should work whether the device is on a mobile or WiFi network).

  5. Finally, the various "putType(name, value)" methods of Bundle are invoked to write the network information into name-value pairs that will be returned with the Bundle object netStatus.

An object of the ProgressBar class is used to create progress bars (in the present case, a spinner indicating indeterminate progress displayed while pages are loading) in a manner described in earlier projects such as Progress Bar Example. This progress spinner will be displayed while network operations are being carried out on the background thread defined by subclassing AsyncTask.

Notice that the layout XML for pages launched by clicking on the three buttons employs a ProgressBar in a RelativeLayout along with the other widgets to be displayed on that page. The relative layout makes it easy to have the spinning progress indicator display on the page and then have it hidden by


progressBar.setVisibility(View.GONE);

when the actual content to be displayed appears in its place.

 

The Inner Class BackgroundLoad

All of the classes defined here execute network operations on a thread separate from the main UI thread by subclassing AsyncTask <params, progress, result>. In each case we shall name the class BackgroundLoad, and define it as an inner class (within another class).


A nested class in Java is a class defined completely within another class. If the nested class is declared static it is called a static nested class. If the nested class is not declared static, it is termed an inner class. An inner class has access to all members of the enclosing class, even if they are declared private.

This general use of AsyncTask allows painless threading that will be employed in several network-access cases for this project.

 

The Class GETexample

The class GETexample illustrates the use of the GET method to access information from a Web server. Although the GET method is used far less than the POST method for communicating query parameters to a server on the modern Web, it may still be encountered for cases where only a small amount of data need be uploaded. In the GET method the query is embedded in the URL address. In the present example, we shall access the Web server with the URL


String url = "https://www.random.org/integers/?num=" + num + "&min=" + lower + "&max=" + upper + "&col=1&base=10&format=plain&rnd=new";

which encodes three name-value pairs to be passed to the server as part of the url, num, lower, and upper. The GET network access is executed on the background thread implemented by subclassing AsyncTask, as described above, by overriding the doInBackground method. The thread is executed by issuing


new BackgroundLoad().execute(getURL, URLEncoder.encode(searchString,"UTF-8"));

from the onCreate() method.

In the method doInBackground the relevant steps are to use the url with data embedded to retrieve a response string from the server. This response is returned in the variable GETResponseString upon completion of the background thread and thus is available to the AsyncTask method onPostExecute(String s) as the argument s.


It is not generally a good idea to try to manipulate large strings as single entities in Java, particularly on limited devices. Short responses from a server can be handled as a single string but larger responses are dealt with most efficiently by processing the returned data as a stream.

In our example here we have built strings with the standard string concatenation operator +. This post may be consulted for a discussion of the relative merits of string concatenation using
  1. the + operator,

  2. the Java class StringBuffer,

  3. the Java class StringBuilder.
For concatenation of many strings, StringBuilder will probably be somewhat faster than StringBuffer, and both will be much faster than using the + operator. Strictly, StringBuilder is not threadsafe and StringBuffer is, so the most efficient concatenation of many strings avoids the + operator and uses StringBuilder if thread synchronization is not an issue and StringBuffer if it is. However, StringBuilder has largely replaced StringBuffer in modern applications because the synchronization afforded by StringBuffer is often not useful. For just a few string concatenations, the + operator is simple and adequate.

The data returned from the server in this case are a sequence of num random integers between the limits lower and upper. They are returned in an ascii string, which we use to construct a short html page and display as a WebView.

 

The Class BitmapExample

The class BitmapExample illustrates retrieving a binary file (in this case a bitmapped image) from a Web server. As for the other examples, we shall make the Web access and download of the image on a background thread created by subclassing AsyncTask.


The complete project for the application described above is archived on GitHub at the link WebDataStreams. Instructions for installing it in Android Studio may be found in Packages for All Projects.

Last modified: July 25, 2016


Previous  | Next  | Home