Google は、黒人コミュニティに対する人種平等の促進に取り組んでいます。取り組みを見る

ネットワークに接続する

ネットワークを介したデータ操作をアプリ内で行うには、マニフェストに以下のパーミッションを組み込む必要があります。

<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 を使用すると、SRVNAPTR など、さまざまなレコードタイプをルックアップできます。ただし、レスポンスの解析はアプリが行う必要があります。

NDK ベースのアプリについては、android_res_nsend をご覧ください。

独立したスレッド上でネットワークを介したデータ操作を実行する

応答しない UI の発生を避けるため、UI スレッド上ではネットワークを介したデータ操作を実行しないでください。Android 3.0(API レベル 11)以降の場合、デフォルトでは、メイン UI スレッド以外のスレッド上でネットワークを介したデータ操作を実行する必要があります。そうしないと、NetworkOnMainThreadException がスローされます。

ヘッドレス Fragment を使用して非同期ネットワーク操作をカプセル化する Activity のスニペットを以下に示します。後で、この処理を 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
                }
            }
        }
    }
    

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

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

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

ここで、次の 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()
    }
    

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

ヘッドレス フラグメントを実装してネットワークを介したデータ操作をカプセル化する

NetworkFragment は、デフォルトでは UI スレッド上で実行されるため、AsyncTask を使用して、バックグラウンド スレッド上でネットワークを介したデータ操作を実行するようにします。この Fragment は UI 要素を参照しないため、ヘッドレスと見なされます。その代わりに、ロジックをカプセル化してライフサイクル イベントを処理するためだけに使用され、UI のアップデートはホスト Activity に任せます。

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

        ...
    }
    

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

        ...
    }
    

ここで、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) {}
    }
    

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

HttpsUrlConnection を使用してデータを取得する

上記のスニペットにおいて、doInBackground() メソッドは、バックグラウンド スレッド内で実行され、downloadUrl() ヘルパー メソッドを呼び出します。downloadUrl() メソッドは、指定 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()
        }
    }
    

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

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

Java

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

上記の例で、InputStream はレスポンス ボディのテキストになります。次のスニペットでは、この InputStream をアクティビティが 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();
    }
    

これまでのコード内のイベント シーケンスは以下のとおりです。

  1. アクティビティが NetworkFragment を開始し、指定 URL を渡します。
  2. ユーザー アクションがアクティビティの downloadData() メソッドをトリガーすると、NetworkFragmentDownloadTask を実行します。
  3. まず、AsyncTaskonPreExecute() メソッドが UI スレッド上で実行され、デバイスがインターネットに接続されていない場合は、タスクをキャンセルします。
  4. 次に、AsyncTaskdoInBackground() メソッドがバックグラウンド スレッド上で実行され、downloadUrl() メソッドを呼び出します。
  5. downloadUrl() メソッドが URL 文字列をパラメータとして受け取り、HttpsURLConnection オブジェクトを使用して、ウェブ コンテンツを InputStream として取得します。
  6. InputStreamreadStream() メソッドに渡され、ストリームが文字列に変換されます。
  7. 最後に、バックグラウンド作業が完了すると、AsyncTaskonPostExecute() メソッドが UI スレッド上で実行され、DownloadCallback を使用して、結果を文字列として UI に返送します。

構成の変更に対処する

ここまでの作業により、ネットワークを介したデータ操作を実行するアクティビティを正常に実装することができました。しかし、doInBackground() がバックグラウンド スレッド上で実行されている最中に、ユーザーがデバイス構成を変更することにした場合(たとえば、画面を 90 度回転するなど)、アクティビティは自らを破棄して再作成し、onCreate() を再実行して、新しい NetworkFragment を参照するようになります(実行時の変更に関するガイドをご覧ください)。元の NetworkFragment 内に存在する AsyncTaskDownloadCallback は、破棄された元の Activity を参照しているため、これでは UI を更新できなくなります。つまり、バックグラウンド スレッド上で実行されるネットワーク処理が無駄になってしまいます。

このような構成の変更に対処するには、元のフラグメントを保持して、再作成された Activity がこのフラグメントを参照するように指定する必要があります。そのためには、以下のようにコードを変更します。

まず、NetworkFragmentonCreate() メソッド内で setRetainInstance(true) を呼び出すようにします。

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

次に、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
        }
    }
    

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

これで、アプリは、インターネットから正常にデータをプルできるようになりました。

なお、これと同じ目標を達成できるバックグラウンド スレッド管理ツールは、ほかにもいくつかあります。高度なアプリの場合は、このような他のツールの方が適していることもあります。たとえば、AsyncTask の代わりに、IntentServiceAsyncTaskLoader を使用する方法などがあります。

このトピックの詳細については、以下の関連ガイドをご覧ください。