Conectar à rede

Para realizar operações de rede no seu aplicativo, o manifesto precisa incluir as seguintes permissões:

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

Criar uma comunicação de rede segura

Antes de adicionar a funcionalidade de rede ao seu aplicativo, você precisa garantir que os dados e as informações dele permaneçam seguros quando forem transmitidos pela rede. Para fazer isso, siga estas práticas recomendadas de segurança de rede:

Para mais informações sobre como aplicar princípios de rede segura, consulte as dicas de segurança na rede do Android. Confira também o exemplo do Android NetworkConnect (link em inglês).

Escolher um cliente HTTP

A maioria dos apps para Android conectados à rede usa HTTP para enviar e receber dados. A plataforma Android inclui o cliente HttpsURLConnection, que é compatível com TLS, uploads e downloads de streaming, tempos limite configuráveis, IPv6 e pool de conexão.

Busca DNS

Em dispositivos com Android 9 e versões anteriores, o resolvedor de DNS da plataforma é compatível apenas com registros A e AAAA, que permitem a busca de endereços IP associados a um nome, mas não é compatível com nenhum outro tipo de registro.

Em dispositivos com o Android 10 e versões posteriores, há compatibilidade nativa com buscas DNS especializadas usando pesquisas de texto sem criptografia e o modo DNS sobre TLS. A API DnsResolver oferece resolução assíncrona e genérica, permitindo busca de SRV, NAPTRcode> e outros tipos de registro. Observe que a análise da resposta é deixada para execução pelo app.

Para apps baseados em NDK, consulte android_res_nsend.

Incluir operações de rede em uma linha de execução separada

Para evitar a criação de uma IU não responsiva, não execute operações de rede na linha de execução da interface. Por padrão, o Android 3.0 (API de nível 11) e versões posteriores requerem a execução de operações de rede em uma linha de execução diferente daquela da IU principal. Se você não fizer isso, NetworkOnMainThreadException será acionado.

O snippet de Activity a seguir usa um Fragment headless para encapsular operações de rede assíncronas. Mais adiante, você verá como a implementação de Fragment, NetworkFragment, faz isso. Sua atividade também precisa implementar a interface DownloadCallback, permitindo que o Fragment faça um callback para a Activity, caso ela precise receber um status de conectividade ou enviar uma atualização de volta à 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;
            }
        }
    }
    

No mínimo, sua interface DownloadCallback pode consistir no seguinte:

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

Agora, adicione as seguintes implementações dos métodos de interface DownloadCallback à sua 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();
        }
    }
    

Implementar um Fragment headless para encapsular operações de rede

Como o NetworkFragment é executado na linha de execução da IU por padrão, ele usa AsyncTask para executar as operações de rede em uma linha de execução de segundo plano. Esse Fragment é considerado headless porque não faz referência a nenhum elemento da IU. Em vez disso, ele é usado apenas para encapsular a lógica e processar eventos do ciclo de vida, deixando a Activity do host para atualizar a IU.

Ao usar uma subclasse de AsyncTask para executar operações de rede, tome cuidado para não criar um vazamento de memória, caso a Activity que é referenciada pela AsyncTask seja interrompida antes de AsyncTask terminar o trabalho em segundo plano. Para garantir que isso não aconteça, o snippet a seguir limpa todas as referências à Activity no método onDetach() do Fragment.

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

        ...
    }
    

Agora, implemente uma subclasse de AsyncTask como uma classe interna privada no seu 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) {
        }
    }
    

Usar HttpsUrlConnection para buscar dados

No snippet acima, o método doInBackground() é executado em uma linha de execução de segundo plano e chama o método auxiliar downloadUrl() O método downloadUrl() precisa pegar o URL fornecido e usá-lo para executar uma solicitação HTTP GET. Depois que uma conexão for estabelecida, você precisará usar o método getInputStream() para recuperar os dados como um InputStream. O snippet a seguir usa a API HttpsURLConnection para fazer isso:

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

Observe que o método getResponseCode() retorna o código de status da conexão. Essa é uma maneira útil de conseguir informações adicionais sobre a conexão. Um código de status 200 indica que a ação foi concluída.

Para ver mais detalhes sobre HttpsURLConnection, consulte o exemplo do Android NetworkConnect (link em inglês).

Converter InputStream em uma string

Um InputStream é uma fonte legível de bytes. Ao receber um InputStream, é comum decodificá-lo ou convertê-lo em um tipo de dados de destino. Por exemplo, se você fizesse o download de dados da imagem, poderia decodificá-los e exibi-los assim:

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

No exemplo mostrado acima, InputStream representa o texto do corpo da resposta. É assim que você precisa converter InputStream em uma string para que a Activity possa exibi-la na 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();
    }
    

A sequência de eventos no código até o momento é a seguinte:

  1. A Activity inicia um NetworkFragment e o transmite em um URL especificado.
  2. Quando um usuário aciona o método downloadData() da Activity, o NetworkFragment executa DownloadTask.
  3. O método AsyncTask onPreExecute() é executado primeiro (na linha de execução da IU) e cancela a tarefa se o dispositivo não está conectado à Internet.
  4. Depois disso, o método AsyncTask doInBackground() é executado na linha de execução de segundo plano e chama o método downloadUrl().
  5. O método downloadUrl() usa uma string de URL como parâmetro e usa um objeto HttpsURLConnection para buscar o conteúdo da Web como um InputStream.
  6. O InputStream é transmitido ao método readStream(), que converte o stream em uma string.
  7. Por último, quando o trabalho em segundo fundo é concluído, o método onPostExecute() de AsyncTask é executado na linha de execução da IU e usa DownloadCallback para enviar o resultado de volta à IU como uma string.

Sobreviver às alterações de configuração

Até o momento, você implementou uma Activity que executa uma operação de rede sem erros. Porém, se o usuário decidir alterar a configuração do dispositivo (por exemplo, girar a tela em 90 graus) enquanto doInBackground() estiver sendo executado na linha de execução de segundo plano, a Activity se destruirá e se recriará. Isso fará com que onCreate() seja gerado novamente e um novo NetworkFragment seja referenciado (consulte o guia de alterações durante o tempo de execução). Portanto, a AsyncTask ativa no NetworkFragment original terá uma DownloadCallback que referencia a Activity original que não poderá mais atualizar a IU. Dessa forma, o trabalho de rede feito na linha de execução de segundo plano terá sido desperdiçado.

Para impedir falhas depois dessas alterações de configuração, você precisa manter seu Fragment original e garantir que a Activity reconstruída o referencie. Para fazer isso, faça as seguintes modificações no seu código:

Primeiro, seu NetworkFragment precisa chamar setRetainInstance(true) no 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);
    }
    

Em seguida, mude a forma como você inicializa o NetworkFragment no 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;
    }
    

Agora seu app pode recuperar dados da Internet.

Existem várias outras ferramentas de gerenciamento de linhas de execução em segundo plano que podem ajudar você a atingir esse mesmo objetivo. À medida que seu aplicativo fica mais complexo, talvez você ache essas outras ferramentas mais adequadas. Em vez de usar AsyncTask, conheça em detalhes as opções IntentService e AsyncTaskLoader.

Para saber mais sobre este tópico, consulte estes guias relacionados: