如何使用Android聚合数据实现天气预报

发布时间:2021-10-09 16:26:45 作者:iii
来源:亿速云 阅读:227
# 如何使用Android聚合数据实现天气预报

## 目录
1. [前言](#前言)
2. [开发环境准备](#开发环境准备)
3. [聚合数据API申请](#聚合数据api申请)
4. [Android项目基础配置](#android项目基础配置)
5. [网络请求框架集成](#网络请求框架集成)
6. [数据模型设计](#数据模型设计)
7. [API接口封装](#api接口封装)
8. [UI界面设计](#ui界面设计)
9. [数据绑定与展示](#数据绑定与展示)
10. [定位功能集成](#定位功能集成)
11. [数据缓存策略](#数据缓存策略)
12. [错误处理与用户体验](#错误处理与用户体验)
13. [性能优化建议](#性能优化建议)
14. [完整代码示例](#完整代码示例)
15. [总结](#总结)

## 前言

在移动应用开发中,天气预报功能是常见的需求场景。本文将详细介绍如何利用聚合数据平台提供的天气API,在Android应用中实现完整的天气预报功能。通过本教程,您将掌握从API申请到最终界面展示的全流程开发技术。

聚合数据(Juhe.cn)是国内知名的数据服务平台,提供包括天气、快递、股票等多种数据接口。其天气API具有以下优势:
- 数据覆盖全国3000+市县
- 提供7天预报、实时天气、生活指数等完整数据
- 免费套餐适合个人开发者
- 响应速度快,稳定性高

## 开发环境准备

### 基础环境要求
- Android Studio 2022.3.1或更高版本
- JDK 17
- Gradle 8.0
- 最低兼容API Level 23(Android 6.0)

### 项目创建步骤
1. 打开Android Studio选择"New Project"
2. 选择"Empty Activity"模板
3. 配置项目信息:
   - 应用名称:WeatherDemo
   - 包名:com.example.weather
   - 语言:Kotlin
   - 最低SDK:API 23

### 必要依赖配置
在app/build.gradle文件中添加以下依赖:

```gradle
dependencies {
    // 网络请求
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0'
    
    // 协程
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
    
    // 生命周期
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
    
    // 定位服务
    implementation 'com.google.android.gms:play-services-location:21.0.1'
    
    // UI组件
    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
}

聚合数据API申请

注册开发者账号

  1. 访问聚合数据官网(https://www.juhe.cn)
  2. 点击注册,填写基本信息完成账号创建
  3. 登录后进入控制台

申请天气API

  1. 在”数据市场”中搜索”天气”
  2. 选择”天气预报”产品
  3. 点击”申请接口”
  4. 填写申请表单(个人开发者选择免费套餐即可)
  5. 等待审核(通常1小时内完成)

获取API密钥

审核通过后: 1. 进入”我的API” 2. 找到”天气预报”服务 3. 记录下分配的AppKey(如:a1b2c3d4e5f6g7h8i9j0)

接口文档分析

关键接口参数说明: - 请求地址:http://apis.juhe.cn/simpleWeather/query - 请求方式:GET - 参数: - city: 城市名称或ID - key: 申请的AppKey

返回数据示例:

{
    "reason": "查询成功",
    "result": {
        "city": "北京",
        "realtime": {
            "temperature": "23",
            "humidity": "45",
            "info": "晴",
            "wid": "00",
            "direct": "东南风",
            "power": "3级",
            "aqi": "65"
        },
        "future": [
            {
                "date": "2023-05-01",
                "temperature": "12/24℃",
                "weather": "晴",
                "wid": {"day": "00", "night": "00"},
                "direct": "东南风"
            }
        ]
    },
    "error_code": 0
}

Android项目基础配置

网络权限配置

在AndroidManifest.xml中添加权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

网络安全配置

创建res/xml/network_security_config.xml:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">apis.juhe.cn</domain>
    </domain-config>
</network-security-config>

然后在AndroidManifest.xml的application标签中添加:

android:networkSecurityConfig="@xml/network_security_config"

基础架构搭建

采用MVVM架构: - Model: 数据模型和网络请求 - View: Activity/Fragment和XML布局 - ViewModel: 业务逻辑处理

项目包结构:

com.example.weather
├── api
├── model
├── repository
├── view
├── viewmodel
└── utils

网络请求框架集成

Retrofit配置

创建ApiService.kt:

interface ApiService {
    @GET("simpleWeather/query")
    suspend fun getWeather(
        @Query("city") city: String,
        @Query("key") key: String = API_KEY
    ): Response<WeatherResponse>
    
    companion object {
        private const val BASE_URL = "http://apis.juhe.cn/"
        private const val API_KEY = "your_app_key_here" // 替换为实际key
        
        fun create(): ApiService {
            val logger = HttpLoggingInterceptor().apply {
                level = HttpLoggingInterceptor.Level.BODY
            }
            
            val client = OkHttpClient.Builder()
                .addInterceptor(logger)
                .connectTimeout(15, TimeUnit.SECONDS)
                .build()
                
            return Retrofit.Builder()
                .baseUrl(BASE_URL)
                .client(client)
                .addConverterFactory(GsonConverterFactory.create())
                .build()
                .create(ApiService::class.java)
        }
    }
}

协程异常处理

创建扩展函数处理网络请求:

suspend fun <T> safeApiCall(
    apiCall: suspend () -> Response<T>
): Result<T> {
    return try {
        val response = apiCall()
        if (response.isSuccessful) {
            Result.success(response.body()!!)
        } else {
            Result.failure(Exception("API error: ${response.code()}"))
        }
    } catch (e: Exception) {
        Result.failure(e)
    }
}

数据模型设计

根据API返回结构创建数据类:

data class WeatherResponse(
    val reason: String,
    val result: WeatherResult,
    val error_code: Int
)

data class WeatherResult(
    val city: String,
    val realtime: RealtimeWeather,
    val future: List<FutureWeather>
)

data class RealtimeWeather(
    val temperature: String,
    val humidity: String,
    val info: String,
    val wid: String,
    val direct: String,
    val power: String,
    val aqi: String
)

data class FutureWeather(
    val date: String,
    val temperature: String,
    val weather: String,
    val wid: DayNightWid,
    val direct: String
)

data class DayNightWid(
    val day: String,
    val night: String
)

API接口封装

创建WeatherRepository处理业务逻辑:

class WeatherRepository {
    private val apiService = ApiService.create()
    
    suspend fun getWeatherByCity(city: String): Result<WeatherResult> {
        return safeApiCall { apiService.getWeather(city) }.map { it.result }
    }
    
    suspend fun getWeatherByLocation(lat: Double, lon: Double): Result<WeatherResult> {
        // 实际项目中需要先通过逆地理编码获取城市名称
        val city = convertLocationToCity(lat, lon)
        return getWeatherByCity(city)
    }
    
    private suspend fun convertLocationToCity(lat: Double, lon: Double): String {
        // 这里简化为返回固定值,实际应使用地理编码API
        return "北京"
    }
}

UI界面设计

主界面布局

res/layout/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"
    android:padding="16dp">
    
    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/swipeRefresh"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">
                
                <!-- 当前天气 -->
                <include layout="@layout/layout_current_weather"/>
                
                <!-- 天气预报列表 -->
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="未来预报"
                    android:textSize="18sp"
                    android:layout_marginTop="24dp"/>
                
                <androidx.recyclerview.widget.RecyclerView
                    android:id="@+id/rvForecast"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="8dp"/>
            </LinearLayout>
        </ScrollView>
    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>

当前天气布局

res/layout/layout_current_weather.xml:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:background="@drawable/bg_weather_card"
    android:padding="16dp">
    
    <TextView
        android:id="@+id/tvCity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="24sp"
        android:textStyle="bold"/>
        
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:orientation="horizontal">
        
        <ImageView
            android:id="@+id/ivWeatherIcon"
            android:layout_width="64dp"
            android:layout_height="64dp"/>
            
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:orientation="vertical">
            
            <TextView
                android:id="@+id/tvTemperature"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="36sp"/>
                
            <TextView
                android:id="@+id/tvWeatherInfo"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="4dp"
                android:textSize="16sp"/>
        </LinearLayout>
    </LinearLayout>
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:orientation="horizontal"
        android:weightSum="3">
        
        <TextView
            android:id="@+id/tvHumidity"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:drawableTop="@drawable/ic_humidity"
            android:gravity="center"/>
            
        <!-- 类似添加风向、空气质量等视图 -->
    </LinearLayout>
</LinearLayout>

数据绑定与展示

ViewModel实现

创建WeatherViewModel:

class WeatherViewModel : ViewModel() {
    private val repository = WeatherRepository()
    
    private val _weatherData = MutableLiveData<WeatherResult>()
    val weatherData: LiveData<WeatherResult> = _weatherData
    
    private val _isLoading = MutableLiveData<Boolean>()
    val isLoading: LiveData<Boolean> = _isLoading
    
    private val _errorMessage = MutableLiveData<String>()
    val errorMessage: LiveData<String> = _errorMessage
    
    fun fetchWeather(city: String) {
        viewModelScope.launch {
            _isLoading.value = true
            when (val result = repository.getWeatherByCity(city)) {
                is Result.Success -> {
                    _weatherData.value = result.data
                }
                is Result.Failure -> {
                    _errorMessage.value = result.exception.message
                }
            }
            _isLoading.value = false
        }
    }
    
    fun fetchWeather(lat: Double, lon: Double) {
        viewModelScope.launch {
            _isLoading.value = true
            when (val result = repository.getWeatherByLocation(lat, lon)) {
                is Result.Success -> {
                    _weatherData.value = result.data
                }
                is Result.Failure -> {
                    _errorMessage.value = result.exception.message
                }
            }
            _isLoading.value = false
        }
    }
}

Activity实现

MainActivity.kt:

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    private val viewModel: WeatherViewModel by viewModels()
    private lateinit var forecastAdapter: ForecastAdapter
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        setupViews()
        setupObservers()
        loadInitialData()
    }
    
    private fun setupViews() {
        forecastAdapter = ForecastAdapter()
        binding.rvForecast.apply {
            layoutManager = LinearLayoutManager(this@MainActivity)
            adapter = forecastAdapter
        }
        
        binding.swipeRefresh.setOnRefreshListener {
            viewModel.weatherData.value?.let {
                viewModel.fetchWeather(it.city)
            } ?: loadInitialData()
        }
    }
    
    private fun setupObservers() {
        viewModel.weatherData.observe(this) { weather ->
            updateCurrentWeather(weather)
            forecastAdapter.submitList(weather.future)
        }
        
        viewModel.isLoading.observe(this) { isLoading ->
            binding.swipeRefresh.isRefreshing = isLoading
        }
        
        viewModel.errorMessage.observe(this) { message ->
            Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
        }
    }
    
    private fun loadInitialData() {
        // 默认加载北京天气
        viewModel.fetchWeather("北京")
    }
    
    private fun updateCurrentWeather(weather: WeatherResult) {
        binding.tvCity.text = weather.city
        binding.tvTemperature.text = "${weather.realtime.temperature}℃"
        binding.tvWeatherInfo.text = weather.realtime.info
        binding.tvHumidity.text = "${weather.realtime.humidity}%"
        
        // 根据天气代码设置图标
        val iconRes = when(weather.realtime.wid) {
            "00" -> R.drawable.ic_sunny
            "01" -> R.drawable.ic_cloudy
            // 其他天气代码处理
            else -> R.drawable.ic_unknown
        }
        binding.ivWeatherIcon.setImageResource(iconRes)
    }
}

定位功能集成

定位权限处理

在Activity中添加权限请求:

private val locationPermissionRequest = registerForActivityResult(
    ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
    when {
        permissions.getOrDefault(Manifest.permission.ACCESS_FINE_LOCATION, false) -> {
            getCurrentLocation()
        }
        permissions.getOrDefault(Manifest.permission.ACCESS_COARSE_LOCATION, false) -> {
            getCurrentLocation()
        }
        else -> {
            // 权限被拒绝,使用默认城市
            viewModel.fetchWeather("北京")
        }
    }
}

private fun checkLocationPermission() {
    when {
        ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.ACCESS_FINE_LOCATION
        ) == PackageManager.PERMISSION_GRANTED -> {
            getCurrentLocation()
        }
        shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION) -> {
            showPermissionExplanationDialog()
        }
        else -> {
            locationPermissionRequest.launch(arrayOf(
                Manifest.permission.ACCESS_FINE_LOCATION,
                Manifest.permission.ACCESS_COARSE_LOCATION
            ))
        }
    }
}

private fun getCurrentLocation() {
    val locationClient = LocationServices.getFusedLocationProviderClient(this)
    try {
        locationClient.lastLocation
            .addOnSuccessListener { location ->
                location?.let {
                    viewModel.fetchWeather(it.latitude, it.longitude)
                } ?: run {
                    viewModel.fetchWeather("北京")
                }
            }
    } catch (e: SecurityException) {
        Log.e("Location", "Error getting location", e)
    }
}

数据缓存策略

Room数据库集成

  1. 添加Room依赖:
implementation 'androidx.room:room-ktx:2.5.2'
kapt 'androidx.room:room-compiler:2.5.2'
  1. 创建数据库实体:
@Entity(tableName = "weather_cache")
data class WeatherCache(
    @PrimaryKey val city: String,
    val timestamp: Long,
    @ColumnInfo(name = "weather_data") val weatherData: String
)
推荐阅读:
  1. 聚合数据Android SDK实现天气查询
  2. 聚合数据Android SDK 短信验证演示示例

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java android

上一篇:如何使用python函数的文档字符串

下一篇:如何使用Python映射和过滤以及缩减函数

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》