凌峰创科服务平台

Android如何访问本地服务器?

核心概念:为什么需要特殊处理?

在 Android 10 (API 29) 及更高版本中,Google 出于安全考虑,引入了分区存储网络限制策略,默认情况下:

Android如何访问本地服务器?-图1
(图片来源网络,侵删)
  • 非 HTTP 网络限制:应用无法访问 http:// 协议的本地网络地址(如 http://10.0.2.2:8080),只能访问 https://,这是为了防止恶意应用在用户不知情的情况下扫描和访问本地网络。
  • 分区存储:应用对文件系统的访问受到了更严格的限制,但这主要影响文件读写,对网络访问影响不大。

要让你的 Android App 成功访问本地服务器,你需要根据你的目标 Android 版本和服务器类型,采取不同的策略。


场景一:访问 PC 或其他设备上的服务器(最常见)

这是最常见的情况,比如你的本地服务器运行在你的开发机器(PC)上,你想用手机上的 App 去访问它。

步骤 1:确定 PC 的本地 IP 地址

你的手机和 PC 必须在同一个 Wi-Fi 网络下。

  1. 在 Windows PC 上,打开命令提示符或 PowerShell,输入 ipconfig,找到你正在使用的网卡的 "IPv4 地址",168.1.105
  2. 在 macOS 上,打开终端,输入 ifconfig,找到 "en0" 或 "en1" 下的 "inet" 地址,168.1.105

步骤 2:在 Android 代码中使用 PC 的 IP 地址

在你的 Android App 的网络请求代码中,不要使用 localhost0.0.1,因为这些指向的是手机本身,你应该使用上一步中找到的 PC 的 IP 地址。

Android如何访问本地服务器?-图2
(图片来源网络,侵删)

错误的地址:

  • http://localhost:8080/api
  • http://127.0.0.1:8080/api
  • http://10.0.2.2:8080/api (这个地址是 Android 模拟器访问宿主 PC 的专用地址,真机不适用)

正确的地址:

  • http://192.168.1.105:8080/api

步骤 3:处理 Android 网络限制(关键!)

如果你的应用 targetSdkVersion 是 28 或更高(强烈建议),并且运行在 Android 9 (Pie) 或更高版本的设备上,你必须在 AndroidManifest.xml 中声明 usesCleartextTraffic

<!-- AndroidManifest.xml -->
<manifest ...>
    <!-- 允许应用在非加密的网络上传输数据 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- 
      android:usesCleartextTraffic="true"
      这行是关键!它允许应用使用 HTTP 协议。
      如果你的服务器是 HTTPS,可以不设置或设为 false。
    -->
    <application
        android:usesCleartextTraffic="true"
        ...>
        ...
    </application>
</manifest>

对于 Android 10 (API 29) 及更高版本:

如果你只希望应用在调试时能访问本地 HTTP 服务器,正式发布时要求更安全,可以使用 networkSecurityConfig

  1. res/xml/ 目录下创建一个 network_security_config.xml 文件:

    <!-- res/xml/network_security_config.xml -->
    <network-security-config>
        <domain-config cleartextTrafficPermitted="true">
            <!-- 将 your-local-server-ip �换成你的PC IP地址 -->
            <domain includeSubdomains="true">192.168.1.105</domain>
        </domain-config>
    </network-security-config>
  2. AndroidManifest.xml<application> 标签中引用它:

    <!-- AndroidManifest.xml -->
    <application
        ...
        android:networkSecurityConfig="@xml/network_security_config"
        ...>
        ...
    </application>

这种方式更安全,因为它只允许访问你指定的 IP 地址,而不是所有 HTTP 流量。


场景二:访问手机本机上的服务器

如果你的应用需要在手机上启动一个服务器(例如一个 HTTP 服务器来提供文件),然后让 App 的其他部分访问它。

  • 地址:在这种情况下,你应该使用 0.2.2 这个特殊地址,这个地址是 Android 模拟器用来访问宿主(开发机器)的代理地址。在真机上,0.2.2 是无效的
  • 真机方案:在真机上,如果你想访问本机服务器,最可靠的方法是使用 localhost0.0.1
    • Android 8.0 (Oreo) 及以下:直接使用 http://localhost:8080 通常可以工作。
    • Android 9 (Pie) 及以上:由于网络限制,直接使用 localhost 可能会失败,你需要结合 usesCleartextTraffic="true"networkSecurityConfig 来允许访问 localhost

network_security_config.xml 示例(允许访问 localhost):

<!-- res/xml/network_security_config.xml -->
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">localhost</domain>
    </domain-config>
</network-security-config>

代码实现示例(使用 Retrofit + OkHttp)

现代 Android 开发中,使用 Retrofit 和 OkHttp 是进行网络请求的最佳实践。

添加依赖 (build.gradle.kts / build.gradle)

// build.gradle (Module :app)
dependencies {
    // Retrofit for networking
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0' // For JSON parsing
    // OkHttp for logging (optional but highly recommended)
    implementation "com.squareup.okhttp3:logging-interceptor:4.9.3"
}

创建 API 接口

// ApiService.kt
import retrofit2.Call
import retrofit2.http.GET
interface ApiService {
    @GET("api/data") // 假设你的本地服务器有这个接口
    fun getData(): Call<List<String>>
}

创建 Retrofit 实例

// RetrofitClient.kt
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
object RetrofitClient {
    // !!! 替换成你的 PC IP 地址 !!!
    private const val BASE_URL = "http://192.168.1.105:8080/"
    val instance: ApiService by lazy {
        // 创建一个 OkHttp 客户端,可以添加拦截器(如日志)
        val okHttpClient = OkHttpClient.Builder()
            .connectTimeout(30, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .writeTimeout(30, TimeUnit.SECONDS)
            .addInterceptor(HttpLoggingInterceptor().apply {
                level = HttpLoggingInterceptor.Level.BODY
            })
            .build()
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .client(okHttpClient)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}

在 Activity 或 ViewModel 中调用

// MainActivity.kt
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
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val textView: TextView = findViewById(R.id.textView)
        val button: Button = findViewById(R.id.button)
        button.setOnClickListener {
            fetchData(textView)
        }
    }
    private fun fetchData(textView: TextView) {
        RetrofitClient.instance.getData().enqueue(object : Callback<List<String>> {
            override fun onResponse(call: Call<List<String>>, response: Response<List<String>>) {
                if (response.isSuccessful) {
                    val data = response.body()
                    textView.text = "Data from server: $data"
                } else {
                    textView.text = "Error: ${response.code()}"
                    Toast.makeText(this@MainActivity, "Request failed: ${response.message()}", Toast.LENGTH_SHORT).show()
                }
            }
            override fun onFailure(call: Call<List<String>>, t: Throwable) {
                textView.text = "Failure: ${t.message}"
                Toast.makeText(this@MainActivity, "Network error: ${t.message}", Toast.LENGTH_SHORT).show()
            }
        })
    }
}

常见问题排查清单

如果连接失败,请按以下顺序检查:

  1. IP 地址是否正确?

    • 确保手机和 PC 在同一个 Wi-Fi 下。
    • 确保你使用的是 PC 的 局域网 IP(如 168.x.x),而不是公网 IP。
    • 在手机浏览器中输入 http://你的PCIP:端口,看是否能正常访问服务器,如果浏览器也无法访问,说明问题出在服务器端或网络连接上。
  2. 端口是否开放且正确?

    • 确保服务器监听的端口是你代码中使用的端口。
    • 检查 PC 防火墙是否阻止了该端口的入站连接,你可以尝试暂时关闭防火墙进行测试。
  3. targetSdkVersionusesCleartextTraffic

    • 检查你的 build.gradle 文件中的 targetSdkVersion
    • targetSdkVersion >= 28,请务必在 AndroidManifest.xml 中添加 android:usesCleartextTraffic="true" 或使用 networkSecurityConfig
  4. 服务器是否正在运行?

    这是最基本但最容易忽略的一点,确保你的本地服务器应用(如 Node.js, Python Flask, Java Spring Boot)已经成功启动。

  5. 是否需要模拟器?

    • 如果你用模拟器测试,记得使用 0.2.2 来访问宿主 PC 的服务器,而不是 localhost
  6. 检查 OkHttp 日志

    • 如代码示例所示,添加 HttpLoggingInterceptor,它会打印出详细的请求和响应信息,是调试网络问题的利器。
场景 目标地址 关键配置
访问 PC 上的服务器 http://PC的局域网IP:端口 android:usesCleartextTraffic="true"networkSecurityConfig
访问手机本机服务器 http://localhost:端口 (真机) android:usesCleartextTraffic="true" + networkSecurityConfig (针对 Android 9+)
访问手机本机服务器 http://10.0.2.2:端口 (仅限模拟器) android:usesCleartextTraffic="true"

遵循以上步骤和注意事项,你就可以顺利地在 Android 应用中访问本地服务器了。

分享:
扫描分享到社交APP
上一篇
下一篇