核心思想
无论使用哪种方法,其核心流程都是相似的:
- 创建一个网络请求:指定要访问的 URL。
- 发送请求并获取响应:通过网络连接获取服务器返回的数据。
- 读取响应数据:将响应中的内容(即 HTML 源码)读取出来。
- 处理数据:在 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 的异步回调已经运行在非主线程,所以我们需要用 runOnUiThread 或 Handler 将结果切回主线程更新 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 并需要获取其当前状态的特殊场景。
