Menghubungkan ke jaringan

Untuk melakukan operasi jaringan di aplikasi Anda, manifes Anda harus menyertakan izin berikut:

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

Mendesain komunikasi jaringan yang aman

Sebelum menambahkan fungsionalitas jaringan ke aplikasi, Anda perlu memastikan bahwa data dan informasi dalam aplikasi Anda tetap aman saat ditransmisikan melalui jaringan. Untuk melakukannya, ikuti praktik terbaik keamanan jaringan berikut:

  • Minimalkan jumlah data pengguna sensitif atau pribadi yang Anda kirimkan melalui jaringan.
  • Kirim semua lalu lintas jaringan dari aplikasi Anda melalui SSL.
  • Pertimbangkan untuk membuat konfigurasi keamanan jaringan, yang memungkinkan aplikasi Anda memercayai CA khusus atau membatasi serangkaian CA sistem yang dipercaya untuk komunikasi yang aman.

Untuk informasi lebih lanjut tentang penerapan prinsip-prinsip jaringan aman, lihat tips keamanan jaringan Android. Anda juga dapat memeriksa contoh Android NetworkConnect.

Memilih klien HTTP

Sebagian besar aplikasi Android yang terhubung ke jaringan menggunakan HTTP untuk mengirim dan menerima data. Platform Android mencakup klien HttpsURLConnection, yang mendukung TLS, upload dan download streaming, timeout yang dapat dikonfigurasi, IPv6, dan kumpulan koneksi.

Memperkenalkan operasi jaringan pada thread terpisah

Untuk menghindari pembuatan UI yang tidak responsif, jangan lakukan operasi jaringan pada thread UI. Secara default, Android 3.0 (API level 11) dan yang lebih tinggi mengharuskan Anda untuk melakukan operasi jaringan pada thread selain thread UI utama; jika tidak, maka NetworkOnMainThreadException akan dibuang.

Cuplikan Activity berikut menggunakan headless Fragment untuk mengenkapsulasi operasi jaringan asinkron. Kemudian, Anda akan melihat bagaimana penerapan Fragment, NetworkFragment, menyelesaikan ini. Activity Anda juga harus menerapkan antarmuka DownloadCallback, yang memungkinkan fragmen untuk melakukan callback ke Activity jika perlu status konektivitas atau perlu mengirim pembaruan kembali ke UI.

Kotlin

class MainActivity : FragmentActivity(), DownloadCallback<String> {

    ...

    // Keep a reference to the NetworkFragment, which owns the AsyncTask object
    // that is used to execute network ops.
    private var networkFragment: NetworkFragment? = null

    // Boolean telling us whether a download is in progress, so we don't trigger overlapping
    // downloads with consecutive button clicks.
    private var downloading = false

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        networkFragment = NetworkFragment.getInstance(supportFragmentManager, "https://www.google.com")
    }

    private fun startDownload() {
        if (!downloading) {
            // Execute the async download.
            networkFragment?.apply {
                startDownload()
                downloading = true
            }
        }
    }
}

Java

public class MainActivity extends FragmentActivity implements DownloadCallback {

    ...

    // Keep a reference to the NetworkFragment, which owns the AsyncTask object
    // that is used to execute network ops.
    private NetworkFragment networkFragment;

    // Boolean telling us whether a download is in progress, so we don't trigger overlapping
    // downloads with consecutive button clicks.
    private boolean downloading = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        networkFragment = NetworkFragment.getInstance(getSupportFragmentManager(), "https://www.google.com");
    }

    private void startDownload() {
        if (!downloading && networkFragment != null) {
            // Execute the async download.
            networkFragment.startDownload();
            downloading = true;
        }
    }
}

Paling tidak, antarmuka DownloadCallback Anda dapat terdiri dari yang berikut:

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

Sekarang, tambahkan penerapan metode antarmuka DownloadCallback berikut ke ActivityAnda:

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

Menerapkan fragmen headless untuk mengenkapsulasi operasi jaringan

Karena NetworkFragment berjalan pada thread UI secara default, maka digunakan AsyncTask untuk menjalankan operasi jaringan pada thread latar belakang. Fragment ini dianggap headless karena tidak merujuk pada elemen UI apa pun. Fragmen ini hanya digunakan untuk mengenkapsulasi logika dan menangani peristiwa lifecycle, membiarkan Activity host untuk memperbarui UI.

Saat menggunakan subkelas AsyncTask untuk menjalankan operasi jaringan, Anda harus berhati-hati agar tidak membuat kebocoran memori dalam kasus ketika Activity yang dirujuk oleh AsyncTask dimusnahkan sebelum AsyncTask menyelesaikan pekerjaan latar belakangnya. Untuk memastikan hal ini tidak terjadi, cuplikan berikut menghapus referensi apa pun ke Activity dalam metode onDetach() Fragmen.

Kotlin

private const val TAG = "NetworkFragment"
private const val URL_KEY = "UrlKey"

class NetworkFragment : Fragment() {
    private var mCallback: 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.
        mCallback = context as? DownloadCallback<String>
    }

    override fun onDetach() {
        super.onDetach()
        // Clear reference to host Activity to avoid memory leak.
        mCallback = null
    }

    override fun onDestroy() {
        // Cancel task when Fragment is destroyed.
        cancelDownload()
        super.onDestroy()
    }

    /**
     * Start non-blocking execution of DownloadTask.
     */
    fun startDownload() {
        cancelDownload()
        mCallback?.also { callback ->
            downloadTask = DownloadTask(callback).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> mCallback;
    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.
        mCallback = (DownloadCallback<String>) context;
    }

    @Override
    public void onDetach() {
        super.onDetach();
        // Clear reference to host Activity to avoid memory leak.
        mCallback = 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(mCallback);
        downloadTask.execute(urlString);
    }

    /**
     * Cancel (and interrupt if necessary) any ongoing DownloadTask execution.
     */
    public void cancelDownload() {
        if (downloadTask != null) {
            downloadTask.cancel(true);
        }
    }

    ...
}

Sekarang, Anda harus menerapkan subkelas AsyncTask sebagai kelas pribadi di dalam FragmentAnda:

Kotlin

/**
 * Implementation of AsyncTask designed to fetch data from the network.
 */
