Únete a ⁠ #Android11: The Beta Launch Show el 3 de junio.

Cómo conectarse a la red

Para que se puedan llevar a cabo operaciones de red en tu app, el manifiesto debe incluir los siguientes permisos:

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

Cómo diseñar una comunicación de red segura

Antes de agregar funcionalidades de red en tu app, debes asegurarte de mantener la seguridad de los datos y la información de la app cuando los transmitas en una red. Para hacerlo, sigue estas prácticas recomendadas de seguridad de redes:

  • Minimiza la cantidad de datos del usuario sensibles o personales que se transmiten en la red.
  • Envía todo el tráfico de red de tu app a través de SSL.
  • Puedes crear una configuración de seguridad de la red, que le permite a tu app confiar en CA personalizadas o restringir el conjunto de CA del sistema en las que confía para lograr una comunicación segura.

Para obtener más información sobre cómo aplicar principios de redes seguras, consulta las sugerencias sobre seguridad de red. También puedes echar un vistazo al ejemplo de Android NetworkConnect.

Cómo elegir un cliente HTTP

La mayoría de las apps para Android conectadas a redes utilizan HTTP para enviar y recibir datos. La plataforma Android incluye el cliente HttpsURLConnection, que admite TLS, cargas y descargas de transmisión, tiempos de espera configurables, IPv6 y agrupaciones de conexiones.

Busca DNS

En nuestros dispositivos que funcionan con Android 9 y versiones anteriores, el agente de resolución de DNS de la plataforma solo admite registros A y AAAA, lo que permite buscar solo las direcciones IP asociadas con un nombre, pero no admite ningún otro tipo de registro.

En dispositivos con Android 10 y versiones posteriores, se agregó compatibilidad nativa para búsquedas de DNS especializadas mediante búsquedas de Cleartext y un modo de DNS mediante TLS. La API de DnsResolver proporciona una resolución asíncrona genérica que te permite buscar SRV, NAPTR y otros tipos de registros. Ten en cuenta que la app realizará el análisis de la respuesta.

Para apps basadas en NDK, consulta android_res_nsend.

Cómo introducir las operaciones de red en un subproceso independiente

Para evitar crear una IU sin respuesta, te recomendamos no llevar a cabo operaciones de red en el subproceso de la IU. De forma predeterminada, Android 3.0 (API nivel 11) y las versiones posteriores requieren que realices las operaciones de red en un subproceso de IU que no sea el principal. De lo contrario, se arrojará un NetworkOnMainThreadException.

El siguiente fragmento de Activity usa un Fragment sin interfaz gráfica para encapsular las operaciones de red asíncronas. Más adelante, verás cómo la implementación de Fragment, NetworkFragment, logra esto. Tu actividad también debería implementar la interfaz de DownloadCallback, lo que permite que el fragmento vuelva a llamar a la actividad en caso de que necesite un estado de conectividad o enviar una actualización a la IU.

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;
            }
        }
    }
    

Como mínimo, la interfaz DownloadCallback puede incluir los siguientes elementos:

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();
    }
    

Ahora, agrega las siguientes implementaciones de los métodos de la interfaz de DownloadCallback en tu 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();
        }
    }
    

Cómo implementar un fragmento sin interfaz gráfica para encapsular operaciones de red

Como NetworkFragment se ejecuta en el subproceso de IU de forma predeterminada, usa AsyncTask para ejecutar las operaciones de red en un subproceso en segundo plano. Se considera que este Fragment no tiene interfaz gráfica porque no hace referencia a ningún elemento de la IU. En cambio, solo se utiliza para encapsular la lógica y gestionar los eventos de ciclo de vida, y se deja que el elemento Activity del host actualice la IU.

Cuando uses una subclase de AsyncTask para ejecutar operaciones de red, debes tener cuidado de no crear una fuga de memoria en el caso de que se destruya la actividad a la que hace referencia AsyncTask antes de que AsyncTask termine su trabajo en segundo plano. Para garantizar que esto no suceda, el siguiente fragmento borra cualquier referencia a la actividad en el método onDetach() del fragmento.

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);
            }
        }

        ...
    }
    

Ahora, deberías implementar una subclase de AsyncTask como una clase interna privada dentro de tu 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) {
        }
    }
    

Cómo usar HttpsUrlConnection para obtener datos

En el fragmento anterior, el método doInBackground() se ejecuta en un subproceso en segundo plano e invoca al método de ayuda downloadUrl(). El método downloadUrl() debería tomar la URL en cuestión y usarla para realizar una solicitud HTTP GET. Una vez establecida la conexión, debes usar el método getInputStream() para recuperar los datos como InputStream. En el siguiente fragmento, se usa la API de HttpsURLConnection para lograr esto:

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;
    }
    

Ten en cuenta que el método getResponseCode() muestra el código de estado de la conexión. Es una manera útil de obtener información adicional sobre la conexión. Un código de estado de 200 es correcto.

Para obtener más información sobre HttpsURLConnection, consulta el ejemplo de Android NetworkConnect.

Cómo convertir InputStream en una string

Un InputStream es una fuente legible de bytes. Cuando se obtiene un elemento InputStream, es común decodificarlo o convertirlo en un tipo de datos de destino. Por ejemplo, si descargaras datos de imágenes, podrías decodificarlos y mostrarlos de la siguiente manera:

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);
    

En el ejemplo anterior, InputStream representa el texto del cuerpo de la respuesta. Sigue estos pasos para convertir el InputStream en una string que la actividad pueda mostrar en la IU:

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();
    }
    

Hasta el momento, la secuencia de eventos del código es la siguiente:

  1. La actividad inicia un NetworkFragment y transfiere una URL especificada.
  2. Cuando una acción del usuario activa el método downloadData() de la actividad, NetworkFragment ejecuta DownloadTask.
  3. El método onPreExecute() de AsyncTask se ejecuta primero (en el subproceso de IU) y cancela la tarea si el dispositivo no está conectado a Internet.
  4. Luego, el método doInBackground() de AsyncTask se ejecuta en un subproceso en segundo plano y llama al método downloadUrl().
  5. El métododownloadUrl() toma una string de URL como parámetro y usa un objeto HttpsURLConnection para obtener el contenido web como InputStream.
  6. InputStream se transfiere al método readStream(), que convierte la transmisión en una string.
  7. Finalmente, cuando se completa el trabajo en segundo plano, el método onPostExecute() de AsyncTask se ejecuta en el subproceso de IU y usa DownloadCallback para enviar el resultado de vuelta a la IU como una string.

Conserva la actividad después de hacer cambios en la configuración

Hasta ahora, implementaste de forma correcta una actividad que lleva a cabo una operación de red. Pero, si el usuario decide cambiar la configuración del dispositivo (es decir, girar la pantalla 90 grados) mientras doInBackground() se ejecuta en el subproceso en segundo plano, la actividad se destruye y se recrea, por lo que vuelve a ejecutarse onCreate() y hace referencia a un NetworkFragment nuevo (consulta la Guía de cambios durante el tiempo de ejecución). Por lo tanto, el AsyncTask del NetworkFragment original tendrá un DownloadCallback que hará referencia al elemento Activity original que ya no puede actualizar la IU. De este modo, se habrá desperdiciado el trabajo en red realizado en un subproceso en segundo plano.

Para conservar la actividad después de estos cambios de configuración, debes conservar el fragmento original y asegurarte de que la Activity reconstruida haga referencia a él. Para lograrlo, debes hacer los siguientes cambios en el código:

Primero, NetworkFragment debería llamar a setRetainInstance(true) en su método onCreate():

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);
    }
    

Luego, modifica la manera en que se inicializa NetworkFragment en tu método estático getInstance():

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;
    }
    

Ahora tu app puede obtener correctamente datos desde Internet.

Ten en cuenta que hay una variedad de otras herramientas de administración de subprocesos en segundo plano que te pueden ayudar a lograr este mismo objetivo. A medida que tu app sea más compleja, es posible que estas otras herramientas sean más adecuadas para ella. En lugar de AsyncTask, las opciones que vale la pena investigar son IntentService y AsyncTaskLoader.

Para obtener más información sobre este tema, consulta las siguientes guías: