核心概念

无论使用哪种方法,基本流程都是一样的:

android中下载网页源码下载
(图片来源网络,侵删)
  1. 添加网络权限:在 AndroidManifest.xml 中声明应用需要访问互联网。
  2. 发起网络请求:使用指定的 URL 向服务器发起一个 HTTP/HTTPS 请求。
  3. 获取响应:从服务器接收返回的数据,也就是网页的 HTML 源码。
  4. 处理数据:在主线程(UI 线程)之外处理网络数据,然后更新 UI。

使用 OkHttp (强烈推荐)

OkHttp 是目前 Android 开发中最流行、最高效的网络库,由 Square 公司开发,性能优异,支持异步请求,并且对现代网络协议(如 HTTP/2)有很好的支持。

步骤 1: 添加依赖

在项目的 app/build.gradle 文件中的 dependencies 代码块里添加 OkHttp 依赖:

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

步骤 2: 添加网络权限

app/src/main/AndroidManifest.xml 文件中添加 <uses-permission>

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <!-- 添加网络权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <application
        ...
        android:usesCleartextTraffic="true" <!-- 如果请求HTTP网站,需要添加此行 -->
        ...>
        ...
    </application>
</manifest>

注意:如果你的应用需要访问 http:// 协议的网站(不推荐,不安全),必须在 <application> 标签中添加 android:usesCleartextTraffic="true",从 Android 9 (API 28) 开始,默认禁止明文流量。

步骤 3: 编写下载代码

在 Activity 或 ViewModel 中,使用 OkHttp 发起异步请求。

android中下载网页源码下载
(图片来源网络,侵删)
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import okhttp3.*
import java.io.IOException
class MainActivity : AppCompatActivity() {
    private val client = OkHttpClient()
    private val sourceCodeTextView: TextView by lazy { findViewById(R.id.sourceCodeTextView) }
    private val downloadButton: Button by lazy { findViewById(R.id.downloadButton) }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        downloadButton.setOnClickListener {
            // 示例:下载 "https://www.example.com" 的源码
            downloadWebSourceCode("https://www.example.com")
        }
    }
    private fun downloadWebSourceCode(url: String) {
        // 创建一个请求对象
        val request = Request.Builder()
            .url(url)
            .build()
        // 异步执行请求
        client.newCall(request).enqueue(object : Callback {
            // 请求失败时调用
            override fun onFailure(call: Call, e: IOException) {
                // 网络错误或请求失败
                runOnUiThread {
                    Toast.makeText(this@MainActivity, "下载失败: ${e.message}", Toast.LENGTH_SHORT).show()
                }
            }
            // 请求成功时调用
            override fun onResponse(call: Call, response: Response) {
                // response.body() 是响应体,即网页的源码
                if (response.isSuccessful) {
                    val sourceCode = response.body?.string()
                    // 更新UI,必须在主线程执行
                    runOnUiThread {
                        sourceCodeTextView.text = sourceCode
                    }
                } else {
                    // 服务器返回了错误状态码,如 404, 500 等
                    runOnUiThread {
                        Toast.makeText(this@MainActivity, "下载失败: HTTP ${response.code}", Toast.LENGTH_SHORT).show()
                    }
                }
            }
        })
    }
    override fun onDestroy() {
        super.onDestroy()
        // 在Activity销毁时取消所有请求,防止内存泄漏
        client.dispatcher.cancelAll()
    }
}

对应的布局文件 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">
    <Button
        android:id="@+id/downloadButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="下载网页源码" />
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="16dp">
        <TextView
            android:id="@+id/sourceCodeTextView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="点击按钮开始下载..."
            android:textSize="12sp"
            android:fontFamily="monospace" />
    </ScrollView>
</LinearLayout>

使用 Retrofit (更高级的推荐)

Retrofit 是一个类型安全的 HTTP 客户端,它基于 OkHttp,但提供了更高级的抽象,比如将 API 请求定义成接口,并支持自动解析 JSON、XML 等数据格式,虽然对于单纯获取 HTML 来说有点“杀鸡用牛刀”,但如果你需要和复杂的 API 交互,Retrofit 是不二之选。

步骤 1: 添加依赖

app/build.gradle 中添加 Retrofit 和 OkHttp 的依赖:

dependencies {
    // Retrofit
    implementation("com.squareup.retrofit2:retrofit:2.9.0") // 请使用最新版本
    // 用于接收原始字符串响应
    implementation("com.squareup.retrofit2:converter-scalars:2.9.0")
}

步骤 2: 定义 API 接口

创建一个接口来描述你的网络请求。

android中下载网页源码下载
(图片来源网络,侵删)
import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.Url
interface WebService {
    // @GET 注解表示这是一个 GET 请求
    // @Url 表示 URL 是动态传入的
    // Call<String> 表示我们期望将响应体解析成 String
    @GET
    fun fetchWebSource(@Url url: String): Call<String>
}

步骤 3: 创建 Retrofit 实例并发起请求

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.scalars.ScalarsConverterFactory
class RetrofitActivity : AppCompatActivity() {
    private lateinit var retrofit: Retrofit
    private lateinit webService: WebService
    private val sourceCodeTextView: TextView by lazy { findViewById(R.id.sourceCodeTextView) }
    private val downloadButton: Button by lazy { findViewById(R.id.downloadButton) }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main) // 复用上面的布局
        // 初始化 Retrofit
        retrofit = Retrofit.Builder()
            .baseUrl("https://www.example.com/") // baseUrl 可以是任意域名,因为我们用 @Url 覆盖了
            .addConverterFactory(ScalarsConverterFactory.create()) // 添加字符串转换器
            .build()
        webService = retrofit.create(WebService::class.java)
        downloadButton.setOnClickListener {
            downloadWithRetrofit("https://www.example.com")
        }
    }
    private fun downloadWithRetrofit(url: String) {
        val call = webService.fetchWebSource(url)
        call.enqueue(object : Callback<String> {
            override fun onResponse(call: Call<String>, response: Response<String>) {
                if (response.isSuccessful) {
                    val sourceCode = response.body()
                    runOnUiThread {
                        sourceCodeTextView.text = sourceCode
                    }
                } else {
                    runOnUiThread {
                        Toast.makeText(this@RetrofitActivity, "下载失败: HTTP ${response.code()}", Toast.LENGTH_SHORT).show()
                    }
                }
            }
            override fun onFailure(call: Call<String>, t: Throwable) {
                runOnUiThread {
                    Toast.makeText(this@RetrofitActivity, "下载失败: ${t.message}", Toast.LENGTH_SHORT).show()
                }
            }
        })
    }
}

方法三: 使用 HttpURLConnection (原生 API,不推荐用于新项目)

这是 Android 原生的网络 API,无需添加第三方库,但是它的 API 比较繁琐,不支持异步请求,需要自己开子线程,并且代码量更多,它通常只用于非常简单的场景或不想引入第三方库的项目。

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
class HttpURLConnectionActivity : AppCompatActivity() {
    private val sourceCodeTextView: TextView by lazy { findViewById(R.id.sourceCodeTextView) }
    private val downloadButton: Button by lazy { findViewById(R.id.downloadButton) }
    private val handler = Handler(Looper.getMainLooper())
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        downloadButton.setOnClickListener {
            // 在子线程中执行网络请求
            Thread {
                downloadWithHttpURLConnection("https://www.example.com")
            }.start()
        }
    }
    private fun downloadWithHttpURLConnection(urlString: String) {
        var connection: HttpURLConnection? = null
        var reader: BufferedReader? = null
        try {
            val url = URL(urlString)
            connection = url.openConnection() as HttpURLConnection
            connection.requestMethod = "GET"
            connection.connectTimeout = 15000
            connection.readTimeout = 15000
            val responseCode = connection.responseCode
            if (responseCode == HttpURLConnection.HTTP_OK) {
                val inputStream = connection.inputStream
                reader = BufferedReader(InputStreamReader(inputStream))
                val stringBuilder = StringBuilder()
                var line: String?
                while (reader.readLine().also { line = it } != null) {
                    stringBuilder.append(line).append("\n")
                }
                // 通过 Handler 在主线程更新 UI
                handler.post {
                    sourceCodeTextView.text = stringBuilder.toString()
                }
            } else {
                // 通过 Handler 在主线程显示错误信息
                handler.post {
                    Toast.makeText(this, "下载失败: HTTP $responseCode", Toast.LENGTH_SHORT).show()
                }
            }
        } catch (e: IOException) {
            e.printStackTrace()
            handler.post {
                Toast.makeText(this, "下载失败: ${e.message}", Toast.LENGTH_SHORT).show()
            }
        } finally {
            reader?.close()
            connection?.disconnect()
        }
    }
}

重要注意事项和最佳实践

  1. 不能在主线程进行网络操作 Android 的主线程(UI 线程)负责处理用户交互和绘制界面,如果在主线程进行网络请求,会导致应用无响应(ANR - Application Not Responding),所有网络请求都必须在子线程或使用异步框架(如 OkHttp 的 enqueue)。

  2. 处理 android:networkSecurityConfig 从 Android 7.0 (API 24) 开始,默认禁止使用不安全的 HTTPS 证书(用户自签名的证书),如果你的服务器使用的是自签名证书,需要在 AndroidManifest.xml 中配置 networkSecurityConfig

  3. 数据量很大时 如果网页源码非常大(比如几 MB),直接用一个 TextView 显示可能会导致性能问题,你可能需要只显示一部分内容,或者使用 RecyclerView 来展示。

  4. 解析源码 vs. 显示网页

    • 下载源码:获取的是纯文本 HTML,适合用于分析、爬取数据或调试。
    • 显示网页:如果你想在 App 内显示一个完整的网页(包括 CSS 样式和 JavaScript 交互),你应该使用 WebView 组件,而不是下载源码。

    使用 WebView 显示网页的示例:

    val webView: WebView = findViewById(R.id.webView)
    webView.webViewClient = WebViewClient() // 防止使用系统浏览器打开
    webView.loadUrl("https://www.example.com")
方法 优点 缺点 推荐场景
OkHttp 简单、高效、功能强大、社区活跃 需要添加依赖 绝大多数情况下的首选,尤其是简单的网络请求。
Retrofit 类型安全、代码优雅、易于维护、支持多种数据解析 比较重量级,对简单请求来说略显复杂 需要与复杂 API 交互的项目,或者团队已经在使用 Retrofit。
HttpURLConnection 无需第三方库、系统自带 API 繁琐、需要手动管理线程、功能有限 学习目的极度轻量级的应用,不想引入任何第三方库。

对于初学者和大多数应用,从 OkHttp 开始是最好的选择,它足够强大,学习曲线也不陡峭。