核心思想

无论使用哪种方法,其核心流程都是相似的:

  1. 创建一个网络请求:指定要访问的 URL。
  2. 发送请求并获取响应:通过网络连接获取服务器返回的数据。
  3. 读取响应数据:将响应中的内容(即 HTML 源码)读取出来。
  4. 处理数据:在 UI 线程上显示或处理这些数据。

使用 HttpURLConnection (标准 Java API)

这是最基础、最传统的方法,不需要引入任何第三方库,它适用于简单的 HTTP 请求。

优点

  • 无需额外依赖,是 Android SDK 自带的。
  • 对于简单的 GET/POST 请求足够用。

缺点

  • API 相对繁琐,需要手动处理线程、输入流、连接关闭等。
  • 不支持现代的 HTTP/2 协议。

完整示例代码

添加网络权限AndroidManifest.xml 中必须声明网络权限,否则应用无法访问网络。

<manifest ...>
    <!-- 添加这一行 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <application ...>
        ...
    </application>
</manifest>

在后台线程中执行网络请求 网络操作不能在主线程(UI 线程)中进行,否则会抛出 NetworkOnMainThreadException 异常,我们可以使用 AsyncTask(已废弃,但适合演示)、Thread + Handler 或现代的 Kotlin Coroutines / Java ExecutorService

这里我们使用 ExecutorService 来演示。

代码实现

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private static final String TARGET_URL = "https://www.baidu.com"; // 替换成你想抓取的网址
    private TextView textView;
    private final ExecutorService executor = Executors.newSingleThreadExecutor();
    private final Handler handler = new Handler(Looper.getMainLooper());
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.textView);
        Button fetchButton = findViewById(R.id.fetchButton);
        fetchButton.setOnClickListener(v -> {
            // 在后台线程中执行网络请求
            executor.execute(() -> {
                String htmlSource = fetchHtmlSource(TARGET_URL);
                // 在主线程中更新 UI
                handler.post(() -> textView.setText(htmlSource));
            });
        });
    }
    private String fetchHtmlSource(String urlString) {
        HttpURLConnection urlConnection = null;
        BufferedReader reader = null;
        StringBuilder response = new StringBuilder();
        try {
            URL url = new URL(urlString);
            urlConnection = (HttpURLConnection) url.openConnection();
            urlConnection.setRequestMethod("GET");
            urlConnection.setConnectTimeout(15000); // 15秒连接超时
            urlConnection.setReadTimeout(15000);    // 15秒读取超时
            urlConnection.connect();
            int responseCode = urlConnection.getResponseCode();
            if (responseCode == HttpURLConnection.HTTP_OK) {
                InputStream inputStream = urlConnection.getInputStream();
                if (inputStream == null) {
                    return null;
                }
                reader = new BufferedReader(new InputStreamReader(inputStream));
                String line;
                while ((line = reader.readLine()) != null) {
                    response.append(line);
                    response.append("\n"); // 保留换行符,方便查看
                }
            } else {
                Log.e(TAG, "Error response code: " + responseCode);
            }
        } catch (IOException e) {
            Log.e(TAG, "IOException: " + e.getMessage(), e);
        } finally {
            // 确保在 finally 块中关闭资源
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    Log.e(TAG, "Error closing stream", e);
                }
            }
        }
        return response.toString();
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 关闭线程池
        executor.shutdown();
    }
}

使用 OkHttp (强烈推荐)

OkHttp 是目前 Android 开发中最流行的网络请求库,它高效、易用,并且支持同步/异步请求、HTTP/2、SPDY 等。

优点

  • 性能卓越:支持 HTTP/2 和连接池,能有效减少延迟。
  • API 简洁:比 HttpURLConnection 简单易用得多。
  • 功能强大:支持异步回调、拦截器、文件上传下载等。
  • 社区活跃:是 Square 公司维护的开源项目,文档完善。

缺点

  • 需要添加第三方库依赖。

完整示例代码

添加依赖app/build.gradle 文件的 dependencies 代码块中添加 OkHttp 依赖。

dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.12.0") // 使用最新版本
}

同步你的项目。

代码实现 OkHttp 的异步回调已经运行在非主线程,所以我们需要用 runOnUiThreadHandler 将结果切回主线程更新 UI。

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class MainActivityOkHttp extends AppCompatActivity {
    private static final String TAG = "MainActivityOkHttp";
    private static final String TARGET_URL = "https://www.baidu.com";
    private TextView textView;
    private final OkHttpClient client = new OkHttpClient();
    private final Handler handler = new Handler(Looper.getMainLooper());
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); // 假设布局和之前一样
        textView = findViewById(R.id.textView);
        Button fetchButton = findViewById(R.id.fetchButton);
        fetchButton.setOnClickListener(v -> fetchHtmlWithOkHttp());
    }
    private void fetchHtmlWithOkHttp() {
        Request request = new Request.Builder()
                .url(TARGET_URL)
                .build();
        client.newCall(request).enqueue(new okhttp3.Callback() {
            @Override
            public void onFailure(okhttp3.Call call, IOException e) {
                // 请求失败,在主线程更新 UI
                handler.post(() -> textView.setText("请求失败: " + e.getMessage()));
            }
            @Override
            public void onResponse(okhttp3.Call call, Response response) throws IOException {
                // 请求成功
                if (response.isSuccessful()) {
                    final String htmlSource = response.body().string();
                    // 在主线程更新 UI
                    handler.post(() -> textView.setText(htmlSource));
                } else {
                    // 服务器返回错误码
                    handler.post(() -> textView.setText("服务器错误: " + response.code()));
                }
            }
        });
    }
}

注意response.body().string() 只能被调用一次,多次调用会抛出异常。


使用 WebView (特定场景)

如果你的应用已经嵌入了 WebView 组件,并且想获取当前 WebView 页面的源码,可以直接调用 WebView 的方法。

适用场景

  • 应用内嵌浏览器。
  • 需要获取由 JavaScript 动态加载内容后的“HTML 源码。

完整示例代码

布局文件 activity_main.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />
    <Button
        android:id="@+id/fetchButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="获取当前页面源码" />
    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:maxLines="10" />
</LinearLayout>

Activity 代码

import android.os.Bundle;
import android.util.Log;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivityWebView extends AppCompatActivity {
    private static final String TAG = "MainActivityWebView";
    private WebView webView;
    private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main_webview); // 使用上面的布局
        webView = findViewById(R.id.webView);
        textView = findViewById(R.id.textView);
        Button fetchButton = findViewById(R.id.fetchButton);
        // 必须设置 WebViewClient,否则会在外部浏览器打开
        webView.setWebViewClient(new WebViewClient());
        // 启用 JavaScript (如果页面需要)
        webView.getSettings().setJavaScriptEnabled(true);
        // 加载一个网页
        webView.loadUrl("https://www.baidu.com");
        fetchButton.setOnClickListener(v -> {
            // 获取 WebView 当前页面的 HTML 源码
            String htmlSource = webView.getOriginalUrl() != null ? webView.getOriginalUrl() + "\n\n" : "";
            htmlSource += webView.getContentDescription(); // 这方法通常拿不到完整HTML
            // 更可靠的方法是使用 evaluateJavascript
            // 但注意,这获取的是渲染后的DOM,可能与原始源码不同
            // 这里为了简单,直接使用一个已知可以工作的方法(如果目标页面允许)
            // 直接获取源码比较困难,通常是通过注入JS来实现
            // 这里我们简化处理,直接显示URL和提示
            textView.setText("当前页面URL: " + webView.getUrl() + "\n\n" +
                             "注意:直接获取WebView渲染后的完整HTML源码比较复杂。" +
                             "通常需要通过JavaScript注入来实现。");
        });
    }
}

WebView 的补充说明: 直接通过 Android API 获取 WebView 当前渲染后的完整 HTML 源码是比较困难的,通常的做法是向 WebView 中注入 JavaScript 代码,让 JS 来获取 document.documentElement.outerHTML,然后再将结果返回给 Java 代码,这比前两种方法要复杂得多,仅在特定需求下使用。


总结与对比

特性 HttpURLConnection OkHttp WebView
依赖 无需 (Android SDK 内置) 需添加 okhttp3 依赖 无需 (Android SDK 内置)
易用性 较繁琐,代码量大 非常简单,API 友好 简单,但特定功能复杂
性能 一般,不支持 HTTP/2 优秀,支持 HTTP/2, 连接池 依赖 WebView 引擎性能
主要用途 简单的、一次性的网络请求 绝大多数 Android 应用的网络请求 显示网页,与网页交互
获取源码 获取服务器原始返回的 HTML 获取服务器原始返回的 HTML 获取 WebView 当前渲染后的 DOM (复杂)

推荐方案

对于绝大多数情况,强烈推荐使用 OkHttp,它是行业标准,性能好,开发效率高,只有在项目有特殊限制(如不能引入第三方库)且需求非常简单时,才考虑使用 HttpURLConnection,而 WebView 的方式则仅适用于应用内已存在 WebView 并需要获取其当前状态的特殊场景。