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" />

Diseña 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 desde tu app mediante 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.

Elige un cliente HTTP

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

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, NAPTRcode> y otros tipos de registro. Ten en cuenta que la app realizará el análisis de la respuesta.

Para obtener información sobre las apps basadas en NDK, consulta android_res_nsend.

Introduce operaciones de red en un subproceso separado

Para evitar crear una IU sin respuesta, te recomendamos no llevar a cabo operaciones de red en el subproceso de la IU. De manera predeterminada, Android 3.0 (API nivel 11) y las versiones posteriores requieren que lleves a cabo operaciones de red en un subproceso separado del de la IU principal; si no lo haces, se genera una NetworkOnMainThreadException.

En el siguiente fragmento de Activity, se usa un Fragment sin interfaz gráfica para encapsular operaciones de red asíncronas. Más adelante, verás cómo se logra esto con la implementación de Fragment NetworkFragment. Tu Activity también debería implementar la interfaz de DownloadCallback, lo que permite al fragmento devolver la llamada a la Activity en caso de que necesite 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 de 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();
        }
    }
    

Implementa un fragmento sin interfaz gráfica para encapsular operaciones de red

Debido a que NetworkFragment se ejecuta en el subproceso de la IU de manera predeterminada, utiliza una AsyncTask para llevar a cabo las operaciones de red en un subproceso en segundo plano. Se considera que el Fragment no tiene interfaz gráfica porque no hace referencia a ningún elemento de la IU. En cambio, solo se utiliza para encapsular lógica y controlar eventos de ciclo de vida, por lo que la Activity host se encarga de actualizar la IU.

Cuando uses una subclase de AsyncTask para llevar a cabo operaciones de red, debes tomar precauciones a fin de no crear una fuga de memoria en el caso en que se destruya la actividad a la que hace referencia la AsyncTask antes de que la AsyncTask termine su trabajo en segundo plano. Para asegurarte de que esto no ocurra, el siguiente fragmento borra las referencias 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, debes implementar una subclase de AsyncTask como clase interna privada dentro del 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) {
        }
    }
    

Usa HttpsUrlConnection para obtener datos

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

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.

Convierte InputStream en una string

Un InputStream es una fuente de bytes que se puede leer. Cuando se obtiene un InputStream, es común decodificarlo o convertirlo en un tipo de datos orientados. 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 que se muestra arriba, el InputStream representa el texto del cuerpo de la respuesta. Sigue estos pasos para convertir el InputStream en una string de modo que la actividad se 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 transmite una URL especificada.
  2. Cuando una acción del usuario activa el método downloadData() de la actividad, el NetworkFragment ejecuta la DownloadTask.
  3. El método AsyncTask onPreExecute() se ejecuta primero (en el subproceso de la IUI) y cancela la tarea si el dispositivo no está conectado a Internet.
  4. Luego, el método AsyncTask doInBackground() se ejecuta en el subproceso de segundo plano y llama al método downloadUrl().
  5. El método downloadUrl() toma una string de URL como parámetro y usa un objeto HttpsURLConnection para obtener el contenido web en forma de InputStream.
  6. Se transfiere el InputStream al método readStream(), que convierte la transmisión en una string.
  7. Finalmente, una vez que se completa el trabajo en segundo plano, el método onPostExecute() de AsyncTask se ejecuta en el subproceso de la IU y usa DownloadCallback para volver a enviar el resultado a la IU en forma de 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 (por ejemplo, rotar la pantalla 90 grados) mientras se ejecuta doInBackground() en el subproceso en segundo plano, la actividad se destruye y se recrea, lo cual hace que se vuelva a ejecutar onCreate() y se haga referencia a un nuevo NetworkFragment (consulta la Guía de cambios de tiempo de ejecución). Por lo tanto, la AsyncTask activa en el NetworkFragment original tendrá una DownloadCallback que haga referencia a la Activity original que ya no podrá actualizar la IU. De esta manera, se habrá desperdiciado el trabajo de red realizado en el 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, el 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 inicializas el NetworkFragment en el método getInstance() estático:

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 se vuelve más compleja, tal vez descubras que estas herramientas son más aptas para ella. En lugar de AsyncTask, puedes probar otras opciones, como IntentService y AsyncTaskLoader.

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