네트워크에 연결

애플리케이션에서 네트워크 작업을 하려면 매니페스트에 다음과 같은 권한을 포함해야 합니다.

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

안전한 네트워크 통신 설계

앱에 네트워킹 기능을 추가하기 전에 앱의 데이터와 정보가 네트워크를 통해 전송될 때 안전하게 보호되는지 확인해야 합니다. 데이터와 정보를 안전하게 유지하려면 다음 네트워킹 보안 권장사항을 따르세요.

  • 네트워크를 통해 전송하는 민감하거나 개인적인 사용자 데이터의 양을 최소화합니다.
  • 앱의 모든 네트워크 트래픽은 SSL을 통해 보냅니다.
  • 앱에서 안전한 통신을 위해 맞춤 CA를 신뢰하거나 신뢰하는 시스템 CA 세트를 제한할 수 있는 네트워크 보안 구성을 만들어봅니다.

안전한 네트워킹 원칙 적용에 관한 자세한 내용은 Android의 네트워킹 보안 팁을 참조하세요. Android NetworkConnect 샘플을 확인할 수도 있습니다.

HTTP 클라이언트 선택

대부분의 네트워크 연결 Android 앱에서는 HTTP를 사용하여 데이터를 보내고 받습니다. Android 플랫폼에는 HttpsURLConnection 클라이언트가 포함되며 이 클라이언트는 TLS, 스트리밍 업로드 및 다운로드, 구성 가능한 시간 제한, IPv6, 연결 풀링을 지원합니다.

DNS 조회

Android 9 이하를 지원하는 기기에서는 플랫폼 DNS 리졸버가 A 및 AAAA 레코드만 지원하여 이름과 연결된 IP 주소 조회만 가능하고, 다른 레코드 유형은 지원하지 않습니다.

Android 10 이상을 실행하는 기기에서는 일반 텍스트 조회와 DNS-over-TLS 모드를 모두 사용하는 특수 DNS 조회를 기본적으로 지원합니다. DnsResolver API는 일반 비동기 해상도를 제공하므로 SRV, NAPTR 및 기타 레코드 유형을 조회할 수 있습니다. 응답의 파싱은 앱에서 처리하도록 남겨두세요.

NDK 기반 앱의 경우 android_res_nsend를 참조하세요.

별도의 스레드에 네트워크 작업 도입

응답하지 않는 UI를 만들지 않으려면 UI 스레드에서 네트워크 작업을 실행하지 마세요. 기본적으로 Android 3.0(API 수준 11) 이상에서는 기본 UI 스레드를 제외한 스레드에서 네트워크 작업을 실행해야 합니다. 그러지 않으면 NetworkOnMainThreadException이 발생합니다.

다음 Activity 스니펫에서는 헤드리스 Fragment를 사용하여 비동기 네트워크 작업을 캡슐화합니다. 나중에 Fragment 구현인 NetworkFragment가 캡슐화하는 방법을 확인할 수 있습니다. 또한 활동에서 DownloadCallback 인터페이스를 구현하여 프래그먼트에서 연결 상태가 필요하거나 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
                }
            }
        }
    }
    

자바

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

DownloadCallback 인터페이스는 최소한 다음과 같이 구성할 수 있습니다.

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

자바

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

이제 DownloadCallback 인터페이스 메서드의 다음과 같은 구현을 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()
    }
    

자바

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

헤드리스 프래그먼트를 구현하여 네트워크 작업 캡슐화

NetworkFragment는 기본적으로 UI 스레드에서 실행되므로 AsyncTask를 사용하여 백그라운드 스레드에서 네트워크 작업을 실행합니다. 이 Fragment는 UI 요소를 참조하지 않으므로 헤드리스로 간주됩니다. 대신 논리 및 핸들 수명 주기 이벤트를 캡슐화할 때만 사용하여 호스트 Activity가 UI를 업데이트하도록 합니다.

AsyncTask의 서브클래스를 사용하여 네트워크 작업을 실행할 때는 AsyncTask가 백그라운드 작업을 완료하기 전에 AsyncTask에서 참조한 활동이 제거되는 경우 메모리 누수가 발생하지 않도록 주의해야 합니다. 이런 일이 발생하지 않도록 다음 스니펫은 프래그먼트의 onDetach() 메서드에서 활동 참조를 제거합니다.

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

        ...
    }
    

자바

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

        ...
    }
    

이제 AsyncTask의 서브클래스를 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) {}
    }
    

자바

    /**
     * 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) {
        }
    }
    

HttpsUrlConnection을 사용하여 데이터 가져오기

위의 스니펫에서 doInBackground() 메서드는 백그라운드 스레드에서 실행되고 도우미 메서드인 downloadUrl()을 호출합니다. downloadUrl() 메서드는 지정된 URL을 가져와 이 URL로 HTTP GET 요청을 실행합니다. 연결이 설정되면 getInputStream() 메서드를 사용하여 데이터를 InputStream으로 검색해야 합니다. 다음 스니펫에서는 HttpsURLConnection API를 사용하여 이 작업을 실행합니다.

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

자바

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

getResponseCode() 메서드는 연결의 상태 코드를 반환합니다. 이 방법은 연결에 관한 추가 정보를 가져오는 데 유용합니다. 상태 코드 200은 성공을 나타냅니다.

HttpsURLConnection을 더 자세히 살펴보고 싶다면 Android NetworkConnect 샘플을 참조하세요.

InputStream을 문자열로 변환

InputStream은 읽을 수 있는 바이트 소스입니다. InputStream을 가져온 후에는 디코딩하거나 대상 데이터 유형으로 변환하는 것이 일반적입니다. 예를 들어 이미지 데이터를 다운로드하는 경우 디코딩하여 다음과 같이 표시할 수도 있습니다.

Kotlin

    val inputStream: InputStream? = null
    ...
    val bitmap: Bitmap = BitmapFactory.decodeStream(inputStream)
    findViewById<ImageView>(R.id.image_view)?.apply {
        setImageBitmap(bitmap)
    }
    

자바

    InputStream inputStream = null;
    ...
    Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
    ImageView imageView = (ImageView) findViewById(R.id.image_view);
    imageView.setImageBitmap(bitmap);
    

위에 표시된 예에서 InputStream은 응답 본문의 텍스트를 나타냅니다. 다음은 활동에서 UI에 표시할 수 있도록 InputStream을 문자열로 변환하는 방법입니다.

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

자바

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

지금까지 코드에서 이벤트의 순서는 다음과 같습니다.

  1. 활동이 NetworkFragment를 시작하고 지정된 URL을 전달합니다.
  2. 사용자 작업이 활동의 downloadData() 메서드를 트리거하면 NetworkFragmentDownloadTask를 실행합니다.
  3. AsyncTask 메서드 onPreExecute()가 UI 스레드에서 먼저 실행되고, 기기가 인터넷에 연결되어 있지 않으면 작업을 취소합니다.
  4. AsyncTask 메서드 doInBackground()가 백그라운드 스레드에서 실행되고 downloadUrl() 메서드를 호출합니다.
  5. downloadUrl() 메서드는 URL 문자열을 매개변수로 받고 HttpsURLConnection 객체를 사용하여 웹 콘텐츠를 InputStream으로 가져옵니다.
  6. InputStreamreadStream() 메서드로 전달되고 이 메서드는 스트림을 문자열로 변환합니다.
  7. 마지막으로 백그라운드 작업이 완료되면 AsyncTaskonPostExecute() 메서드가 UI 스레드에서 실행되고 DownloadCallback을 사용하여 결과를 다시 문자열로 UI에 보냅니다.

구성 변경사항 유지

지금까지 네트워크 작업을 실행하는 활동을 구현했습니다. 하지만 doInBackground()가 백그라운드 스레드에서 실행되는 동안 사용자가 기기 설정을 변경(예: 화면을 90도 회전)하면 활동이 스스로 제거되었다가 다시 만들어지면서 onCreate()를 재실행하고 새 NetworkFragment를 참조합니다(런타임 변경사항 가이드 참조). 그러므로 원본 NetworkFragment에 있는 AsyncTask는 UI를 더 이상 업데이트할 수 없는 원본 Activity를 참조하는 DownloadCallback을 포함하게 됩니다. 따라서 백그라운드 스레드에서 실행된 네트워크 작업이 낭비됩니다.

이러한 구성 변경사항이 있어도 작업을 지속하려면 원본 프래그먼트를 그대로 두고 다시 구성된 Activity가 원본 프래그먼트를 참조하도록 해야 합니다. 이렇게 하려면 코드를 다음과 같이 수정하세요.

먼저, NetworkFragmentonCreate() 메서드에서 setRetainInstance(true)를 호출해야 합니다.

Kotlin

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Retain this Fragment across configuration changes in the host Activity.
        retainInstance = true
    }
    

자바

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        ...
        // Retain this Fragment across configuration changes in the host Activity.
        setRetainInstance(true);
    }
    

그런 다음, 정적 getInstance() 메서드에서 NetworkFragment를 초기화하는 방법을 변경합니다.

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

자바

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

이제 앱이 인터넷에서 데이터를 가져올 수 있습니다.

이 목표를 달성하는 데 도움이 되는 다양한 백그라운드 스레드 관리 도구가 있습니다. 앱이 더 복잡해지면 이러한 다른 도구가 앱에 더 적합하게 될 수도 있습니다. AsyncTask 대신 살펴볼 만한 옵션은 IntentServiceAsyncTaskLoader입니다.

이 주제에 관한 자세한 내용은 다음 관련 가이드를 참조하세요.