连接至网络

要在您的应用中执行网络操作,您的清单必须包含以下权限:

<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,以及连接池。

在单独线程上引入网络操作

为避免创建出响应迟钝的界面,请勿在界面线程上执行网络操作。默认情况下,Android 3.0(API 级别 11)及更高版本需要您在主界面线程以外的线程上执行网络操作,如您未按此要求操作,则会抛出 NetworkOnMainThreadException

以下 Activity 片段使用一个无头 Fragment 来封装异步网络操作。随后,您会看到 Fragment 的实现(即 NetworkFragment)如何完成这一步骤。您的 Activity 也应实现 DownloadCallback 接口,使该 Fragment 能够在需要连接状态或需要将更新发送回界面时回调至 Activity。

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

实施无头 Fragment 以封装网络操作

由于 NetworkFragment 默认在界面线程上运行,所以它使用 AsyncTask 在后台线程上执行网络操作。系统将此 Fragment 视为无头 Fragment,因为其并未引用任何界面元素。相反,它仅用于封装逻辑和处理生命周期活动,留下宿主 Activity 来更新界面。

使用 AsyncTask 子类执行网络操作时,如果在 AsyncTask 完成其后台工作前,AsyncTask 引用的 Activity 遭到销毁,则您必须留心切勿创建内存泄漏。为确保杜绝此类情况发生,以下片段在 Fragment 的 onDetach() 方法中清除了对 Activity 的所有引用。

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

    ...
}

现在,您应该实现 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 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) {
    }
}

使用 HttpsUrlConnection 获取数据

在以上片段中,doInBackground() 方法在后台线程上运行,并调用辅助方法 downloadUrl()downloadUrl() 方法应采用给定网址并用其执行 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 转化为字符串,以便 Activity 在界面中对其进行呈现的方法:

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. Activity 启动 NetworkFragment 并传入指定网址。
  2. 当用户操作触发 Activity 的 downloadData() 方法时,NetworkFragment 会执行 DownloadTask
  3. AsyncTask 方法 onPreExecute() 首先(在界面线程上)运行,如果设备未连接至互联网,其会取消任务。
  4. 然后,AsyncTask 方法 doInBackground() 在后台线程上运行,并调用 downloadUrl() 方法。
  5. downloadUrl() 方法将网址字符串作为参数,并使用 HttpsURLConnection 对象来获取网页内容作为 InputStream
  6. 系统将 InputStream 传递给 readStream() 方法,该方法会将信息流转化为字符串。
  7. 最终,后台工作完成后,AsyncTaskonPostExecute() 方法会在界面线程上运行,并使用 DownloadCallback 将结果以字符串形式发送回界面。

让 Activity 在配置变更后继续存在

截至目前,您已成功实现执行网络操作的 Activity。然而,如果用户决定于 doInBackground() 在后台线程上运行时更改设备配置(即将屏幕旋转 90 度),则 Activity 会进行自我销毁和重新创建,这会导致其重新执行 onCreate() 并引用新的 NetworkFragment(请参阅运行时变更指南)。因此,原始 NetworkFragment 中的 AsyncTask 将拥有一个 DownloadCallback,该方法引用的原始 Activity 无法更新界面。这样在后台线程上完成的网络工作将会白白浪费。

要让 Activity 在经过这些配置变更后继续存在,您需要保留原始 Fragment,并确保重新创建的 Activity 会引用该 Fragment。要完成此项操作,您应该对代码做出如下修改:

首先,您的 NetworkFragment 应在其 onCreate() 方法中调用 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

如需详细了解此主题,请参阅以下相关指南: