01-04-04 网络监控与测试完全指南
01-04-04 网络监控与测试完全指南
问题背景
在Android开发中,网络请求的调试、监控和测试是保证应用质量的关键环节。然而,直接使用生产环境API会遇到诸多问题:后端API尚未就绪、无法模拟各种异常场景、难以追踪网络性能、多环境切换繁琐、生产环境日志不足等。
在deviceSecurity项目中,需要解决以下挑战:如何记录详细的网络日志用于问题排查?如何监控网络性能指标(耗时、成功率、流量)?如何快速切换开发/测试/生产环境?如何Mock网络请求进行UI测试?如何在不同环境下验证功能?
本文将全面深入讲解网络请求日志、性能监控、多环境配置、Mock测试、网络层最佳实践,并结合deviceSecurity项目提供18+个企业级代码示例。
核心概念
1. 网络监控维度
日志维度:
- 请求信息(URL、Method、Headers、Body)
- 响应信息(Status Code、Headers、Body、Duration)
- 错误信息(Exception、Stack Trace)
性能维度:
- 请求耗时(总耗时、DNS耗时、连接耗时、传输耗时)
- 数据传输量(上传字节、下载字节)
- 成功率(成功请求数/总请求数)
业务维度:
- API调用频率
- 错误分布(网络错误、HTTP错误、业务错误)
- 慢请求追踪(耗时超过阈值的请求)
2. 环境配置架构
┌─────────────────────────────────────┐
│ Build Variants │
│ (debug/release + flavor) │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ BuildConfig │
│ (BASE_URL, API_KEY, etc.) │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ EnvironmentManager │
│ (动态切换环境) │
└──────────────┬──────────────────────┘
│
┌──────────────▼──────────────────────┐
│ OkHttpClient/Retrofit │
│ (使用当前环境配置) │
└─────────────────────────────────────┘
3. Mock测试策略
MockWebServer:单元测试,完全控制响应
Interceptor Mock:开发调试,快速验证UI
Repository Mock:ViewModel测试,隔离网络层
Fake实现:集成测试,模拟复杂逻辑
网络日志监控
1. HttpLoggingInterceptor基础配置
// HttpLoggingInterceptor配置
class NetworkModule {
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(createLoggingInterceptor())
.build()
}
private fun createLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor { message ->
Log.d("OkHttp", message)
}.apply {
level = if (BuildConfig.DEBUG) {
HttpLoggingInterceptor.Level.BODY
} else {
HttpLoggingInterceptor.Level.NONE
}
}
}
}
日志级别说明:
NONE:不记录任何日志BASIC:记录请求和响应的基本信息(URL、状态码、耗时)HEADERS:记录BASIC + HeadersBODY:记录HEADERS + Body(完整内容)
2. 自定义网络日志拦截器
class DetailedLoggingInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val startTime = System.nanoTime()
// 记录请求信息
logRequest(request)
val response = try {
chain.proceed(request)
} catch (e: Exception) {
// 记录网络异常
logError(request, e, System.nanoTime() - startTime)
throw e
}
val elapsedTime = System.nanoTime() - startTime
// 记录响应信息
logResponse(request, response, elapsedTime)
return response
}
private fun logRequest(request: Request) {
val requestBody = request.body
Log.d("NetworkRequest", """
════════════════════════════════════════
URL: ${request.url}
Method: ${request.method}
Headers: ${request.headers}
Body: ${requestBody?.let { readBody(it) } ?: "No Body"}
════════════════════════════════════════
""".trimIndent())
}
private fun logResponse(request: Request, response: Response, elapsedTime: Long) {
val responseBody = response.peekBody(1024 * 1024) // 最多读取1MB
Log.d("NetworkResponse", """
════════════════════════════════════════
URL: ${request.url}
Status Code: ${response.code}
Message: ${response.message}
Duration: ${TimeUnit.NANOSECONDS.toMillis(elapsedTime)}ms
Headers: ${response.headers}
Body: ${responseBody.string()}
════════════════════════════════════════
""".trimIndent())
}
private fun logError(request: Request, error: Exception, elapsedTime: Long) {
Log.e("NetworkError", """
════════════════════════════════════════
URL: ${request.url}
Duration: ${TimeUnit.NANOSECONDS.toMillis(elapsedTime)}ms
Error: ${error.javaClass.simpleName}
Message: ${error.message}
════════════════════════════════════════
""".trimIndent(), error)
}
private fun readBody(body: RequestBody): String {
val buffer = Buffer()
body.writeTo(buffer)
return buffer.readUtf8()
}
}
3. 网络日志持久化
// 将网络日志保存到文件
class FileLoggingInterceptor(
private val context: Context
) : Interceptor {
private val logFile = File(context.getExternalFilesDir(null), "network_logs.txt")
private val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.getDefault())
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val startTime = System.currentTimeMillis()
val response = chain.proceed(request)
val duration = System.currentTimeMillis() - startTime
// 记录到文件
saveLogToFile(
timestamp = dateFormat.format(Date(startTime)),
method = request.method,
url = request.url.toString(),
statusCode = response.code,
duration = duration
)
return response
}
private fun saveLogToFile(
timestamp: String,
method: String,
url: String,
statusCode: Int,
duration: Long
) {
val logEntry = "$timestamp | $method | $url | $statusCode | ${duration}ms\n"
try {
logFile.appendText(logEntry)
// 限制文件大小,超过10MB则清空
if (logFile.length() > 10 * 1024 * 1024) {
logFile.delete()
}
} catch (e: IOException) {
Log.e("FileLogging", "Failed to write log", e)
}
}
// 读取日志文件
fun getLogs(): String {
return try {
logFile.readText()
} catch (e: IOException) {
""
}
}
// 清空日志
fun clearLogs() {
logFile.delete()
}
}
性能监控
1. 网络性能监控拦截器
class NetworkPerformanceInterceptor : Interceptor {
private val metricsCollector = NetworkMetricsCollector()
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val url = request.url.toString()
val startTime = System.nanoTime()
var requestBytesSent = 0L
var responseBytesReceived = 0L
try {
// 计算请求大小
request.body?.let {
requestBytesSent = it.contentLength()
}
val response = chain.proceed(request)
// 计算响应大小
responseBytesReceived = response.body?.contentLength() ?: 0L
val duration = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
// 收集指标
metricsCollector.recordSuccess(
url = url,
method = request.method,
statusCode = response.code,
duration = duration,
requestBytes = requestBytesSent,
responseBytes = responseBytesReceived
)
return response
} catch (e: Exception) {
val duration = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)
metricsCollector.recordFailure(
url = url,
method = request.method,
duration = duration,
error = e
)
throw e
}
}
fun getMetrics(): NetworkMetrics = metricsCollector.getMetrics()
}
// 网络指标收集器
class NetworkMetricsCollector {
private val metrics = mutableMapOf<String, ApiMetrics>()
fun recordSuccess(
url: String,
method: String,
statusCode: Int,
duration: Long,
requestBytes: Long,
responseBytes: Long
) {
val key = "$method:$url"
val apiMetrics = metrics.getOrPut(key) { ApiMetrics() }
apiMetrics.totalRequests++
apiMetrics.successRequests++
apiMetrics.totalDuration += duration
apiMetrics.totalRequestBytes += requestBytes
apiMetrics.totalResponseBytes += responseBytes
if (duration > apiMetrics.maxDuration) {
apiMetrics.maxDuration = duration
}
if (duration < apiMetrics.minDuration || apiMetrics.minDuration == 0L) {
apiMetrics.minDuration = duration
}
}
fun recordFailure(
url: String,
method: String,
duration: Long,
error: Exception
) {
val key = "$method:$url"
val apiMetrics = metrics.getOrPut(key) { ApiMetrics() }
apiMetrics.totalRequests++
apiMetrics.failedRequests++
apiMetrics.totalDuration += duration
apiMetrics.errors.add(error.javaClass.simpleName)
}
fun getMetrics(): NetworkMetrics {
return NetworkMetrics(
apiMetrics = metrics.toMap(),
totalRequests = metrics.values.sumOf { it.totalRequests },
successRate = calculateSuccessRate()
)
}
private fun calculateSuccessRate(): Double {
val total = metrics.values.sumOf { it.totalRequests }
val success = metrics.values.sumOf { it.successRequests }
return if (total > 0) (success.toDouble() / total) * 100 else 0.0
}
}
data class ApiMetrics(
var totalRequests: Int = 0,
var successRequests: Int = 0,
var failedRequests: Int = 0,
var totalDuration: Long = 0,
var maxDuration: Long = 0,
var minDuration: Long = 0,
var totalRequestBytes: Long = 0,
var totalResponseBytes: Long = 0,
val errors: MutableList<String> = mutableListOf()
) {
val avgDuration: Long
get() = if (totalRequests > 0) totalDuration / totalRequests else 0
val successRate: Double
get() = if (totalRequests > 0) (successRequests.toDouble() / totalRequests) * 100 else 0.0
}
data class NetworkMetrics(
val apiMetrics: Map<String, ApiMetrics>,
val totalRequests: Int,
val successRate: Double
)
2. 慢请求追踪
class SlowRequestTracker(
private val thresholdMs: Long = 3000 // 慢请求阈值:3秒
) : Interceptor {
private val slowRequests = mutableListOf<SlowRequestRecord>()
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val startTime = System.currentTimeMillis()
val response = chain.proceed(request)
val duration = System.currentTimeMillis() - startTime
// 记录慢请求
if (duration > thresholdMs) {
recordSlowRequest(
url = request.url.toString(),
method = request.method,
duration = duration,
statusCode = response.code
)
}
return response
}
private fun recordSlowRequest(
url: String,
method: String,
duration: Long,
statusCode: Int
) {
synchronized(slowRequests) {
slowRequests.add(
SlowRequestRecord(
timestamp = System.currentTimeMillis(),
url = url,
method = method,
duration = duration,
statusCode = statusCode
)
)
// 保留最近100条
if (slowRequests.size > 100) {
slowRequests.removeAt(0)
}
}
Log.w("SlowRequest", "⚠️ 慢请求检测: $method $url 耗时 ${duration}ms")
}
fun getSlowRequests(): List<SlowRequestRecord> {
return synchronized(slowRequests) {
slowRequests.toList()
}
}
fun clearSlowRequests() {
synchronized(slowRequests) {
slowRequests.clear()
}
}
}
data class SlowRequestRecord(
val timestamp: Long,
val url: String,
val method: String,
val duration: Long,
val statusCode: Int
)
3. 性能监控Dashboard
// ViewModel for displaying network metrics
class NetworkMonitorViewModel : ViewModel() {
private val performanceInterceptor: NetworkPerformanceInterceptor = // inject
private val _metrics = MutableLiveData<NetworkMetrics>()
val metrics: LiveData<NetworkMetrics> = _metrics
private val _slowRequests = MutableLiveData<List<SlowRequestRecord>>()
val slowRequests: LiveData<List<SlowRequestRecord>> = _slowRequests
fun refreshMetrics() {
_metrics.value = performanceInterceptor.getMetrics()
}
fun refreshSlowRequests() {
_slowRequests.value = slowRequestTracker.getSlowRequests()
}
}
// UI Fragment
class NetworkMonitorFragment : Fragment() {
private val viewModel: NetworkMonitorViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.metrics.observe(viewLifecycleOwner) { metrics ->
displayMetrics(metrics)
}
viewModel.slowRequests.observe(viewLifecycleOwner) { slowRequests ->
displaySlowRequests(slowRequests)
}
viewModel.refreshMetrics()
viewModel.refreshSlowRequests()
}
private fun displayMetrics(metrics: NetworkMetrics) {
binding.apply {
totalRequestsText.text = "总请求数: ${metrics.totalRequests}"
successRateText.text = "成功率: ${"%.2f".format(metrics.successRate)}%"
// 显示各API的详细指标
val metricsText = metrics.apiMetrics.entries.joinToString("\n\n") { (api, apiMetrics) ->
"""
$api
- 请求次数: ${apiMetrics.totalRequests}
- 成功率: ${"%.2f".format(apiMetrics.successRate)}%
- 平均耗时: ${apiMetrics.avgDuration}ms
- 最大耗时: ${apiMetrics.maxDuration}ms
- 数据上传: ${formatBytes(apiMetrics.totalRequestBytes)}
- 数据下载: ${formatBytes(apiMetrics.totalResponseBytes)}
""".trimIndent()
}
metricsDetailText.text = metricsText
}
}
private fun displaySlowRequests(slowRequests: List<SlowRequestRecord>) {
val text = slowRequests.joinToString("\n\n") { record ->
val date = SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date(record.timestamp))
"$date | ${record.method} | ${record.url}\n耗时: ${record.duration}ms"
}
binding.slowRequestsText.text = text.ifEmpty { "无慢请求记录" }
}
private fun formatBytes(bytes: Long): String {
return when {
bytes < 1024 -> "$bytes B"
bytes < 1024 * 1024 -> "${bytes / 1024} KB"
else -> "${bytes / (1024 * 1024)} MB"
}
}
}
多环境配置
1. BuildConfig环境配置
// build.gradle.kts
android {
buildTypes {
debug {
buildConfigField("String", "BASE_URL", "\"https://dev-api.example.com\"")
buildConfigField("String", "API_KEY", "\"dev_key_123\"")
buildConfigField("boolean", "ENABLE_LOGGING", "true")
}
release {
buildConfigField("String", "BASE_URL", "\"https://api.example.com\"")
buildConfigField("String", "API_KEY", "\"prod_key_456\"")
buildConfigField("boolean", "ENABLE_LOGGING", "false")
}
}
flavorDimensions += "environment"
productFlavors {
create("dev") {
dimension = "environment"
applicationIdSuffix = ".dev"
buildConfigField("String", "BASE_URL", "\"https://dev-api.example.com\"")
}
create("staging") {
dimension = "environment"
applicationIdSuffix = ".staging"
buildConfigField("String", "BASE_URL", "\"https://staging-api.example.com\"")
}
create("prod") {
dimension = "environment"
buildConfigField("String", "BASE_URL", "\"https://api.example.com\"")
}
}
}
// 使用BuildConfig
class NetworkModule {
fun provideRetrofit(): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.build()
}
}
2. 动态环境切换
// 环境管理器
object EnvironmentManager {
private const val PREF_KEY_ENV = "current_environment"
enum class Environment(val baseUrl: String, val displayName: String) {
DEV("https://dev-api.example.com", "开发环境"),
STAGING("https://staging-api.example.com", "测试环境"),
PRODUCTION("https://api.example.com", "生产环境")
}
private var currentEnvironment: Environment = Environment.DEV
fun init(context: Context) {
val prefs = context.getSharedPreferences("app_config", Context.MODE_PRIVATE)
val envName = prefs.getString(PREF_KEY_ENV, Environment.DEV.name)
currentEnvironment = Environment.valueOf(envName ?: Environment.DEV.name)
}
fun getCurrentEnvironment(): Environment = currentEnvironment
fun switchEnvironment(context: Context, environment: Environment) {
currentEnvironment = environment
val prefs = context.getSharedPreferences("app_config", Context.MODE_PRIVATE)
prefs.edit().putString(PREF_KEY_ENV, environment.name).apply()
// 重启应用以应用新环境
restartApp(context)
}
private fun restartApp(context: Context) {
val intent = context.packageManager.getLaunchIntentForPackage(context.packageName)
intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(intent)
System.exit(0)
}
}
// Retrofit配置使用动态环境
class NetworkModule(private val context: Context) {
fun provideRetrofit(): Retrofit {
EnvironmentManager.init(context)
return Retrofit.Builder()
.baseUrl(EnvironmentManager.getCurrentEnvironment().baseUrl)
.client(provideOkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}
3. 环境切换UI
// 环境切换Dialog
class EnvironmentSwitchDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val environments = EnvironmentManager.Environment.values()
val currentEnv = EnvironmentManager.getCurrentEnvironment()
val items = environments.map { it.displayName }.toTypedArray()
val checkedItem = environments.indexOf(currentEnv)
return AlertDialog.Builder(requireContext())
.setTitle("选择环境")
.setSingleChoiceItems(items, checkedItem) { dialog, which ->
val selectedEnv = environments[which]
AlertDialog.Builder(requireContext())
.setTitle("确认切换")
.setMessage("切换到${selectedEnv.displayName}?\n应用将重启。")
.setPositiveButton("确定") { _, _ ->
EnvironmentManager.switchEnvironment(requireContext(), selectedEnv)
}
.setNegativeButton("取消", null)
.show()
dialog.dismiss()
}
.setNegativeButton("取消", null)
.create()
}
}
// 在设置页面添加入口(仅Debug模式)
class SettingsFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (BuildConfig.DEBUG) {
binding.environmentSwitchButton.visibility = View.VISIBLE
binding.environmentSwitchButton.setOnClickListener {
EnvironmentSwitchDialog().show(childFragmentManager, "env_switch")
}
// 显示当前环境
binding.currentEnvironmentText.text =
"当前环境: ${EnvironmentManager.getCurrentEnvironment().displayName}"
} else {
binding.environmentSwitchButton.visibility = View.GONE
}
}
}
Mock测试
1. MockWebServer单元测试
class ApiServiceTest {
private lateinit var mockWebServer: MockWebServer
private lateinit var apiService: ApiService
@Before
fun setUp() {
mockWebServer = MockWebServer()
mockWebServer.start()
val retrofit = Retrofit.Builder()
.baseUrl(mockWebServer.url("/"))
.addConverterFactory(GsonConverterFactory.create())
.build()
apiService = retrofit.create(ApiService::class.java)
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
@Test
fun `test get devices success`() = runBlocking {
// 准备Mock响应
val mockResponse = """
{
"devices": [
{"id": "1", "name": "Camera 1"},
{"id": "2", "name": "Camera 2"}
]
}
""".trimIndent()
mockWebServer.enqueue(
MockResponse()
.setResponseCode(200)
.setBody(mockResponse)
)
// 执行请求
val response = apiService.getDevices()
// 验证结果
assertTrue(response.isSuccessful)
assertEquals(2, response.body()?.devices?.size)
// 验证请求
val request = mockWebServer.takeRequest()
assertEquals("GET", request.method)
assertEquals("/devices", request.path)
}
@Test
fun `test get devices with network error`() = runBlocking {
// Mock网络错误
mockWebServer.enqueue(
MockResponse().setSocketPolicy(SocketPolicy.DISCONNECT_AT_START)
)
// 执行并捕获异常
val exception = assertThrows(IOException::class.java) {
runBlocking {
apiService.getDevices()
}
}
assertNotNull(exception)
}
@Test
fun `test get devices with 500 error`() = runBlocking {
// Mock服务器错误
mockWebServer.enqueue(
MockResponse()
.setResponseCode(500)
.setBody("{\"error\": \"Internal Server Error\"}")
)
val response = apiService.getDevices()
assertFalse(response.isSuccessful)
assertEquals(500, response.code())
}
}
2. Interceptor Mock for UI测试
class MockInterceptor : Interceptor {
private val mockResponses = mutableMapOf<String, MockResponse>()
fun registerMockResponse(urlPattern: String, response: MockResponse) {
mockResponses[urlPattern] = response
}
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val url = request.url.toString()
// 查找匹配的Mock响应
val mockResponse = mockResponses.entries.firstOrNull { (pattern, _) ->
url.contains(pattern)
}?.value
return if (mockResponse != null) {
// 返回Mock响应
Response.Builder()
.code(mockResponse.code)
.message(mockResponse.message)
.body(mockResponse.body.toResponseBody("application/json".toMediaType()))
.protocol(Protocol.HTTP_1_1)
.request(request)
.build()
} else {
// 返回真实请求
chain.proceed(request)
}
}
data class MockResponse(
val code: Int,
val message: String = "",
val body: String
)
}
// 使用示例
class NetworkModule {
fun provideOkHttpClient(useMock: Boolean = false): OkHttpClient {
val builder = OkHttpClient.Builder()
if (useMock && BuildConfig.DEBUG) {
val mockInterceptor = MockInterceptor().apply {
// 注册Mock响应
registerMockResponse(
urlPattern = "/devices",
response = MockInterceptor.MockResponse(
code = 200,
body = """
{
"devices": [
{"id": "1", "name": "Mock Camera 1"},
{"id": "2", "name": "Mock Camera 2"}
]
}
""".trimIndent()
)
)
}
builder.addInterceptor(mockInterceptor)
}
return builder.build()
}
}
3. Repository Mock for ViewModel测试
// Repository接口
interface DeviceRepository {
suspend fun getDevices(): Result<List<Device>>
suspend fun getDeviceDetail(deviceId: String): Result<Device>
}
// Fake实现(用于测试)
class FakeDeviceRepository : DeviceRepository {
private val devices = mutableListOf(
Device("1", "Camera 1", true),
Device("2", "Camera 2", false)
)
var shouldReturnError = false
var networkDelay = 0L
override suspend fun getDevices(): Result<List<Device>> {
delay(networkDelay)
return if (shouldReturnError) {
Result.failure(IOException("Network error"))
} else {
Result.success(devices)
}
}
override suspend fun getDeviceDetail(deviceId: String): Result<Device> {
delay(networkDelay)
return if (shouldReturnError) {
Result.failure(IOException("Network error"))
} else {
val device = devices.find { it.id == deviceId }
if (device != null) {
Result.success(device)
} else {
Result.failure(NoSuchElementException("Device not found"))
}
}
}
// 测试辅助方法
fun addDevice(device: Device) {
devices.add(device)
}
fun clearDevices() {
devices.clear()
}
}
// ViewModel测试
class DeviceViewModelTest {
private lateinit var viewModel: DeviceViewModel
private lateinit var fakeRepository: FakeDeviceRepository
@Before
fun setUp() {
fakeRepository = FakeDeviceRepository()
viewModel = DeviceViewModel(fakeRepository)
}
@Test
fun `load devices success`() = runTest {
// 准备数据
fakeRepository.addDevice(Device("3", "Camera 3", true))
// 执行
viewModel.loadDevices()
// 验证
val state = viewModel.devicesState.value
assertTrue(state is UiState.Success)
assertEquals(3, (state as UiState.Success).data.size)
}
@Test
fun `load devices with network error`() = runTest {
// 设置错误
fakeRepository.shouldReturnError = true
// 执行
viewModel.loadDevices()
// 验证
val state = viewModel.devicesState.value
assertTrue(state is UiState.Error)
assertEquals("Network error", (state as UiState.Error).message)
}
@Test
fun `load devices with loading state`() = runTest {
// 设置延迟
fakeRepository.networkDelay = 1000L
// 执行
val job = launch {
viewModel.loadDevices()
}
// 验证加载状态
advanceTimeBy(500)
assertTrue(viewModel.devicesState.value is UiState.Loading)
// 等待完成
advanceTimeBy(600)
job.join()
assertTrue(viewModel.devicesState.value is UiState.Success)
}
}
网络调试工具
1. Charles代理配置
步骤:
- 安装Charles并启动
- 手机和电脑连接同一WiFi
- 手机WiFi设置中配置HTTP代理
- 服务器:电脑IP
- 端口:8888
- 安装Charles证书(抓取HTTPS)
- Android 7.0+需要配置network_security_config.xml
<!-- res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!-- Debug模式信任用户证书 -->
<debug-overrides>
<trust-anchors>
<certificates src="user" />
<certificates src="system" />
</trust-anchors>
</debug-overrides>
</network-security-config>
<!-- AndroidManifest.xml -->
<application
android:networkSecurityConfig="@xml/network_security_config">
</application>
2. Flipper集成
// build.gradle.kts
dependencies {
debugImplementation("com.facebook.flipper:flipper:0.164.0")
debugImplementation("com.facebook.flipper:flipper-network-plugin:0.164.0")
debugImplementation("com.facebook.soloader:soloader:0.10.4")
releaseImplementation("com.facebook.flipper:flipper-noop:0.164.0")
}
// Application初始化
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) {
SoLoader.init(this, false)
val client = AndroidFlipperClient.getInstance(this)
client.addPlugin(InspectorFlipperPlugin(this, DescriptorMapping.withDefaults()))
client.addPlugin(NetworkFlipperPlugin())
client.start()
}
}
}
// OkHttp配置
fun provideOkHttpClient(): OkHttpClient {
val builder = OkHttpClient.Builder()
if (BuildConfig.DEBUG) {
builder.addNetworkInterceptor(FlipperOkhttpInterceptor(NetworkFlipperPlugin()))
}
return builder.build()
}
3. Chucker实时网络监控
// build.gradle.kts
dependencies {
debugImplementation("com.github.chuckerteam.chucker:library:3.5.2")
releaseImplementation("com.github.chuckerteam.chucker:library-no-op:3.5.2")
}
// OkHttp配置
class NetworkModule(private val context: Context) {
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(
ChuckerInterceptor.Builder(context)
.maxContentLength(250_000L)
.redactHeaders("Authorization", "Bearer")
.alwaysReadResponseBody(true)
.build()
)
.build()
}
}
// 在通知栏显示网络请求
// Chucker会自动在通知栏显示请求详情,点击可查看完整信息
最佳实践
1. 日志安全
class SecureLoggingInterceptor : Interceptor {
private val sensitiveHeaders = setOf(
"Authorization",
"API-Key",
"X-Auth-Token"
)
private val sensitiveQueryParams = setOf(
"password",
"token",
"api_key"
)
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
// 脱敏后记录
val safeUrl = redactUrl(request.url)
val safeHeaders = redactHeaders(request.headers)
Log.d("SecureRequest", """
URL: $safeUrl
Headers: $safeHeaders
""".trimIndent())
return chain.proceed(request)
}
private fun redactUrl(url: HttpUrl): String {
val sanitizedQuery = url.queryParameterNames.joinToString("&") { param ->
val value = if (param in sensitiveQueryParams) {
"***"
} else {
url.queryParameter(param)
}
"$param=$value"
}
return "${url.scheme}://${url.host}${url.encodedPath}?$sanitizedQuery"
}
private fun redactHeaders(headers: Headers): String {
return headers.names().joinToString("\n") { name ->
val value = if (name in sensitiveHeaders) {
"***"
} else {
headers[name]
}
"$name: $value"
}
}
}
2. 性能监控最佳实践
✅ 只在Debug模式开启详细日志:避免Release版本性能损耗
✅ 异步记录日志:避免阻塞主线程
✅ 限制日志大小:防止磁盘空间占用过多
✅ 脱敏敏感信息:保护用户隐私和API密钥
✅ 合理的采样率:生产环境只记录关键指标
3. Mock测试最佳实践
✅ 单元测试使用MockWebServer:完全控制响应,测试边界条件
✅ UI测试使用Interceptor Mock:快速验证界面,无需真实后端
✅ ViewModel测试使用Repository Mock:隔离网络层,测试业务逻辑
✅ 集成测试使用Fake实现:模拟复杂场景,保持测试稳定
总结
本文全面讲解了Android网络监控与测试的核心技术和最佳实践:
核心知识点
- 网络日志监控 - HttpLoggingInterceptor、自定义日志拦截器、日志持久化
- 性能监控 - 请求耗时、流量统计、慢请求追踪、监控Dashboard
- 多环境配置 - BuildConfig、动态环境切换、环境切换UI
- Mock测试 - MockWebServer、Interceptor Mock、Repository Mock、Fake实现
- 调试工具 - Charles代理、Flipper、Chucker
- 最佳实践 - 日志安全、性能优化、测试策略
实践要点
✅ 开发阶段 - 使用详细日志和Chucker实时监控网络请求
✅ 测试阶段 - Mock网络请求进行UI测试,使用不同环境验证功能
✅ 性能优化 - 监控慢请求、优化网络性能、追踪异常请求
✅ 生产环境 - 关闭详细日志、保留关键指标、保护敏感信息
✅ 自动化测试 - 编写单元测试、集成测试,确保网络层稳定性
延伸学习
- OkHttp Interceptors
- Retrofit Testing
- MockWebServer文档
- Android Network Security Config
- Flipper Documentation
掌握网络监控与测试技术,能够快速定位网络问题、保证应用质量、提升开发效率,为构建稳定可靠的网络层打下坚实基础。
文档更新时间:2026-03-21
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)