private class DownloadTask(callback: DownloadCallback<String>)
    : AsyncTask<String, Int, DownloadTask.Result>() {

    private var mCallback: DownloadCallback<String>? = null

    init {
        setCallback(callback)
    }

    internal fun setCallback(callback: DownloadCallback<String>) {
        mCallback = 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 mResultValue: String? = null
        var mException: Exception? = null

        constructor(resultValue: String) {
            mResultValue = resultValue
        }

        constructor(exception: Exception) {
            mException = exception
        }
    }

    /**
     * Cancel background network operation if we do not have network connectivity.
     */
    override fun onPreExecute() {
        if (mCallback != null) {
            val networkInfo = mCallback?.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.
                mCallback?.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?) {
        mCallback?.apply {
            result?.mException?.also { exception ->
                updateFromDownload(exception.message)
                return
            }
            result?.mResultValue?.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> mCallback;

    DownloadTask(DownloadCallback<String> callback) {
        setCallback(callback);
    }

    void setCallback(DownloadCallback<String> callback) {
        mCallback = 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 mResultValue;
        public Exception mException;
        public Result(String resultValue) {
            mResultValue = resultValue;
        }
        public Result(Exception exception) {
            mException = exception;
        }
    }

    /**
     * Cancel background network operation if we do not have network connectivity.
     */
    @Override
    protected void onPreExecute() {
        if (mCallback != null) {
            NetworkInfo networkInfo = mCallback.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.
                mCallback.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 && mCallback != null) {
            if (result.mException != null) {
                mCallback.updateFromDownload(result.mException.getMessage());
            } else if (result.mResultValue != null) {
                mCallback.updateFromDownload(result.mResultValue);
            }
            mCallback.finishDownloading();
        }
    }

    /**
     * Override to add special behavior for cancelled AsyncTask.
     */
    @Override
    protected void onCancelled(Result result) {
    }
}

Gunakan HttpsUrlConnection untuk mengambil data

Dalam cuplikan di atas, metode doInBackground() berjalan di thread latar belakang dan memanggil downloadUrl() metode helper. Metode downloadUrl() harus mengambil URL itu dan menggunakannya untuk melakukan permintaan HTTP GET. Setelah koneksi dibuat, Anda harus menggunakan metode getInputStream() untuk mengambil data sebagai InputStream. Cuplikan berikut menggunakan HttpsURLConnection API untuk mencapai ini:

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

Perhatikan bahwa metode getResponseCode() mengembalikan kode status koneksi. Ini adalah cara yang berguna untuk mendapatkan informasi tambahan tentang koneksi. Kode status 200 menunjukkan keberhasilan.

Untuk melihat lebih tentang HttpsURLConnection, lihat Contoh Android NetworkConnect.

Mengonversi InputStream ke string

InputStream adalah sumber byte yang bisa dibaca. Setelah Anda mendapatkan InputStream, biasanya digunakan untuk mendekoding atau mengonversinya menjadi jenis data target. Misalnya, jika Anda mendownload data gambar, Anda dapat mendekoding dan menampilkannya seperti ini:

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

Dalam contoh yang ditunjukkan di atas, InputStream mewakili teks dari badan respons. Ini adalah cara Anda mengonversi InputStream menjadi string sehingga Activity tersebut dapat menampilkannya di UI:

Kotlin

/**
 * Converts the contents of an InputStream to a String.
 */
@Throws(IOException::class, UnsupportedEncodingException::class)
fun readStream(stream: InputStream, maxReadSize: Int): String? {
    val reader: Reader? = InputStreamReader(stream, "UTF-8")
    val rawBuffer = CharArray(maxReadSize)
    val buffer = StringBuffer()
    var readSize: Int = reader?.read(rawBuffer) ?: -1
    var maxReadBytes = maxReadSize
    while (readSize != -1 && maxReadBytes > 0) {
        if (readSize > maxReadBytes) {
            readSize = maxReadBytes
        }
        buffer.append(rawBuffer, 0, readSize)
        maxReadBytes -= readSize
        readSize = reader?.read(rawBuffer) ?: -1
    }
    return buffer.toString()
}

Java

/**
 * Converts the contents of an InputStream to a String.
 */
public String readStream(InputStream stream, int maxReadSize)
        throws IOException, UnsupportedEncodingException {
    Reader reader = null;
    reader = new InputStreamReader(stream, "UTF-8");
    char[] rawBuffer = new char[maxReadSize];
    int readSize;
    StringBuffer buffer = new StringBuffer();
    while (((readSize = reader.read(rawBuffer)) != -1) && maxReadSize > 0) {
        if (readSize > maxReadSize) {
            readSize = maxReadSize;
        }
        buffer.append(rawBuffer, 0, readSize);
        maxReadSize -= readSize;
    }
    return buffer.toString();
}

Urutan peristiwa dalam kode sejauh ini adalah sebagai berikut:

  1. Activity memulai NetworkFragment dan memberikan URL yang telah ditentukan.
  2. Ketika tindakan pengguna memicu metode downloadData() Activity, NetworkFragment mengeksekusi DownloadTask.
  3. Metode AsyncTask onPreExecute() berjalan terlebih dahulu (di thread UI) dan membatalkan tugas jika perangkat tidak terhubung ke Internet.
  4. Metode AsyncTask doInBackground() kemudian berjalan di thread latar belakang dan memanggil metode downloadUrl().
  5. Metode downloadUrl() mengambil string URL sebagai parameter dan menggunakan objek HttpsURLConnection untuk mengambil konten web sebagai InputStream.
  6. InputStream diteruskan ke metode readStream(), yang mengonversi streaming ke string.
  7. Terakhir, setelah pekerjaan latar belakang selesai, metode onPostExecute() AsyncTaskberjalan di thread UI dan menggunakan DownloadCallback untuk mengirimkan hasilnya kembali ke UI sebagai String.

Bertahan dari perubahan konfigurasi

Sejauh ini, Anda telah berhasil menerapkan Activity yang melakukan operasi jaringan. Tetapi, jika pengguna memutuskan untuk mengubah konfigurasi perangkat (yaitu memutar layar 90 derajat) ketika doInBackground() berjalan pada thread latar belakang, Activity ini menghancurkan dan membuat ulang sendiri, yang menyebabkannya menjalankan kembali onCreate() dan mereferensikan NetworkFragment baru (lihat Panduan Runtime Changes). Oleh karena itu, AsyncTask yang berada dalam NetworkFragment aslinya akan memiliki DownloadCallback yang mereferensikan pada Activity asli yang tidak lagi dapat memperbarui UI. Dengan demikian, pekerjaan jaringan yang dilakukan pada thread latar belakang akan sia-sia.

Untuk bertahan melalui perubahan konfigurasi ini, Anda perlu mempertahankan Fragmen asli Anda dan memastikan bahwa Activity yang direkonstruksi mereferensikannya. Untuk mewujudkan hal ini, Anda harus melakukan modifikasi berikut pada kode Anda:

Pertama, NetworkFragment Anda harus memanggil setRetainInstance(true) dalam metode 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);
}

Selanjutnya, ubah bagaimana Anda menginisialisasi NetworkFragment dalam metode statis getInstance() Anda:

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

Aplikasi Anda sekarang dapat berhasil menarik data dari Internet!

Perhatikan bahwa ada beberapa fitur manajemen latar belakang lain yang dapat membantu Anda mencapai tujuan yang sama ini. Saat kompleksitas aplikasi Anda tumbuh, Anda mungkin menemukan bahwa fitur-fitur lain ini lebih cocok untuk aplikasi Anda. Alih-alih AsyncTask, opsi yang perlu diinvestigasi adalah IntentService dan AsyncTaskLoader.

Untuk mempelajari lebih lanjut tentang topik ini, lihat panduan terkait berikut: