diff options
| author | Scott Main <smain@google.com> | 2012-06-21 17:14:39 -0700 |
|---|---|---|
| committer | Scott Main <smain@google.com> | 2012-06-21 21:27:30 -0700 |
| commit | 50e990c64fa23ce94efa76b9e72df7f8ec3cee6a (patch) | |
| tree | 52605cd25e01763596477956963fabcd087054b0 /docs/html/training/basics/network-ops | |
| parent | a2860267cad115659018d636bf9203a644c680a7 (diff) | |
| download | frameworks_base-50e990c64fa23ce94efa76b9e72df7f8ec3cee6a.zip frameworks_base-50e990c64fa23ce94efa76b9e72df7f8ec3cee6a.tar.gz frameworks_base-50e990c64fa23ce94efa76b9e72df7f8ec3cee6a.tar.bz2 | |
Massive clobber of all HTML files in developer docs for new site design
Change-Id: Idc55a0b368c1d2c1e7d4999601b739dd57f08eb3
Diffstat (limited to 'docs/html/training/basics/network-ops')
| -rw-r--r-- | docs/html/training/basics/network-ops/connecting.jd | 284 | ||||
| -rw-r--r-- | docs/html/training/basics/network-ops/index.jd | 73 | ||||
| -rw-r--r-- | docs/html/training/basics/network-ops/managing.jd | 445 | ||||
| -rw-r--r-- | docs/html/training/basics/network-ops/xml.jd | 555 |
4 files changed, 1357 insertions, 0 deletions
diff --git a/docs/html/training/basics/network-ops/connecting.jd b/docs/html/training/basics/network-ops/connecting.jd new file mode 100644 index 0000000..f70cf58 --- /dev/null +++ b/docs/html/training/basics/network-ops/connecting.jd @@ -0,0 +1,284 @@ +page.title=Connecting to the Network +parent.title=Performing Network Operations +parent.link=index.html + +trainingnavtop=true +next.title=Managing Network Usage +next.link=managing.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + + + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#http-client">Choose an HTTP Client</a></li> + <li><a href="#connection">Check the Network Connection</a></li> + <li><a href="#AsyncTask">Perform Network Operations on a Separate Thread</a></li> + <li><a href="#download">Connect and Download Data</a></li> + <li><a href="#stream">Convert the InputStream to a String</a></li> + +</ol> + +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}training/monitoring-device-state/index.html">Optimizing Battery Life</a></li> + <li><a href="{@docRoot}training/efficient-downloads/index.html">Transferring Data Without Draining the Battery</a></li> + <li><a href="{@docRoot}guide/webapps/index.html">Web Apps Overview</a></li> + <li><a href="{@docRoot}guide/components/fundamentals.html">Application Fundamentals</a></li> +</ul> + +</div> +</div> + +<p>This lesson shows you how to implement a simple application that connects to +the network. It explains some of the best practices you should follow in +creating even the simplest network-connected app.</p> + +<p>Note that to perform the network operations described in this lesson, your +application manifest must include the following permissions:</p> + +<pre><uses-permission android:name="android.permission.INTERNET" /> +<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /></pre> + + + +<h2 id="http-client">Choose an HTTP Client</h2> + +<p>Most network-connected Android apps use HTTP to send and receive data. +Android includes two HTTP clients: {@link java.net.HttpURLConnection} and Apache + {@link org.apache.http.client.HttpClient}. Both support HTTPS, streaming uploads and downloads, configurable +timeouts, IPv6, and connection pooling. We recommend using {@link +java.net.HttpURLConnection} for applications targeted at Gingerbread and higher. For +more discussion of this topic, see the blog post <a +href="http://android-developers.blogspot.com/2011/09/androids-http-clients.html" +>Android's HTTP Clients</a>.</p> + +<h2 id="connection">Check the Network Connection</h2> + +<p>Before your app attempts to connect to the network, it should check to see whether a +network connection is available using +{@link android.net.ConnectivityManager#getActiveNetworkInfo getActiveNetworkInfo()} +and {@link android.net.NetworkInfo#isConnected isConnected()}. +Remember, the device may be out of range of a +network, or the user may have disabled both Wi-Fi and mobile data access. +For more discussion of this topic, see the lesson <a +href="{@docRoot}training/network-ops/managing.html">Managing Network +Usage</a>.</p> + +<pre> +public void myClickHandler(View view) { + ... + ConnectivityManager connMgr = (ConnectivityManager) + getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); + if (networkInfo != null && networkInfo.isConnected()) { + // fetch data + } else { + // display error + } + ... +}</pre> + +<h2 id="AsyncTask">Perform Network Operations on a Separate Thread</h2> + +<p>Network operations can involve unpredictable delays. To prevent this from +causing a poor user experience, always perform network operations on a separate +thread from the UI. The {@link android.os.AsyncTask} class provides one of the +simplest ways to fire off a new task from the UI thread. For more discussion of +this topic, see the blog post <a +href="http://android-developers.blogspot.com/2010/07/multithreading-for- +performance.html">Multithreading For Performance</a>.</p> + + +<p>In the following snippet, the <code>myClickHandler()</code> method invokes <code>new +DownloadWebpageTask().execute(stringUrl)</code>. The +<code>DownloadWebpageTask</code> class is a subclass of {@link +android.os.AsyncTask}. <code>DownloadWebpageTask</code> implements the following +{@link android.os.AsyncTask} methods:</p> + + <ul> + + <li>{@link android.os.AsyncTask#doInBackground doInBackground()} executes +the method <code>downloadUrl()</code>. It passes the web page URL as a +parameter. The method <code>downloadUrl()</code> fetches and processes the web +page content. When it finishes, it passes back a result string.</li> + + <li>{@link android.os.AsyncTask#onPostExecute onPostExecute()} takes the +returned string and displays it in the UI.</li> + + + </ul> + +<pre> +public class HttpExampleActivity extends Activity { + private static final String DEBUG_TAG = "HttpExample"; + private EditText urlText; + private TextView textView; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + urlText = (EditText) findViewById(R.id.myUrl); + textView = (TextView) findViewById(R.id.myText); + } + + // When user clicks button, calls AsyncTask. + // Before attempting to fetch the URL, makes sure that there is a network connection. + public void myClickHandler(View view) { + // Gets the URL from the UI's text field. + String stringUrl = urlText.getText().toString(); + ConnectivityManager connMgr = (ConnectivityManager) + getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); + if (networkInfo != null && networkInfo.isConnected()) { + new DownloadWebpageText().execute(stringUrl); + } else { + textView.setText("No network connection available."); + } + } + + // Uses AsyncTask to create a task away from the main UI thread. This task takes a + // URL string and uses it to create an HttpUrlConnection. Once the connection + // has been established, the AsyncTask downloads the contents of the webpage as + // an InputStream. Finally, the InputStream is converted into a string, which is + // displayed in the UI by the AsyncTask's onPostExecute method. + private class DownloadWebpageText extends AsyncTask<String, Void, String> { + @Override + protected String doInBackground(String... urls) { + + // params comes from the execute() call: params[0] is the url. + try { + return downloadUrl(urls[0]); + } catch (IOException e) { + return "Unable to retrieve web page. URL may be invalid."; + } + } + // onPostExecute displays the results of the AsyncTask. + @Override + protected void onPostExecute(String result) { + textView.setText(result); + } + } + ... +}</pre> + +<p>The sequence of events in this snippet is as follows:</p> +<ol> + + <li>When users click the button that invokes {@code myClickHandler()}, + the app passes +the specified URL to the {@link android.os.AsyncTask} subclass +<code>DownloadWebpageTask</code>.</li> + + <li>The {@link android.os.AsyncTask} method {@link +android.os.AsyncTask#doInBackground doInBackground()} calls the +<code>downloadUrl()</code> method. </li> + + <li>The <code>downloadUrl()</code> method takes a URL string as a parameter +and uses it to create a {@link java.net.URL} object.</li> + + <li>The {@link java.net.URL} object is used to establish an {@link +java.net.HttpURLConnection}.</li> + + <li>Once the connection has been established, the {@link +java.net.HttpURLConnection} object fetches the web page content as an {@link +java.io.InputStream}.</li> + + <li>The {@link java.io.InputStream} is passed to the <code>readIt()</code> +method, which converts the stream to a string.</li> + + <li>Finally, the {@link android.os.AsyncTask}'s {@link +android.os.AsyncTask#onPostExecute onPostExecute()} method displays the string +in the main activity's UI.</li> + +</ol> + + <h2 id="download">Connect and Download Data</h2> + + <p>In your thread that performs your network transactions, you can use + {@link java.net.HttpURLConnection} to perform a {@code GET} and download your data. + After you call {@code connect()}, you can get an {@link java.io.InputStream} of the data + by calling {@code getInputStream()}. + + <p>In the following snippet, the {@link android.os.AsyncTask#doInBackground +doInBackground()} method calls the method <code>downloadUrl()</code>. The +<code>downloadUrl()</code> method takes the given URL and uses it to connect to +the network via {@link java.net.HttpURLConnection}. Once a connection has been +established, the app uses the method <code>getInputStream()</code> to retrieve +the data as an {@link java.io.InputStream}.</p> + +<pre> +// Given a URL, establishes an HttpUrlConnection and retrieves +// the web page content as a InputStream, which it returns as +// a string. +private String downloadUrl(String myurl) throws IOException { + InputStream is = 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 */); + conn.setRequestMethod("GET"); + conn.setDoInput(true); + // Starts the query + conn.connect(); + int response = conn.getResponseCode(); + Log.d(DEBUG_TAG, "The response is: " + response); + is = conn.getInputStream(); + + // Convert the InputStream into a string + String contentAsString = readIt(is, len); + return contentAsString; + + // Makes sure that the InputStream is closed after the app is + // finished using it. + } finally { + if (is != null) { + is.close(); + } + } +}</pre> + +<p>Note that the method <code>getResponseCode()</code> returns the connection's +<a href="http://www.w3.org/Protocols/HTTP/HTRESP.html">status code</a>. This is +a useful way of getting additional information about the connection. A status +code of 200 indicates success.</p> + +<h2 id="stream">Convert the InputStream to a String</h2> + +<p>An {@link java.io.InputStream} is a readable source of bytes. Once you get an {@link java.io.InputStream}, +it's common to decode or convert it into a +target data type. For example, if you were downloading image data, you might +decode and display it like this:</p> + +<pre>InputStream is = null; +... +Bitmap bitmap = BitmapFactory.decodeStream(is); +ImageView imageView = (ImageView) findViewById(R.id.image_view); +imageView.setImageBitmap(bitmap); +</pre> + +<p>In the example shown above, the {@link java.io.InputStream} represents the text of a +web page. This is how the example converts the {@link java.io.InputStream} to +a string so that the activity can display it in the UI:</p> + +<pre>// Reads an InputStream and converts it to a String. +public String readIt(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); +}</pre> + + + diff --git a/docs/html/training/basics/network-ops/index.jd b/docs/html/training/basics/network-ops/index.jd new file mode 100644 index 0000000..b213c03 --- /dev/null +++ b/docs/html/training/basics/network-ops/index.jd @@ -0,0 +1,73 @@ +page.title=Performing Network Operations + +trainingnavtop=true +startpage=true +next.title=Connecting to the Network +next.link=connecting.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + + +<h2>Dependencies and prerequisites</h2> +<ul> + <li>Android 1.6 (API level 4) or higher</li> + <li>A device that is able to connect to mobile and Wi-Fi networks</li> +</ul> + + +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}training/monitoring-device-state/index.html">Optimizing Battery Life</a></li> + <li><a href="{@docRoot}training/efficient-downloads/index.html">Transferring Data Without Draining the Battery</a></li> + <li><a href="{@docRoot}guide/webapps/index.html">Web Apps Overview</a></li> +</ul> + + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/NetworkUsage.zip" +class="button">Download the sample</a> + <p class="filename">NetworkUsage.zip</p> +</div> + +</div> +</div> + +<p>This class explains the basic tasks involved in connecting to the network, +monitoring the network connection (including connection changes), and giving +users control over an app's network usage. It also describes how to parse and +consume XML data.</p> + +<p>This class includes a sample application that illustrates how to perform +common network operations. You can download the sample (to the right) and use it +as a source of reusable code for your own application.</p> + +<p>By going through these lessons, you'll have the +fundamental building blocks for creating Android applications that download +content and parse data efficiently, while minimizing network traffic.</p> + + + +<h2>Lessons</h2> + +<dl> + <dt><b><a href="connecting.html">Connecting to the Network</a></b></dt> + + <dd>Learn how to connect to the network, choose an HTTP client, and perform +network operations outside of the UI thread.</dd> + + <dt><b><a href="managing.html">Managing Network Usage</a></b></dt> + + <dd>Learn how to check a +device's network connection, create a preferences UI for controlling network +usage, and respond to connection changes.</dd> + + <dt><b><a href="xml.html">Parsing XML Data</a></b></dt> + <dd>Learn how to parse and consume XML data.</dd> + +</dl> + diff --git a/docs/html/training/basics/network-ops/managing.jd b/docs/html/training/basics/network-ops/managing.jd new file mode 100644 index 0000000..33cb195 --- /dev/null +++ b/docs/html/training/basics/network-ops/managing.jd @@ -0,0 +1,445 @@ +page.title=Managing Network Usage +parent.title=Performing Network Operations +parent.link=index.html + +trainingnavtop=true + +previous.title=Connecting to the Network +previous.link=connecting.html +next.title=Parsing XML Data +next.link=xml.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + +<h2>This lesson teaches you to</h2> + <ol> + <li><a href="#check-connection">Check a Device's Network Connection</a></li> + <li><a href="#manage-usage">Manage Network Usage</a></li> + <li><a href="#prefs">Implement a Preferences Activity</a></li> + <li><a href="#pref-change">Respond to Preference Changes</a></li> + <li><a href="#detect-changes">Detect Connection Changes</a></li> +</ol> +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}training/monitoring-device-state/index.html">Optimizing Battery Life</a></li> + <li><a href="{@docRoot}training/efficient-downloads/index.html">Transferring Data Without Draining the Battery</a></li> + <li><a href="{@docRoot}guide/webapps/index.html">Web Apps Overview</a></li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/NetworkUsage.zip" +class="button">Download the sample</a> + <p class="filename">NetworkUsage.zip</p> +</div> + +</div> +</div> + +<p>This lesson describes how to write applications that have fine-grained +control over their usage of network resources. If your application performs a +lot of network operations, you should provide user settings that allow users +to control your app’s data habits, such as how often your app syncs data, +whether to perform uploads/downloads only when on Wi-Fi, whether to use data +while roaming, and so on. With these controls available to them, users are much +less likely to disable your app’s access to background data when they approach their +limits, because they can instead precisely control how much data your app +uses.</p> + +<p>For general guidelines on how to write apps that minimize the battery life +impact of downloads and network connections, see +<a href="{@docRoot}training/monitoring-device-state/index.html">Optimizing Battery Life</a> +and <a href="{@docRoot}training/efficient-downloads/index.html">Transferring Data Without Draining the Battery</a>. + +<h2 id="check-connection">Check a Device's Network Connection</h2> + +<p>A device can have various types of network connections. This lesson +focuses on using either a Wi-Fi or a mobile network connection. For the full +list of possible network types, see {@link android.net.ConnectivityManager}.<p> + +<p>Wi-Fi is typically faster. Also, mobile data is often metered, which can get +expensive. +A common strategy for apps is to only fetch large data +if a Wi-Fi network is available.</p> + +<p>Before you perform network operations, it's good practice to check the state of +network connectivity. Among other things, this could prevent your app from inadvertently using +the wrong radio. If a network connection is unavailable, your application +should respond gracefully. To check the network connection, you typically use +the following classes:</p> + +<ul> + + <li>{@link android.net.ConnectivityManager}: Answers queries about the state +of network connectivity. It also notifies applications when network +connectivity changes. </li> + + <li>{@link android.net.NetworkInfo}: Describes the status of a network +interface of a given type (currently either Mobile or Wi-Fi). + </li> + +</ul> + + + +<p>This code snippet tests network connectivity for Wi-Fi and mobile. It +determines whether these network interfaces are available (that is, whether +network connectivity is possible) and/or connected (that is, whether network +connectivity exists and if it is possible to establish sockets and pass +data): </p> + +<pre> +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); +</pre> + +<p>Note that you should not base decisions on whether a network is +"available." You should always check {@link +android.net.NetworkInfo#isConnected isConnected()} before performing network +operations, since {@link android.net.NetworkInfo#isConnected isConnected()} +handles cases like flaky mobile networks, airplane mode, and restricted +background data.</p> + +<p>A more concise way of checking whether a network interface is available is as +follows. The method {@link +android.net.ConnectivityManager#getActiveNetworkInfo() getActiveNetworkInfo()} +returns a {@link android.net.NetworkInfo} instance representing the first +connected network interface it can find, or <code>null</code> if none if the +interfaces is connected (meaning that an +internet connection is not available):</p> + +<pre> +public boolean isOnline() { + ConnectivityManager connMgr = (ConnectivityManager) + getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); + return (networkInfo != null && networkInfo.isConnected()); +} </pre> + +<p>To query more fine-grained state you can use {@link +android.net.NetworkInfo.DetailedState}, but this should seldom be necessary.</p> + + +<h2 id="manage-usage">Manage Network Usage</h2> + +<p>You can implement a preferences activity that gives users explicit control +over your app's usage of network resources. For +example:</p> + +<ul> + +<li>You might allow users to upload videos only when the device is connected to a +Wi-Fi network.</li> + +<li>You might sync (or not) depending on specific criteria such as network +availability, time interval, and so on.</li> + +</ul> + +<p>To write an app that supports network access and managing +network usage, your manifest must have the right permissions and +intent filters. +</p> + +<ul> + <li>The manifest excerpted below includes the following permissions: + <ul> + + <li>{@link android.Manifest.permission#INTERNET +android.permission.INTERNET}—Allows applications to open network +sockets.</li> + + <li>{@link android.Manifest.permission#ACCESS_NETWORK_STATE +android.permission.ACCESS_NETWORK_STATE}—Allows applications to access +information about networks.</li> + + </ul> + </li> + + <li>You can declare the intent filter for the +{@link android.content.Intent#ACTION_MANAGE_NETWORK_USAGE} action (introduced in +Android 4.0) to indicate that your application defines an activity that offers +options to control data usage. {@link +android.content.Intent#ACTION_MANAGE_NETWORK_USAGE} shows settings for managing +the network data usage of a specific application. When your app has a settings activity +that allows users to control network usage, you should declare this intent filter for that activity. +In the sample application, this action is handled by the class +<code>SettingsActivity</code>, which displays a preferences UI to let users +decide when to download a feed.</li> + +</ul> + + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.example.android.networkusage" + ...> + + <uses-sdk android:minSdkVersion="4" + android:targetSdkVersion="14" /> + + <uses-permission android:name="android.permission.INTERNET" /> + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + + <application + ...> + ... + <activity android:label="SettingsActivity" android:name=".SettingsActivity"> + <intent-filter> + <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + </application> +</manifest> +</pre> + +<h2 id="prefs">Implement a Preferences Activity</h2> + +<p>As you can see in the manifest excerpt above, the sample app's activity +<code>SettingsActivity</code> has an intent filter for the {@link +android.content.Intent#ACTION_MANAGE_NETWORK_USAGE} action. +<code>SettingsActivity</code> is a subclass of {@link +android.preference.PreferenceActivity}. It displays a preferences screen +(shown in figure 1) that +lets users specify the following:</p> + +<ul> + + <li>Whether to display summaries for each XML feed entry, or just a link for +each entry.</li> + + <li>Whether to download the XML feed if any network connection is available, +or only if Wi-Fi is available.</li> + +</ul> + +<img src="{@docRoot}images/training/basics/network-settings1.png" alt="Preferences panel" /> + +<img src="{@docRoot}images/training/basics/network-settings2.png" alt="Setting a network preference" /> +<p class="img-caption"><strong>Figure 1.</strong> Preferences activity.</p> + +<p>Here is <code>SettingsActivity</code>. Note that it implements +{@link android.content.SharedPreferences.OnSharedPreferenceChangeListener OnSharedPreferenceChangeListener}. +When a user changes a preference, it fires +{@link android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged onSharedPreferenceChanged()}, +which sets {@code refreshDisplay} to true. This causes the display to refresh when the user +returns to the main activity:</p> + +<pre>public class SettingsActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Loads the XML preferences file + addPreferencesFromResource(R.xml.preferences); + } + + @Override + protected void onResume() { + super.onResume(); + + // Registers a listener whenever a key changes + getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + } + + @Override + protected void onPause() { + super.onPause(); + + // Unregisters the listener set in onResume(). + // It's best practice to unregister listeners when your app isn't using them to cut down on + // unnecessary system overhead. You do this in onPause(). + getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this); + } + + // When the user changes the preferences selection, + // onSharedPreferenceChanged() restarts the main activity as a new + // task. Sets the the refreshDisplay flag to "true" to indicate that + // the main activity should update its display. + // The main activity queries the PreferenceManager to get the latest settings. + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + // Sets refreshDisplay to true so that when the user returns to the main + // activity, the display refreshes to reflect the new settings. + NetworkActivity.refreshDisplay = true; + } +}</pre> + +<h2 id="pref-change">Respond to Preference Changes</h2> + +<p>When the user changes preferences in the settings screen, it typically has +consequences for the app's behavior. In this snippet, the app checks the +preferences settings in {@code onStart()}. if there is a match between the setting and +the device's network connection (for example, if the setting is {@code "Wi-Fi"} and the +device has a Wi-Fi connection), the app downloads the feed and refreshes the +display.</p> + +<pre> +public class NetworkActivity extends Activity { + public static final String WIFI = "Wi-Fi"; + public static final String ANY = "Any"; + private static final String URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest"; + + // Whether there is a Wi-Fi connection. + private static boolean wifiConnected = false; + // Whether there is a mobile connection. + private static boolean mobileConnected = false; + // Whether the display should be refreshed. + public static boolean refreshDisplay = true; + + // The user's current network preference setting. + public static String sPref = null; + + // The BroadcastReceiver that tracks network connectivity changes. + private NetworkReceiver receiver = new NetworkReceiver(); + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Registers BroadcastReceiver to track network connection changes. + IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); + receiver = new NetworkReceiver(); + this.registerReceiver(receiver, filter); + } + + @Override + public void onDestroy() { + super.onDestroy(); + // Unregisters BroadcastReceiver when app is destroyed. + if (receiver != null) { + this.unregisterReceiver(receiver); + } + } + + // Refreshes the display if the network connection and the + // pref settings allow it. + + @Override + public void onStart () { + super.onStart(); + + // Gets the user's network preference settings + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + + // Retrieves a string value for the preferences. The second parameter + // is the default value to use if a preference value is not found. + sPref = sharedPrefs.getString("listPref", "Wi-Fi"); + + updateConnectedFlags(); + + if(refreshDisplay){ + loadPage(); + } + } + + // Checks the network connection and sets the wifiConnected and mobileConnected + // variables accordingly. + public void updateConnectedFlags() { + ConnectivityManager connMgr = (ConnectivityManager) + getSystemService(Context.CONNECTIVITY_SERVICE); + + NetworkInfo activeInfo = connMgr.getActiveNetworkInfo(); + if (activeInfo != null && activeInfo.isConnected()) { + wifiConnected = activeInfo.getType() == ConnectivityManager.TYPE_WIFI; + mobileConnected = activeInfo.getType() == ConnectivityManager.TYPE_MOBILE; + } else { + wifiConnected = false; + mobileConnected = false; + } + } + + // Uses AsyncTask subclass to download the XML feed from stackoverflow.com. + public void loadPage() { + if (((sPref.equals(ANY)) && (wifiConnected || mobileConnected)) + || ((sPref.equals(WIFI)) && (wifiConnected))) { + // AsyncTask subclass + new DownloadXmlTask().execute(URL); + } else { + showErrorPage(); + } + } +... + +}</pre> + +<h2 id="detect-changes">Detect Connection Changes</h2> + +<p>The final piece of the puzzle is the {@link +android.content.BroadcastReceiver} subclass, <code>NetworkReceiver</code>. When +the device's network connection changes, <code>NetworkReceiver</code> intercepts +the action {@link android.net.ConnectivityManager#CONNECTIVITY_ACTION}, +determines what the network connection status is, and sets the flags +<code>wifiConnected</code> and <code>mobileConnected</code> to true/false +accordingly. The upshot is that the next time the user returns to the app, the +app will only download the latest feed and update the display if +<code>NetworkActivity.refreshDisplay</code> is set to <code>true</code>.</p> + +<p>Setting up a BroadcastReceiver that gets called unnecessarily can be a +drain on system resources. +The sample application registers the +{@link android.content.BroadcastReceiver} {@code NetworkReceiver} in +{@link android.app.Activity#onCreate(android.os.Bundle) onCreate()}, +and it unregisters it in +{@link android.app.Activity#onDestroy onDestroy()}. This is more lightweight +than declaring a {@code <receiver>} in the manifest. When you declare a +{@code <receiver>} in the manifest, it can wake up your app at any time, +even if you haven't run it for weeks. By registering and unregistering +{@code NetworkReceiver} within the main activity, you ensure that the app won't +be woken up after the user leaves the app. +If you do declare a {@code <receiver>} in the manifest and you know exactly +where you need it, you can use +{@link android.content.pm.PackageManager#setComponentEnabledSetting setComponentEnabledSetting()} +to enable and disable it as appropriate.</p> + +<p>Here is <code>NetworkReceiver</code>:</p> + +<pre>public class NetworkReceiver extends BroadcastReceiver { + +@Override +public void onReceive(Context context, Intent intent) { + ConnectivityManager conn = (ConnectivityManager) + context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo networkInfo = conn.getActiveNetworkInfo(); + + // Checks the user prefs and the network connection. Based on the result, decides whether + // to refresh the display or keep the current display. + // If the userpref is Wi-Fi only, checks to see if the device has a Wi-Fi connection. + if (WIFI.equals(sPref) && networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) { + // If device has its Wi-Fi connection, sets refreshDisplay + // to true. This causes the display to be refreshed when the user + // returns to the app. + refreshDisplay = true; + Toast.makeText(context, R.string.wifi_connected, Toast.LENGTH_SHORT).show(); + + // If the setting is ANY network and there is a network connection + // (which by process of elimination would be mobile), sets refreshDisplay to true. + } else if (ANY.equals(sPref) && networkInfo != null) { + refreshDisplay = true; + + // Otherwise, the app can't download content--either because there is no network + // connection (mobile or Wi-Fi), or because the pref setting is WIFI, and there + // is no Wi-Fi connection. + // Sets refreshDisplay to false. + } else { + refreshDisplay = false; + Toast.makeText(context, R.string.lost_connection, Toast.LENGTH_SHORT).show(); + } +}</pre> + diff --git a/docs/html/training/basics/network-ops/xml.jd b/docs/html/training/basics/network-ops/xml.jd new file mode 100644 index 0000000..b148257 --- /dev/null +++ b/docs/html/training/basics/network-ops/xml.jd @@ -0,0 +1,555 @@ +page.title=Parsing XML Data +parent.title=Performing Network Operations +parent.link=index.html + +trainingnavtop=true + +previous.title=Managing Network Usage +previous.link=managing.html + +@jd:body + +<div id="tb-wrapper"> +<div id="tb"> + + + +<h2>This lesson teaches you to</h2> +<ol> + <li><a href="#choose">Choose a Parser</a></li> + <li><a href="#analyze">Analyze the Feed</a></li> + <li><a href="#instantiate">Instantiate the Parser</a></li> + <li><a href="#read">Read the Feed</a></li> + <li><a href="#parse">Parse XML</a></li> + <li><a href="#skip">Skip Tags You Don't Care About</a></li> + <li><a href="#consume">Consume XML Data</a></li> +</ol> + +<h2>You should also read</h2> +<ul> + <li><a href="{@docRoot}guide/webapps/index.html">Web Apps Overview</a></li> +</ul> + +<h2>Try it out</h2> + +<div class="download-box"> + <a href="{@docRoot}shareables/training/NetworkUsage.zip" +class="button">Download the sample</a> + <p class="filename">NetworkUsage.zip</p> +</div> + +</div> +</div> + +<p>Extensible Markup Language (XML) is a set of rules for encoding documents in +machine-readable form. XML is a popular format for sharing data on the internet. +Websites that frequently update their content, such as news sites or blogs, +often provide an XML feed so that external programs can keep abreast of content +changes. Uploading and parsing XML data is a common task for network-connected +apps. This lesson explains how to parse XML documents and use their data.</p> + +<h2 id="choose">Choose a Parser</h2> + +<p>We recommend {@link org.xmlpull.v1.XmlPullParser}, which is an efficient and +maintainable way to parse XML on Android. Historically Android has had two +implementations of this interface:</p> + +<ul> + <li><a href="http://kxml.sourceforge.net/"><code>KXmlParser</code></a> + via {@link org.xmlpull.v1.XmlPullParserFactory#newPullParser XmlPullParserFactory.newPullParser()}. + </li> + <li><code>ExpatPullParser</code>, via + {@link android.util.Xml#newPullParser Xml.newPullParser()}. + </li> +</ul> + +<p>Either choice is fine. The +example in this section uses <code>ExpatPullParser</code>, via +{@link android.util.Xml#newPullParser Xml.newPullParser()}. </p> + +<h2 id="analyze">Analyze the Feed</h2> + +<p>The first step in parsing a feed is to decide which fields you're interested in. +The parser extracts data for those fields and ignores the rest.</p> + +<p>Here is an excerpt from the feed that's being parsed in the sample app. Each +post to <a href="http://stackoverflow.com">StackOverflow.com</a> appears in the +feed as an <code>entry</code> tag that contains several nested tags:</p> + +<pre><?xml version="1.0" encoding="utf-8"?> +<feed xmlns="http://www.w3.org/2005/Atom" xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule" ..."> +<title type="text">newest questions tagged android - Stack Overflow</title> +... + <entry> + ... + </entry> + <entry> + <id>http://stackoverflow.com/q/9439999</id> + <re:rank scheme="http://stackoverflow.com">0</re:rank> + <title type="text">Where is my data file?</title> + <category scheme="http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest/tags" term="android"/> + <category scheme="http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest/tags" term="file"/> + <author> + <name>cliff2310</name> + <uri>http://stackoverflow.com/users/1128925</uri> + </author> + <link rel="alternate" href="http://stackoverflow.com/questions/9439999/where-is-my-data-file" /> + <published>2012-02-25T00:30:54Z</published> + <updated>2012-02-25T00:30:54Z</updated> + <summary type="html"> + <p>I have an Application that requires a data file...</p> + + </summary> + </entry> + <entry> + ... + </entry> +... +</feed></pre> + +<p>The sample app +extracts data for the <code>entry</code> tag and its nested tags +<code>title</code>, <code>link</code>, and <code>summary</code>.</p> + + +<h2 id="instantiate">Instantiate the Parser</h2> + +<p>The next step is to +instantiate a parser and kick off the parsing process. In this snippet, a parser +is initialized to not process namespaces, and to use the provided {@link +java.io.InputStream} as its input. It starts the parsing process with a call to +{@link org.xmlpull.v1.XmlPullParser#nextTag() nextTag()} and invokes the +<code>readFeed()</code> method, which extracts and processes the data the app is +interested in:</p> + +<pre>public class StackOverflowXmlParser { + // We don't use namespaces + private static final String ns = null; + + public List<Entry> parse(InputStream in) throws XmlPullParserException, IOException { + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); + parser.setInput(in, null); + parser.nextTag(); + return readFeed(parser); + } finally { + in.close(); + } + } + ... +}</pre> + +<h2 id="read">Read the Feed</h2> + +<p>The <code>readFeed()</code> method does the actual work of processing the +feed. It looks for elements tagged "entry" as a starting point for recursively +processing the feed. If a tag isn't an {@code entry} tag, it skips it. Once the whole +feed has been recursively processed, <code>readFeed()</code> returns a {@link +java.util.List} containing the entries (including nested data members) it +extracted from the feed. This {@link java.util.List} is then returned by the +parser.</p> + +<pre> +private List<Entry> readFeed(XmlPullParser parser) throws XmlPullParserException, IOException { + List<Entry> entries = new ArrayList<Entry>(); + + parser.require(XmlPullParser.START_TAG, ns, "feed"); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String name = parser.getName(); + // Starts by looking for the entry tag + if (name.equals("entry")) { + entries.add(readEntry(parser)); + } else { + skip(parser); + } + } + return entries; +}</pre> + + +<h2 id="parse">Parse XML</h2> + + +<p>The steps for parsing an XML feed are as follows:</p> +<ol> + + <li>As described in <a href="#analyze">Analyze the Feed</a>, identify the tags you want to include in your app. This +example extracts data for the <code>entry</code> tag and its nested tags +<code>title</code>, <code>link</code>, and <code>summary</code>.</li> + +<li>Create the following methods:</p> + +<ul> + +<li>A "read" method for each tag you're interested in. For example, +<code>readEntry()</code>, <code>readTitle()</code>, and so on. The parser reads +tags from the input stream. When it encounters a tag named <code>entry</code>, +<code>title</code>, +<code>link</code> or <code>summary</code>, it calls the appropriate method +for that tag. Otherwise, it skips the tag. +</li> + +<li>Methods to extract data for each different type of tag and to advance the +parser to the next tag. For example: +<ul> + +<li>For the <code>title</code> and <code>summary</code> tags, the parser calls +<code>readText()</code>. This method extracts data for these tags by calling +<code>parser.getText()</code>.</li> + +<li>For the <code>link</code> tag, the parser extracts data for links by first +determining if the link is the kind +it's interested in. Then it uses <code>parser.getAttributeValue()</code> to +extract the link's value.</li> + +<li>For the <code>entry</code> tag, the parser calls <code>readEntry()</code>. +This method parses the entry's nested tags and returns an <code>Entry</code> +object with the data members <code>title</code>, <code>link</code>, and +<code>summary</code>.</li> + +</ul> +</li> +<li>A helper <code>skip()</code> method that's recursive. For more discussion of this topic, see <a href="#skip">Skip Tags You Don't Care About</a>.</li> +</ul> + + </li> +</ol> + +<p>This snippet shows how the parser parses entries, titles, links, and summaries.</p> +<pre>public static class Entry { + public final String title; + public final String link; + public final String summary; + + private Entry(String title, String summary, String link) { + this.title = title; + this.summary = summary; + this.link = link; + } +} + +// Parses the contents of an entry. If it encounters a title, summary, or link tag, hands them off +// to their respective "read" methods for processing. Otherwise, skips the tag. +private Entry readEntry(XmlPullParser parser) throws XmlPullParserException, IOException { + parser.require(XmlPullParser.START_TAG, ns, "entry"); + String title = null; + String summary = null; + String link = null; + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String name = parser.getName(); + if (name.equals("title")) { + title = readTitle(parser); + } else if (name.equals("summary")) { + summary = readSummary(parser); + } else if (name.equals("link")) { + link = readLink(parser); + } else { + skip(parser); + } + } + return new Entry(title, summary, link); +} + +// Processes title tags in the feed. +private String readTitle(XmlPullParser parser) throws IOException, XmlPullParserException { + parser.require(XmlPullParser.START_TAG, ns, "title"); + String title = readText(parser); + parser.require(XmlPullParser.END_TAG, ns, "title"); + return title; +} + +// Processes link tags in the feed. +private String readLink(XmlPullParser parser) throws IOException, XmlPullParserException { + String link = ""; + parser.require(XmlPullParser.START_TAG, ns, "link"); + String tag = parser.getName(); + String relType = parser.getAttributeValue(null, "rel"); + if (tag.equals("link")) { + if (relType.equals("alternate")){ + link = parser.getAttributeValue(null, "href"); + parser.nextTag(); + } + } + parser.require(XmlPullParser.END_TAG, ns, "link"); + return link; +} + +// Processes summary tags in the feed. +private String readSummary(XmlPullParser parser) throws IOException, XmlPullParserException { + parser.require(XmlPullParser.START_TAG, ns, "summary"); + String summary = readText(parser); + parser.require(XmlPullParser.END_TAG, ns, "summary"); + return summary; +} + +// For the tags title and summary, extracts their text values. +private String readText(XmlPullParser parser) throws IOException, XmlPullParserException { + String result = ""; + if (parser.next() == XmlPullParser.TEXT) { + result = parser.getText(); + parser.nextTag(); + } + return result; +} + ... +}</pre> + +<h2 id="skip">Skip Tags You Don't Care About</h2> + +<p>One of the steps in the XML parsing described above is for the parser to skip tags it's not interested in. Here is the parser's <code>skip()</code> method:</p> + +<pre> +private void skip(XmlPullParser parser) throws XmlPullParserException, IOException { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException(); + } + int depth = 1; + while (depth != 0) { + switch (parser.next()) { + case XmlPullParser.END_TAG: + depth--; + break; + case XmlPullParser.START_TAG: + depth++; + break; + } + } + } +</pre> + +<p>This is how it works:</p> + +<ul> + +<li>It throws an exception if the current event isn't a +<code>START_TAG</code>.</li> + +<li>It consumes the <code>START_TAG</code>, and all events up to and including +the matching <code>END_TAG</code>.</li> + +<li>To make sure that it stops at the correct <code>END_TAG</code> and not at +the first tag it encounters after the original <code>START_TAG</code>, it keeps +track of the nesting depth.</li> + +</ul> + +<p>Thus if the current element has nested elements, the value of +<code>depth</code> won't be 0 until the parser has consumed all events between +the original <code>START_TAG</code> and its matching <code>END_TAG</code>. For +example, consider how the parser skips the <code><author></code> element, +which has 2 nested elements, <code><name></code> and +<code><uri></code>:</p> + +<ul> + +<li>The first time through the <code>while</code> loop, the next tag the parser +encounters after <code><author></code> is the <code>START_TAG</code> for +<code><name></code>. The value for <code>depth</code> is incremented to +2.</li> + +<li>The second time through the <code>while</code> loop, the next tag the parser +encounters is the <code>END_TAG</code> <code></name></code>. The value +for <code>depth</code> is decremented to 1.</li> + +<li>The third time through the <code>while</code> loop, the next tag the parser +encounters is the <code>START_TAG</code> <code><uri></code>. The value +for <code>depth</code> is incremented to 2.</li> + +<li>The fourth time through the <code>while</code> loop, the next tag the parser +encounters is the <code>END_TAG</code> <code></uri></code>. The value for +<code>depth</code> is decremented to 1.</li> + +<li>The fifth time and final time through the <code>while</code> loop, the next +tag the parser encounters is the <code>END_TAG</code> +<code></author></code>. The value for <code>depth</code> is decremented to +0, indicating that the <code><author></code> element has been successfully +skipped.</li> + +</ul> + +<h2 id="consume">Consume XML Data</h2> + +<p>The example application fetches and parses the XML feed within an {@link +android.os.AsyncTask}. This takes the processing off the main UI thread. When +processing is complete, the app updates the UI in the main activity +(<code>NetworkActivity</code>).</p> +<p>In the excerpt shown below, the <code>loadPage()</code> method does the +following:</p> + +<ul> + + <li>Initializes a string variable with the URL for the XML feed.</li> + + <li>If the user's settings and the network connection allow it, invokes +<code>new DownloadXmlTask().execute(url)</code>. This instantiates a new +<code>DownloadXmlTask</code> object ({@link android.os.AsyncTask} subclass) and +runs its {@link android.os.AsyncTask#execute execute()} method, which downloads +and parses the feed and returns a string result to be displayed in the UI.</li> + +</ul> +<pre> +public class NetworkActivity extends Activity { + public static final String WIFI = "Wi-Fi"; + public static final String ANY = "Any"; + private static final String URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest"; + + // Whether there is a Wi-Fi connection. + private static boolean wifiConnected = false; + // Whether there is a mobile connection. + private static boolean mobileConnected = false; + // Whether the display should be refreshed. + public static boolean refreshDisplay = true; + public static String sPref = null; + + ... + + // Uses AsyncTask to download the XML feed from stackoverflow.com. + public void loadPage() { + + if((sPref.equals(ANY)) && (wifiConnected || mobileConnected)) { + new DownloadXmlTask().execute(URL); + } + else if ((sPref.equals(WIFI)) && (wifiConnected)) { + new DownloadXmlTask().execute(URL); + } else { + // show error + } + }</pre> + +<p>The {@link android.os.AsyncTask} subclass shown below, +<code>DownloadXmlTask</code>, implements the following {@link +android.os.AsyncTask} methods:</p> + + <ul> + + <li>{@link android.os.AsyncTask#doInBackground doInBackground()} executes +the method <code>loadXmlFromNetwork()</code>. It passes the feed URL as a +parameter. The method <code>loadXmlFromNetwork()</code> fetches and processes +the feed. When it finishes, it passes back a result string.</li> + + <li>{@link android.os.AsyncTask#onPostExecute onPostExecute()} takes the +returned string and displays it in the UI.</li> + + </ul> + +<pre> +// Implementation of AsyncTask used to download XML feed from stackoverflow.com. +private class DownloadXmlTask extends AsyncTask<String, Void, String> { + @Override + protected String doInBackground(String... urls) { + try { + return loadXmlFromNetwork(urls[0]); + } catch (IOException e) { + return getResources().getString(R.string.connection_error); + } catch (XmlPullParserException e) { + return getResources().getString(R.string.xml_error); + } + } + + @Override + protected void onPostExecute(String result) { + setContentView(R.layout.main); + // Displays the HTML string in the UI via a WebView + WebView myWebView = (WebView) findViewById(R.id.webview); + myWebView.loadData(result, "text/html", null); + } +}</pre> + + <p>Below is the method <code>loadXmlFromNetwork()</code> that is invoked from +<code>DownloadXmlTask</code>. It does the following:</p> + + <ol> + + <li>Instantiates a <code>StackOverflowXmlParser</code>. It also creates variables for +a {@link java.util.List} of <code>Entry</code> objects (<code>entries</code>), and +<code>title</code>, <code>url</code>, and <code>summary</code>, to hold the +values extracted from the XML feed for those fields.</li> + + <li>Calls <code>downloadUrl()</code>, which fetches the feed and returns it as + an {@link java.io.InputStream}.</li> + + <li>Uses <code>StackOverflowXmlParser</code> to parse the {@link java.io.InputStream}. + <code>StackOverflowXmlParser</code> populates a + {@link java.util.List} of <code>entries</code> with data from the feed.</li> + + <li>Processes the <code>entries</code> {@link java.util.List}, + and combines the feed data with HTML markup.</li> + + <li>Returns an HTML string that is displayed in the main activity +UI by the {@link android.os.AsyncTask} method {@link +android.os.AsyncTask#onPostExecute onPostExecute()}.</li> + +</ol> + +<pre> +// Uploads XML from stackoverflow.com, parses it, and combines it with +// HTML markup. Returns HTML string. +private String loadXmlFromNetwork(String urlString) throws XmlPullParserException, IOException { + InputStream stream = null; + // Instantiate the parser + StackOverflowXmlParser stackOverflowXmlParser = new StackOverflowXmlParser(); + List<Entry> entries = null; + String title = null; + String url = null; + String summary = null; + Calendar rightNow = Calendar.getInstance(); + DateFormat formatter = new SimpleDateFormat("MMM dd h:mmaa"); + + // Checks whether the user set the preference to include summary text + SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); + boolean pref = sharedPrefs.getBoolean("summaryPref", false); + + StringBuilder htmlString = new StringBuilder(); + htmlString.append("<h3>" + getResources().getString(R.string.page_title) + "</h3>"); + htmlString.append("<em>" + getResources().getString(R.string.updated) + " " + + formatter.format(rightNow.getTime()) + "</em>"); + + try { + stream = downloadUrl(urlString); + entries = stackOverflowXmlParser.parse(stream); + // Makes sure that the InputStream is closed after the app is + // finished using it. + } finally { + if (stream != null) { + stream.close(); + } + } + + // StackOverflowXmlParser returns a List (called "entries") of Entry objects. + // Each Entry object represents a single post in the XML feed. + // This section processes the entries list to combine each entry with HTML markup. + // Each entry is displayed in the UI as a link that optionally includes + // a text summary. + for (Entry entry : entries) { + htmlString.append("<p><a href='"); + htmlString.append(entry.link); + htmlString.append("'>" + entry.title + "</a></p>"); + // If the user set the preference to include summary text, + // adds it to the display. + if (pref) { + htmlString.append(entry.summary); + } + } + return htmlString.toString(); +} + +// Given a string representation of a URL, sets up a connection and gets +// an input stream. +private InputStream downloadUrl(String urlString) throws IOException { + URL url = new URL(urlString); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setReadTimeout(10000 /* milliseconds */); + conn.setConnectTimeout(15000 /* milliseconds */); + conn.setRequestMethod("GET"); + conn.setDoInput(true); + // Starts the query + conn.connect(); + InputStream stream = conn.getInputStream(); +}</pre> |
