7.2: Internet connection

Contents:

Most Android apps engage the user with useful data. That data might be news articles, weather information, contacts, game statistics, and more. Often, data is provided over the network by a web API.

In this chapter you learn about network security and how to make network calls, which involves these general steps:

  • Include permissions in your AndroidManifest.xml file.
  • On a worker thread, make an HTTP client connection that connects to the network and downloads or uploads data.
  • Parse the results, which are usually in JSON format.
  • Check the state of the network and respond accordingly.

Network security

Network transactions are inherently risky, because they involve transmitting data that could be private to the user. People are increasingly aware of these risks, especially when their devices perform network transactions, so it's very important that your app implement best practices for keeping user data secure at all times.

Security best practices for network operations include:

  • Use appropriate protocols for sensitive data. For example for secure web traffic, use the HttpsURLConnection subclass of HttpURLConnection.
  • Use HTTPS instead of HTTP anywhere that HTTPS is supported on the server, because mobile devices frequently connect on insecure networks such as public Wi-Fi hotspots. Consider using SSLSocketClass to implement authenticated, encrypted socket-level communication.
  • Don't use localhost network ports to handle sensitive interprocess communication (IPC), because other apps on the device can access these local ports. Instead, use a mechanism that lets you use authentication, for example, a Service.
  • Don't trust data downloaded from HTTP or other insecure protocols. Validate input that's entered into a WebView and responses to intents that you issue against HTTP.

For more best practices and security tips, take a look at the Security Tips article.

Including permissions in the manifest

Before your app can make network calls, you need to include a permission in your AndroidManifest.xml file. Add the following tag inside the <manifest> tag:

<uses-permission android:name="android.permission.INTERNET" />

When using the network, it's a best practice to monitor the network state of the device so that you don't attempt to make network calls when the network is unavailable. To access the network state of the device, your app needs an additional permission:

<uses-permission 
    android:name="android.permission.ACCESS_NETWORK_STATE" />

Performing network operations on a worker thread

Always perform network operations on a worker thread, separate from the UI thread. For example, in your Java code you could create an AsyncTask (or AsyncTaskLoader) implementation that opens a network connection and queries an API. Your main code checks whether a network connection is active. If so, it runs the AsyncTask in a separate thread, then displays the results in the UI.

Note: If you run network operations on the main thread instead of on a worker thread, your code will throw a NetworkOnMainThreadException and your app will close.

Making an HTTP connection

Most network-connected Android apps use HTTP and HTTPS to send and receive data over the network. For a refresher on HTTP, visit this Learn HTTP tutorial.

Note: If a web server offers HTTPS, you should use it instead of HTTP for improved security.

The HttpURLConnection Android client supports HTTPS, streaming uploads and downloads, configurable timeouts, IPv6, and connection pooling. To use the HttpURLConnection client, build a URI (the request's destination). Then obtain a connection, send the request and any request headers, download and read the response and any response headers, and disconnect.

Building your URI

To open an HTTP connection, you need to build a request URI as a Uri object. A URI object is usually made up of a base URL and a collection of query parameters that specify the resource in question. For example to search for the first five book results for "Pride and Prejudice" in the Google Books API, use the following URI:

https://www.googleapis.com/books/v1/volumes?q=pride+prejudice&maxResults=5&printType=books

To construct a request URI programmatically, use the URI.parse() method with the buildUpon() and appendQueryParameter() methods. The following code builds the complete URI shown above:

// Base URL for the Books API.
final String BOOK_BASE_URL =
   "https://www.googleapis.com/books/v1/volumes?";

// Parameter for the search string
final String QUERY_PARAM = "q"; 
// Parameter to limit search results.
final String MAX_RESULTS = "maxResults"; 
// Parameter to filter by print type
final String PRINT_TYPE = "printType"; 

// Build up the query URI, limiting results to 5 printed books.
Uri builtURI = Uri.parse(BOOK_BASE_URL).buildUpon()
       .appendQueryParameter(QUERY_PARAM, "pride+prejudice")
       .appendQueryParameter(MAX_RESULTS, "5")
       .appendQueryParameter(PRINT_TYPE, "books")
       .build();

To convert the Uri object to a string, use the toString() method:

String myurl = builtURI.toString();

Connecting and downloading data

In the worker thread that performs your network transactions, for example within your override of the doInBackground() method in an AsyncTask, use the HttpURLConnection class to perform an HTTP GET request and download the data your app needs. Here's how:

  1. To obtain a new HttpURLConnection, call URL.openConnection() using the URI that you've built. Cast the result to HttpURLConnection.

    The URI is the primary property of the request, but request headers can also include metadata such as credentials, preferred content types, and session cookies.

  2. Set optional parameters. For a slow connection, you might want a long connection timeout (the time to make the initial connection to the resource) or read timeout (the time to actually read the data).

    To change the request method to something other than GET, use the setRequestMethod() method. If you won't use the network for input, call the setDoInput() method with an argument of false. (The default is true.)

    For more methods you can set, see the HttpURLConnection and URLConnection reference documentation.

  3. Open an input stream using the getInputStream() method, then read the response and convert it into a string. Response headers typically include metadata such as the response body content type and length, modification dates, and session cookies. If the response has no body, getInputStream() returns an empty stream.

  4. Call the disconnect() method to close the connection. Disconnecting releases the resources held by a connection so they can be closed or reused.

These steps are shown in the Request example, below.

Uploading data

If you're uploading (posting) data to a web server, you need to upload a request body, which holds the data to be posted. To do this:

  1. Configure the connection so that output is possible by calling setDoOutput(true). By default, HttpURLConnection uses HTTP GET requests. When setDoOutput is true, HttpURLConnection uses HTTP POST requests instead.
  2. Open an output stream by calling the getOutputStream() method.

For more about posting data to the network, see "Posting Content" in the HttpURLConnection documentation.

Note: All network calls must be performed in a worker thread and not on the UI thread.

Request example

The following example sends a request to the URL built in the Building your URI section, above. The request obtains a new HttpURLConnection, opens an InputStream, reads the response, converts the response into a string, and closes the connection.

private String downloadUrl(String myurl) throws IOException {
    InputStream inputStream = null;
    // Only display the first 500 characters of the retrieved
    // web page content.
    int len = 500;

    try {
        URL url = new URL(myurl);
        HttpURLConnection conn = 
          (HttpURLConnection) url.openConnection();
        conn.setReadTimeout(10000 /* milliseconds */);
        conn.setConnectTimeout(15000 /* milliseconds */);
        // Start the query
        conn.connect();
        int response = conn.getResponseCode();
        Log.d(DEBUG_TAG, "The response is: " + response);
        inputStream = conn.getInputStream();

        // Convert the InputStream into a string
        String contentAsString = 
           convertInputToString(inputStream, len);
        return contentAsString;

    // Close the InputStream and connection
    } finally {
          conn.disconnect();
        if (inputStream != null) {
            inputStream.close();
        }
    }
}

Converting the InputStream to a string

An InputStream is a readable source of bytes. Once you get an InputStream, it's common to decode or convert it into the data type you need. In the example above, the InputStream represents plain text from the web page located at https://www.googleapis.com/books/v1/volumes?q=pride+prejudice&maxResults=5&printType=books

The convertInputToString method defined below converts the InputStream to a string so that the activity can display it in the UI. The method uses an InputStreamReader instance to read bytes and decode them into characters:

// Reads an InputStream and converts it to a String.
public String convertInputToString(InputStream stream, int len) 
         throws IOException, UnsupportedEncodingException {
    Reader reader = null;
    reader = new InputStreamReader(stream, "UTF-8");
    char[] buffer = new char[len];
    reader.read(buffer);
    return new String(buffer);
}
Note: If you expect a long response, wrap your InputStreamReader inside a BufferedReader for more efficient reading of characters, arrays, and lines. For example: ``` reader = new BufferedReader(new InputStreamReader(stream, "UTF-8")); ```

Parsing the results

When you make web API queries, the results are often in JSON format. Below is an example of a JSON response from an HTTP request. It shows the names of three menu items in a popup menu and the methods that are triggered when the menu items are clicked:

{"menu": {
  "id": "file",
  "value": "File",
  "popup": {
    "menuitem": [
      {"value": "New", "onclick": "CreateNewDoc()"},
      {"value": "Open", "onclick": "OpenDoc()"},
      {"value": "Close", "onclick": "CloseDoc()"}
    ]
  }
}

To find the value of an item in the response, use methods from the JSONObject and JSONArray classes. For example, here's how to find the "onclick" value of the third item in the "menuitem" array:

JSONObject data = new JSONObject(responseString);
JSONArray menuItemArray = data.getJSONArray("menuitem");
JSONObject thirdItem = menuItemArray.getJSONObject(2);
String onClick = thirdItem.getString("onclick");

Managing the network state

Making network calls can be expensive and slow, especially if the device has little connectivity. Being aware of the network connection state can prevent your app from attempting to make network calls when the network isn't available.

Sometimes it's also important for your app to know what kind of connectivity the device has: Wi-Fi networks are typically faster than data networks, and data networks are often metered and expensive. To control when certain tasks are performed, monitor the network state and respond appropriately. For example, you may want to wait until the device is connected to Wifi to perform a large file download.

To check the network connection, use the following classes:

  • ConnectivityManager answers queries about the state of network connectivity. It also notifies apps when network connectivity changes.
  • NetworkInfo describes the status of a network interface of a given type (currently either mobile or Wi-Fi).

The following code snippet tests whether Wi-Fi and mobile are connected. In the code:

  • The getSystemService() method gets an instance of ConnectivityManager from the context.
  • The getNetworkInfo() method gets the status of the device's Wi-Fi connection, then its mobile connection. The getNetworkInfo() method returns a NetworkInfo object, which contains information about the given network's connection status (whether that connection is idle, connecting, and so on).
  • The networkInfo.isConnected() method returns true if the given network is connected. If the network is connected, it can be used to establish sockets and pass data.
    private static final String DEBUG_TAG = "NetworkStatusExample";
    ConnectivityManager connMgr = (ConnectivityManager)
               getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = 
               connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
    boolean isWifiConn = networkInfo.isConnected();
    networkInfo = 
               connMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
    boolean isMobileConn = networkInfo.isConnected();
    Log.d(DEBUG_TAG, "Wifi connected: " + isWifiConn);
    Log.d(DEBUG_TAG, "Mobile connected: " + isMobileConn);
    

The related practical is in 7.2: AsyncTask and AsyncTaskLoader.

Learn more

results matching ""

    No results matching ""