In order to perform network operations in your application, your manifest must include the following permissions:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Design secure network communication
Before you add networking functionality to your app, you need to ensure that data and information within your app stays safe when transmitting it over a network. To do so, follow these networking security best practices:
- Minimize the amount of sensitive or personal user data that you transmit over the network.
- Send all network traffic from your app over SSL.
- Consider creating a network security configuration, which allows your app to trust custom CAs or restrict the set of system CAs that it trusts for secure communication.
For more information on applying secure networking principles, see Android's networking security tips. You can also check out the Android NetworkConnect sample.
Choose an HTTP client
Most network-connected Android apps use HTTP to send and receive data. The
Android platform includes the
HttpsURLConnection
client,
which supports TLS, streaming uploads and downloads, configurable timeouts,
IPv6, and connection pooling.
Look up DNS
On devices running Android 9 and lower, the platform DNS resolver supports only A and AAAA records, which allow looking up the IP addresses associated with a name, but does not support any other record types.
On devices running Android 10 and higher, there is native support
for specialized DNS lookups using both cleartext lookups and DNS-over-TLS mode.
The DnsResolver
API provides generic,
asynchronous resolution, enabling you to look up SRV
, NAPTR
, and other
record types. Note that parsing the response is left to the app to perform.
For NDK-based apps, see
android_res_nsend
.
Introduce network operations on a separate thread
To avoid creating an unresponsive UI, don't perform network operations on the
UI thread. By default, Android 3.0 (API level 11) and higher requires you to
perform network operations on a thread other than the main UI thread; if you
don't, a
NetworkOnMainThreadException
is thrown.
The following Activity
snippet uses a headless Fragment
to encapsulate
asynchronous network operations. Later, you will see how the Fragment
implementation, NetworkFragment
, accomplishes this. Your
Activity
should also implement the DownloadCallback
interface, allowing the
fragment to call back to the Activity
in case it needs connectivity status or
needs to send an update back to the UI.
Kotlin
class MainActivity : FragmentActivity(), DownloadCallback<String> { ... // Keep a reference to the NetworkFragment, which owns the AsyncTask object // that is used to execute network ops. private var networkFragment: NetworkFragment? = null // Boolean telling us whether a download is in progress, so we don't trigger overlapping // downloads with consecutive button clicks. private var downloading = false override fun onCreate(savedInstanceState: Bundle?) { ... networkFragment = NetworkFragment.getInstance(supportFragmentManager, "https://www.google.com") } private fun startDownload() { if (!downloading) { // Execute the async download. networkFragment?.apply { startDownload() downloading = true } } } }
Java
public class MainActivity extends FragmentActivity implements DownloadCallback { ... // Keep a reference to the NetworkFragment, which owns the AsyncTask object // that is used to execute network ops. private NetworkFragment networkFragment; // Boolean telling us whether a download is in progress, so we don't trigger overlapping // downloads with consecutive button clicks. private boolean downloading = false; @Override protected void onCreate(Bundle savedInstanceState) { ... networkFragment = NetworkFragment.getInstance(getSupportFragmentManager(), "https://www.google.com"); } private void startDownload() { if (!downloading && networkFragment != null) { // Execute the async download. networkFragment.startDownload(); downloading = true; } } }
At the minimum, your DownloadCallback
interface can consist of the following:
Kotlin
const val ERROR = -1 const val CONNECT_SUCCESS = 0 const val GET_INPUT_STREAM_SUCCESS = 1 const val PROCESS_INPUT_STREAM_IN_PROGRESS = 2 const val PROCESS_INPUT_STREAM_SUCCESS = 3 interface DownloadCallback<T> { /** * Indicates that the callback handler needs to update its appearance or information based on * the result of the task. Expected to be called from the main thread. */ fun updateFromDownload(result: T?) /** * Get the device's active network status in the form of a NetworkInfo object. */ fun getActiveNetworkInfo(): NetworkInfo /** * Indicate to callback handler any progress update. * @param progressCode must be one of the constants defined in DownloadCallback.Progress. * @param percentComplete must be 0-100. */ fun onProgressUpdate(progressCode: Int, percentComplete: Int) /** * Indicates that the download operation has finished. This method is called even if the * download hasn't completed successfully. */ fun finishDownloading() }
Java
public interface DownloadCallback<T> { interface Progress { int ERROR = -1; int CONNECT_SUCCESS = 0; int GET_INPUT_STREAM_SUCCESS = 1; int PROCESS_INPUT_STREAM_IN_PROGRESS = 2; int PROCESS_INPUT_STREAM_SUCCESS = 3; } /** * Indicates that the callback handler needs to update its appearance or information based on * the result of the task. Expected to be called from the main thread. */ void updateFromDownload(T result); /** * Get the device's active network status in the form of a NetworkInfo object. */ NetworkInfo getActiveNetworkInfo(); /** * Indicate to callback handler any progress update. * @param progressCode must be one of the constants defined in DownloadCallback.Progress. * @param percentComplete must be 0-100. */ void onProgressUpdate(int progressCode, int percentComplete); /** * Indicates that the download operation has finished. This method is called even if the * download hasn't completed successfully. */ void finishDownloading(); }
Now, add the following implementations of the DownloadCallback
interface
methods to your Activity
:
Kotlin
override fun updateFromDownload(result: String?) { // Update your UI here based on result of download. } override fun getActiveNetworkInfo(): NetworkInfo { val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager return connectivityManager.activeNetworkInfo } override fun onProgressUpdate(progressCode: Int, percentComplete: Int) { when (progressCode) { // You can add UI behavior for progress updates here. ERROR -> { } CONNECT_SUCCESS -> { } GET_INPUT_STREAM_SUCCESS -> { } PROCESS_INPUT_STREAM_IN_PROGRESS -> { } PROCESS_INPUT_STREAM_SUCCESS -> { } } } override fun finishDownloading() { downloading = false networkFragment?.cancelDownload() }
Java
@Override public void updateFromDownload(String result) { // Update your UI here based on result of download. } @Override public NetworkInfo getActiveNetworkInfo() { ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); return networkInfo; } @Override public void onProgressUpdate(int progressCode, int percentComplete) { switch(progressCode) { // You can add UI behavior for progress updates here. case Progress.ERROR: ... break; case Progress.CONNECT_SUCCESS: ... break; case Progress.GET_INPUT_STREAM_SUCCESS: ... break; case Progress.PROCESS_INPUT_STREAM_IN_PROGRESS: ... break; case Progress.PROCESS_INPUT_STREAM_SUCCESS: ... break; } } @Override public void finishDownloading() { downloading = false; if (networkFragment != null) { networkFragment.cancelDownload(); } }
Implement a headless fragment to encapsulate network operations
Since the NetworkFragment
runs on the UI thread by default, it uses an
AsyncTask
to run the network operations on
a background thread. This Fragment
is considered headless because it doesn't
reference any UI elements. Instead, it is only used to encapsulate logic and
handle lifecycle events, leaving the host Activity
to update the UI.
When using a subclass of AsyncTask
to run network operations, you must be
cautious that you don't create a memory leak in the case where the Activity
that is referenced by the AsyncTask
is destroyed before the AsyncTask
finishes its background work. To ensure this doesn't happen, the following
snippet clears any references to the Activity
in the Fragment
's
onDetach()
method.
Kotlin
private const val TAG = "NetworkFragment" private const val URL_KEY = "UrlKey" class NetworkFragment : Fragment() { private var callback: DownloadCallback<String>? = null private var downloadTask: DownloadTask? = null private var urlString: String? = null companion object { /** * Static initializer for NetworkFragment that sets the URL of the host it will be * downloading from. */ fun getInstance(fragmentManager: FragmentManager, url: String): NetworkFragment { val networkFragment = NetworkFragment() val args = Bundle() args.putString(URL_KEY, url) networkFragment.arguments = args fragmentManager.beginTransaction().add(networkFragment, TAG).commit() return networkFragment } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) urlString = arguments?.getString(URL_KEY) ... } override fun onAttach(context: Context?) { super.onAttach(context) // Host Activity will handle callbacks from task. callback = context as? DownloadCallback<String> } override fun onDetach() { super.onDetach() // Clear reference to host Activity to avoid memory leak. callback = null } override fun onDestroy() { // Cancel task when Fragment is destroyed. cancelDownload() super.onDestroy() } /** * Start non-blocking execution of DownloadTask. */ fun startDownload() { cancelDownload() callback?.also { downloadTask = DownloadTask(it).apply { execute(urlString) } } } /** * Cancel (and interrupt if necessary) any ongoing DownloadTask execution. */ fun cancelDownload() { downloadTask?.cancel(true) } ... }
Java
/** * Implementation of headless Fragment that runs an AsyncTask to fetch data from the network. */ public class NetworkFragment extends Fragment { public static final String TAG = "NetworkFragment"; private static final String URL_KEY = "UrlKey"; private DownloadCallback<String> callback; private DownloadTask downloadTask; private String urlString; /** * Static initializer for NetworkFragment that sets the URL of the host it will be downloading * from. */ public static NetworkFragment getInstance(FragmentManager fragmentManager, String url) { NetworkFragment networkFragment = new NetworkFragment(); Bundle args = new Bundle(); args.putString(URL_KEY, url); networkFragment.setArguments(args); fragmentManager.beginTransaction().add(networkFragment, TAG).commit(); return networkFragment; } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); urlString = getArguments().getString(URL_KEY); ... } @Override public void onAttach(Context context) { super.onAttach(context); // Host Activity will handle callbacks from task. callback = (DownloadCallback<String>) context; } @Override public void onDetach() { super.onDetach(); // Clear reference to host Activity to avoid memory leak. callback = null; } @Override public void onDestroy() { // Cancel task when Fragment is destroyed. cancelDownload(); super.onDestroy(); } /** * Start non-blocking execution of DownloadTask. */ public void startDownload() { cancelDownload(); downloadTask = new DownloadTask(callback); downloadTask.execute(urlString); } /** * Cancel (and interrupt if necessary) any ongoing DownloadTask execution. */ public void cancelDownload() { if (downloadTask != null) { downloadTask.cancel(true); } } ... }
Now, you should implement a subclass of AsyncTask
as a private inner class
inside your Fragment
:
Kotlin
/** * Implementation of AsyncTask designed to fetch data from the network. */ private class DownloadTask(callback: DownloadCallback<String>) : AsyncTask<String, Int, DownloadTask.Result>() { private var callback: DownloadCallback<String>? = null init { setCallback(callback) } internal fun setCallback(callback: DownloadCallback<String>) { this.callback = callback } /** * Wrapper class that serves as a union of a result value and an exception. When the download * task has completed, either the result value or exception can be a non-null value. * This allows you to pass exceptions to the UI thread that were thrown during doInBackground(). */ internal class Result { var resultValue: String? = null var exception: Exception? = null constructor(resultValue: String) { this.resultValue = resultValue } constructor(exception: Exception) { this.exception = exception } } /** * Cancel background network operation if we do not have network connectivity. */ override fun onPreExecute() { if (callback != null) { val networkInfo = callback?.getActiveNetworkInfo() if (networkInfo?.isConnected == false || networkInfo?.type != ConnectivityManager.TYPE_WIFI && networkInfo?.type != ConnectivityManager.TYPE_MOBILE) { // If no connectivity, cancel task and update Callback with null data. callback?.updateFromDownload(null) cancel(true) } } } /** * Defines work to perform on the background thread. */ override fun doInBackground(vararg urls: String): DownloadTask.Result? { var result: Result? = null if (!isCancelled && urls.isNotEmpty()) { val urlString = urls[0] result = try { val url = URL(urlString) val resultString = downloadUrl(url) if (resultString != null) { Result(resultString) } else { throw IOException("No response received.") } } catch (e: Exception) { Result(e) } } return result } /** * Updates the DownloadCallback with the result. */ override fun onPostExecute(result: Result?) { callback?.apply { result?.exception?.also { exception -> updateFromDownload(exception.message) return } result?.resultValue?.also { resultValue -> updateFromDownload(resultValue) return } finishDownloading() } } /** * Override to add special behavior for cancelled AsyncTask. */ override fun onCancelled(result: Result) {} }
Java
/** * Implementation of AsyncTask designed to fetch data from the network. */ private class DownloadTask extends AsyncTask<String, Integer, DownloadTask.Result> { private DownloadCallback<String> callback; DownloadTask(DownloadCallback<String> callback) { setCallback(callback); } void setCallback(DownloadCallback<String> callback) { this.callback = callback; } /** * Wrapper class that serves as a union of a result value and an exception. When the download * task has completed, either the result value or exception can be a non-null value. * This allows you to pass exceptions to the UI thread that were thrown during doInBackground(). */ static class Result { public String resultValue; public Exception exception; public Result(String resultValue) { this.resultValue = resultValue; } public Result(Exception exception) { this.exception = exception; } } /** * Cancel background network operation if we do not have network connectivity. */ @Override protected void onPreExecute() { if (callback != null) { NetworkInfo networkInfo = callback.getActiveNetworkInfo(); if (networkInfo == null || !networkInfo.isConnected() || (networkInfo.getType() != ConnectivityManager.TYPE_WIFI && networkInfo.getType() != ConnectivityManager.TYPE_MOBILE)) { // If no connectivity, cancel task and update Callback with null data. callback.updateFromDownload(null); cancel(true); } } } /** * Defines work to perform on the background thread. */ @Override protected DownloadTask.Result doInBackground(String... urls) { Result result = null; if (!isCancelled() && urls != null && urls.length > 0) { String urlString = urls[0]; try { URL url = new URL(urlString); String resultString = downloadUrl(url); if (resultString != null) { result = new Result(resultString); } else { throw new IOException("No response received."); } } catch(Exception e) { result = new Result(e); } } return result; } /** * Updates the DownloadCallback with the result. */ @Override protected void onPostExecute(Result result) { if (result != null && callback != null) { if (result.exception != null) { callback.updateFromDownload(result.exception.getMessage()); } else if (result.resultValue != null) { callback.updateFromDownload(result.resultValue); } callback.finishDownloading(); } } /** * Override to add special behavior for cancelled AsyncTask. */ @Override protected void onCancelled(Result result) { } }
Use HttpsUrlConnection to fetch data
In the snippet above, the
doInBackground()
method runs in a background thread and calls the helper method downloadUrl()
.
The downloadUrl()
method should take the given URL and use it to perform an
HTTP GET
request. Once a connection has been established, you should use the
getInputStream()
method
to retrieve the data as an InputStream
. The
following snippet uses the HttpsURLConnection
API to accomplish this:
Kotlin
/** * Given a URL, sets up a connection and gets the HTTP response body from the server. * If the network request is successful, it returns the response body in String form. Otherwise, * it will throw an IOException. */ @Throws(IOException::class) private fun downloadUrl(url: URL): String? { var connection: HttpsURLConnection? = null return try { connection = (url.openConnection() as? HttpsURLConnection) connection?.run { // Timeout for reading InputStream arbitrarily set to 3000ms. readTimeout = 3000 // Timeout for connection.connect() arbitrarily set to 3000ms. connectTimeout = 3000 // For this use case, set HTTP method to GET. requestMethod = "GET" // Already true by default but setting just in case; needs to be true since this request // is carrying an input (response) body. doInput = true // Open communications link (network traffic occurs here). connect() publishProgress(CONNECT_SUCCESS) if (responseCode != HttpsURLConnection.HTTP_OK) { throw IOException("HTTP error code: $responseCode") } // Retrieve the response body as an InputStream. publishProgress(GET_INPUT_STREAM_SUCCESS, 0) inputStream?.let { stream -> // Converts Stream to String with max length of 500. readStream(stream, 500) } } } finally { // Close Stream and disconnect HTTPS connection. connection?.inputStream?.close() connection?.disconnect() } }
Java
/** * Given a URL, sets up a connection and gets the HTTP response body from the server. * If the network request is successful, it returns the response body in String form. Otherwise, * it will throw an IOException. */ private String downloadUrl(URL url) throws IOException { InputStream stream = null; HttpsURLConnection connection = null; String result = null; try { connection = (HttpsURLConnection) url.openConnection(); // Timeout for reading InputStream arbitrarily set to 3000ms. connection.setReadTimeout(3000); // Timeout for connection.connect() arbitrarily set to 3000ms. connection.setConnectTimeout(3000); // For this use case, set HTTP method to GET. connection.setRequestMethod("GET"); // Already true by default but setting just in case; needs to be true since this request // is carrying an input (response) body. connection.setDoInput(true); // Open communications link (network traffic occurs here). connection.connect(); publishProgress(DownloadCallback.Progress.CONNECT_SUCCESS); int responseCode = connection.getResponseCode(); if (responseCode != HttpsURLConnection.HTTP_OK) { throw new IOException("HTTP error code: " + responseCode); } // Retrieve the response body as an InputStream. stream = connection.getInputStream(); publishProgress(DownloadCallback.Progress.GET_INPUT_STREAM_SUCCESS, 0); if (stream != null) { // Converts Stream to String with max length of 500. result = readStream(stream, 500); } } finally { // Close Stream and disconnect HTTPS connection. if (stream != null) { stream.close(); } if (connection != null) { connection.disconnect(); } } return result; }
Note that the method getResponseCode()
returns the connection's status
code. This is a useful way of getting
additional information about the connection. A status code of 200 indicates
success.
For a further look into HttpsURLConnection
, see the Android NetworkConnect
Sample.
Convert 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 a target data type. For example, if you
were downloading image data, you might decode and display it like this:
Kotlin
val inputStream: InputStream? = null ... val bitmap: Bitmap = BitmapFactory.decodeStream(inputStream) findViewById<ImageView>(R.id.image_view)?.apply { setImageBitmap(bitmap) }
Java
InputStream inputStream = null; ... Bitmap bitmap = BitmapFactory.decodeStream(inputStream); ImageView imageView = (ImageView) findViewById(R.id.image_view); imageView.setImageBitmap(bitmap);
In the example shown above, the InputStream
represents the text of the
response body. This is how you would convert the InputStream
to a string so
that the Activity
can display it in the UI:
Kotlin
/** * Converts the contents of an InputStream to a String. */ @Throws(IOException::class, UnsupportedEncodingException::class) fun readStream(stream: InputStream, maxReadSize: Int): String? { val reader: Reader? = InputStreamReader(stream, "UTF-8") val rawBuffer = CharArray(maxReadSize) val buffer = StringBuffer() var readSize: Int = reader?.read(rawBuffer) ?: -1 var maxReadBytes = maxReadSize while (readSize != -1 && maxReadBytes > 0) { if (readSize > maxReadBytes) { readSize = maxReadBytes } buffer.append(rawBuffer, 0, readSize) maxReadBytes -= readSize readSize = reader?.read(rawBuffer) ?: -1 } return buffer.toString() }
Java
/** * Converts the contents of an InputStream to a String. */ public String readStream(InputStream stream, int maxReadSize) throws IOException, UnsupportedEncodingException { Reader reader = null; reader = new InputStreamReader(stream, "UTF-8"); char[] rawBuffer = new char[maxReadSize]; int readSize; StringBuffer buffer = new StringBuffer(); while (((readSize = reader.read(rawBuffer)) != -1) && maxReadSize > 0) { if (readSize > maxReadSize) { readSize = maxReadSize; } buffer.append(rawBuffer, 0, readSize); maxReadSize -= readSize; } return buffer.toString(); }
The sequence of events in the code so far is as follows:
The
Activity
starts aNetworkFragment
and passes in a specified URL.When a user action triggers the
Activity
'sdownloadData()
method, theNetworkFragment
executes theDownloadTask
.The
AsyncTask
methodonPreExecute()
runs first (on the UI thread) and cancels the task if the device is not connected to the internet.The
AsyncTask
methoddoInBackground()
then runs on the background thread and calls thedownloadUrl()
method.The
downloadUrl()
method takes a URL string as a parameter and uses anHttpsURLConnection
object to fetch the web content as anInputStream
.The
InputStream
is passed to thereadStream()
method, which converts the stream to a string.Finally, once the background work is complete, the
AsyncTask
'sonPostExecute()
method runs on the UI thread and uses theDownloadCallback
to send the result back to the UI as a String.
Survive configuration changes
So far, you have successfully implemented an Activity
that performs a network
operation. But, if the user decides to change the device configuration (such as
to rotate the screen 90 degrees) while
doInBackground()
is running on the background thread, the Activity
destroys and recreates
itself, causing it to re-run onCreate()
and reference a new NetworkFragment
(see Runtime Changes guide).
Therefore, the AsyncTask
living in the original NetworkFragment
will have a
DownloadCallback
that references the original Activity
that can no longer
update the UI. Thus, the network work done on the background thread will have
been wasted.
To persist through these configuration changes, you need to retain your original
Fragment
and ensure that the reconstructed Activity
references it. To
accomplish this, you should make the following modifications to your code.
First, your NetworkFragment
should call
setRetainInstance(true)
in its
onCreate()
method as shown in the following code sample:
Kotlin
override fun onCreate(savedInstanceState: Bundle?) { ... // Retain this Fragment across configuration changes in the host Activity. retainInstance = true }
Java
@Override public void onCreate(@Nullable Bundle savedInstanceState) { ... // Retain this Fragment across configuration changes in the host Activity. setRetainInstance(true); }
Then, modify how you initialize the NetworkFragment
in your static
getInstance()
method:
Kotlin
companion object { fun getInstance(fragmentManager: FragmentManager, url: String): NetworkFragment { // Recover NetworkFragment in case we are re-creating the Activity due to a config change. // This is necessary because NetworkFragment might have a task that began running before // the config change occurred and has not finished yet. // The NetworkFragment is recoverable because it calls setRetainInstance(true). var networkFragment = fragmentManager.findFragmentByTag(TAG) as? NetworkFragment if (networkFragment == null) { networkFragment = NetworkFragment() networkFragment.arguments = Bundle().apply { putString(URL_KEY, url) } fragmentManager.beginTransaction() .add(networkFragment, TAG) .commit() } return networkFragment } }
Java
public static NetworkFragment getInstance(FragmentManager fragmentManager, String url) { // Recover NetworkFragment in case we are re-creating the Activity due to a config change. // This is necessary because NetworkFragment might have a task that began running before // the config change occurred and has not finished yet. // The NetworkFragment is recoverable because it calls setRetainInstance(true). NetworkFragment networkFragment = (NetworkFragment) fragmentManager .findFragmentByTag(NetworkFragment.TAG); if (networkFragment == null) { networkFragment = new NetworkFragment(); Bundle args = new Bundle(); args.putString(URL_KEY, url); networkFragment.setArguments(args); fragmentManager.beginTransaction().add(networkFragment, TAG).commit(); } return networkFragment; }
Your app can now successfully pull data from the internet!
Note that there are several other background thread management tools that can
help you achieve this same goal. As your app grows in complexity, you might find
that these other tools are better suited for your app. Instead of AsyncTask
,
options worth investigating are
IntentService
and
AsyncTaskLoader
.
Read related guides
To learn more about this topic, see the following related guides: