安卓开发 博客
打造一款精致的安卓天气应用是掌握现代安卓开发核心技术的绝佳实践,本教程将引导你使用最新的 Jetpack 组件和 Kotlin 协程,构建一个功能完整、架构清晰的应用。

开发环境与基础配置
-
工具准备:
- 安装最新 Android Studio Hedgehog (2026.1.1) 或更高版本。
- 确保 Android SDK 包含 API 级别 34 (Android 14) 及常用低版本 (如 API 28)。
- 使用 Kotlin 1.9.0 或更高版本。
-
项目初始化:
- 新建项目,选择 “Empty Activity” 模板。
- 项目命名:
WeatherWise,包名自定义(如com.yourdomain.weatherwise)。 - 语言选择 Kotlin。
- Minimum SDK:建议 API 23 (Android 6.0 Marshmallow) 以平衡功能和覆盖率。
-
核心依赖添加 (
build.gradle.kts (Module:app)):dependencies { // AndroidX Core implementation("androidx.core:core-ktx:1.12.0") implementation("androidx.appcompat:appcompat:1.6.1") implementation("com.google.android.material:material:1.11.0") implementation("androidx.constraintlayout:constraintlayout:2.1.4") // Lifecycle (ViewModel & LiveData) implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2") implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2") implementation("androidx.activity:activity-ktx:1.8.2") // Coroutines implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3") // Retrofit & Moshi (网络请求 & JSON解析) implementation("com.squareup.retrofit2:retrofit:2.9.0") implementation("com.squareup.retrofit2:converter-moshi:2.9.0") implementation("com.squareup.moshi:moshi-kotlin:1.15.0") implementation("com.squareup.okhttp3:logging-interceptor:4.12.0") // 网络日志 // Coil (图片加载) implementation("io.coil-kt:coil:2.5.0") // Location Services (Play Services) implementation("com.google.android.gms:play-services-location:21.0.1") // Hilt (依赖注入 - 可选但强烈推荐) implementation("com.google.dagger:hilt-android:2.48.1") kapt("com.google.dagger:hilt-android-compiler:2.48.1") }
- 同步 Gradle。
核心功能实现 (MVVM 架构)
-
数据层 (Repository & DataSource)
- API 接口 (
WeatherApiService.kt):interface WeatherApiService { @GET("data/2.5/weather") suspend fun getCurrentWeather( @Query("lat") latitude: Double, @Query("lon") longitude: Double, @Query("units") units: String = "metric", // 公制单位 @Query("appid") apiKey: String = YOUR_API_KEY // 替换为真实Key ): Response<WeatherResponse> } - 数据模型 (
WeatherResponse.kt,WeatherData.kt等): 根据 API 返回的 JSON 结构定义 Kotlin 数据类 (使用@JsonClass(generateAdapter = true)配合 Moshi)。 - Repository (
WeatherRepository.kt):class WeatherRepository @Inject constructor(private val apiService: WeatherApiService) { suspend fun getCurrentWeather(lat: Double, lon: Double): Result<WeatherData> { return try { val response = apiService.getCurrentWeather(lat, lon) if (response.isSuccessful && response.body() != null) { Result.Success(response.body()!!.toWeatherData()) // 转换API模型为领域模型 } else { Result.Error(Exception("API error: ${response.code()} ${response.message()}")) } } catch (e: Exception) { Result.Error(e) } } sealed class Result<out T> { data class Success<out T>(val data: T) : Result<T>() data class Error(val exception: Exception) : Result<Nothing>() } }
- API 接口 (
-
领域层 (Use Cases – 可选但推荐)
- 封装特定业务逻辑。
GetCurrentWeatherUseCase调用 Repository。
- 封装特定业务逻辑。
-
ViewModel 层 (
WeatherViewModel.kt)@HiltViewModel class WeatherViewModel @Inject constructor( private val getWeatherUseCase: GetCurrentWeatherUseCase, private val locationClient: LocationClient // 自定义位置服务封装 ) : ViewModel() { private val _weatherState = MutableStateFlow<WeatherUiState>(WeatherUiState.Loading) val weatherState: StateFlow<WeatherUiState> = _weatherState init { fetchLocationAndWeather() } private fun fetchLocationAndWeather() { viewModelScope.launch { locationClient.getCurrentLocation().collect { locationResult -> when (locationResult) { is LocationResult.Success -> { fetchWeather(locationResult.location.latitude, locationResult.location.longitude) } is LocationResult.Error -> { _weatherState.value = WeatherUiState.Error(locationResult.exception.message ?: "Unknown location error") } } } } } private suspend fun fetchWeather(lat: Double, lon: Double) { _weatherState.value = WeatherUiState.Loading when (val result = getWeatherUseCase(lat, lon)) { is WeatherRepository.Result.Success -> { _weatherState.value = WeatherUiState.Success(result.data) } is WeatherRepository.Result.Error -> { _weatherState.value = WeatherUiState.Error(result.exception.message ?: "Unknown weather error") } } } fun retry() { fetchLocationAndWeather() } sealed class WeatherUiState { object Loading : WeatherUiState() data class Success(val data: WeatherData) : WeatherUiState() data class Error(val message: String?) : WeatherUiState() } } -
UI 层 (Compose 或 View Binding)

-
使用 Jetpack Compose (
WeatherScreen.kt– 推荐):@Composable fun WeatherScreen(viewModel: WeatherViewModel = hiltViewModel()) { val weatherState by viewModel.weatherState.collectAsStateWithLifecycle() Column( modifier = Modifier .fillMaxSize() .padding(16.dp), horizontalAlignment = Alignment.CenterHorizontally ) { when (val state = weatherState) { is WeatherViewModel.WeatherUiState.Loading -> CircularProgressIndicator() is WeatherViewModel.WeatherUiState.Success -> { WeatherInfoDisplay(state.data) } is WeatherViewModel.WeatherUiState.Error -> { Text(text = "Error: ${state.message}", color = Color.Red) Button(onClick = { viewModel.retry() }) { Text("Retry") } } } } }
@Composable
fun WeatherInfoDisplay(weatherData: WeatherData) {
// 使用Coil加载天气图标
AsyncImage(
model = “https://openweathermap.org/img/wn/${weatherData.iconCode}@4x.png”,
contentDescription = weatherData.description,
modifier = Modifier.size(120.dp)
)
Text(text = “${weatherData.temperature}°C”, style = MaterialTheme.typography.displayMedium)
Text(text = weatherData.description, style = MaterialTheme.typography.titleMedium)
Text(text = weatherData.locationName, style = MaterialTheme.typography.bodyLarge)
// 显示更多数据:湿度、风速、气压等…
}使用 View Binding (XML): 在 `Activity`/`Fragment` 中观察 `ViewModel` 的 `LiveData`/`StateFlow` 并更新 UI。 -
-
位置服务 (
LocationClient.kt):- 封装
FusedLocationProviderClient,处理权限请求和位置获取回调,返回Flow<LocationResult>。关键点: 妥善处理ACCESS_FINE_LOCATION/ACCESS_COARSE_LOCATION权限的动态申请。
- 封装
架构优化与进阶技巧
-
依赖注入 (Hilt):
- 使用 Hilt 管理
ApiService,Repository,UseCase,ViewModel,LocationClient等依赖项的生命周期和注入,大幅提升代码可测试性和可维护性,在Application类上添加@HiltAndroidApp。
- 使用 Hilt 管理
-
响应式 UI & 状态管理:
- Compose: 充分利用
StateFlow/State和remember/derivedStateOf管理 UI 状态,保证 UI 与数据源同步。 - View System: 使用
LiveData或StateFlow配合LifecycleOwner安全更新 UI。
- Compose: 充分利用
-
错误处理与用户体验:
- 在
Repository和ViewModel中捕获异常,转换为用户友好的错误状态 (WeatherUiState.Error)。 - 提供明确的加载状态 (
WeatherUiState.Loading) 和重试机制 (retry()函数)。 - 处理网络不可用情况(利用
ConnectivityManager或 OkHttp 拦截器检查网络状态)。
- 在
-
离线支持 (可选):
- 引入 Room 数据库缓存最近的天气数据和位置信息。
- Repository 优先从本地缓存读取数据,同时发起网络请求更新缓存,ViewModel 可展示缓存数据作为占位或离线状态。
-
测试:

- Unit Tests (JUnit): 测试
ViewModel逻辑 (使用TestCoroutineDispatcher)、Repository方法、数据转换、Use Cases。 - Instrumented Tests (Espresso / Compose Test): 测试 UI 交互和状态变化,Mock 网络层和位置服务。
- Unit Tests (JUnit): 测试
发布准备
-
代码混淆 (R8):
- 在
build.gradle.kts (Module:app)中启用minifyEnabled true。 - 配置 ProGuard/Rules (
proguard-rules.pro) 保留必要的类(如 Retrofit 接口、数据模型、Hilt 组件),使用@Keep注解。
- 在
-
应用签名:
- 使用 Android Studio 生成或导入签名密钥库 (Keystore)。
- 在
build.gradle.kts中配置签名信息 (避免将密码硬编码在版本控制中)。
-
资源优化:
- 移除未使用的资源 (
shrinkResources true)。 - 为不同屏幕密度提供合适的图片资源。
- 移除未使用的资源 (
-
持续集成/持续部署 (CI/CD): 使用 GitHub Actions、GitLab CI 或 Jenkins 自动化构建、测试和发布流程。
通过这个项目,你实践了现代安卓开发的精髓:Kotlin协程处理异步、Jetpack组件构建健壮架构、Retrofit进行网络通信、依赖注入管理复杂度,以及Compose构建声明式UI,持续关注Android Developers官方文档和Kotlin协程指南是保持技术领先的关键。
你的挑战: 在实现基础功能后,你会优先添加哪个进阶特性来提升应用价值?是更详细的多天预报、城市搜索收藏功能、天气预警通知,还是深度集成系统主题的动态换肤?分享你的想法在评论区,一起探讨最优解!
原创文章,作者:世雄 - 原生数据库架构专家,如若转载,请注明出处:https://idctop.com/article/25681.html