• Android MVVM架构 + Retrofit完成网络请求


    关于Retrofit,这个应该不是一个很新颖的东西了,简单过一下吧

    1.由Square公司开发,基于Type-safe的REST客户端
    2.使用注解来定义API接口,使得HTTP请求变得简洁且易于维护。
    3.支持同步和异步请求,可与RxJava、Coroutines等响应式编程库结合使用,实现流畅的异步操作。
    4.内置转换器(Gson、Moshi、Jackson等)便于JSON和其他数据格式的序列化与反序列化。
    5.支持自定义拦截器,进行统一的请求头添加、错误处理、日志记录等。
    6.集成了OkHttp作为底层HTTP客户端,受益于其高效的连接复用、缓存策略和灵活的配置选项。

    Retrofit因其强大的功能、清晰的API设计和广泛的社区支持,通常被视为首选。

    先简单看一下本文要实现的效果吧

    下面就一步步实现它吧

    本文使用的开发环境:

             Android Studio Iguana | 2023.2.1 Patch 1

    Gradle版本:

            gradle-8.4-bin.zip 

    本文所使用的天气预报API来源:

            聚合数据(天气预报API可以免费调用)

            接口地址:https://apis.juhe.cn/simpleWeather/query

            APIKey还请自行申请

     1.网络权限

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

    2.引用依赖

    1. //retrofit
    2. implementation 'com.squareup.retrofit2:retrofit:2.9.0'
    3. implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

    3.定义Constant文件

    1. /**
    2. * Constant 类用于存储应用程序中使用的常量。
    3. * 该类不包含任何方法,仅包含静态常量字段。
    4. */
    5. public class Constant {
    6. // 城市名称,示例为"长垣"
    7. public static final String CITY_NAME = "长垣";
    8. // 天气API的URL基础路径
    9. public static final String BASE_URL = "https://apis.juhe.cn";
    10. // 天气API的密钥,用于身份验证
    11. public static final String WEATHER_API_KEY = "你的APIKey";
    12. }

    4.编写天气服务接口

    1. /**
    2. * 天气服务接口,用于获取指定城市的天气信息。
    3. */
    4. public interface WeatherService {
    5. /**
    6. * 获取指定城市的天气信息。
    7. *
    8. * @param cityName 要查询天气的城市名称。
    9. * @param apiKey 用户的API密钥,用于身份验证。
    10. * @return Call 返回一个天气响应的Call对象,允许进行异步请求和响应处理。
    11. */
    12. @GET("/simpleWeather/query")
    13. Call getWeather(@Query("city") String cityName, @Query("key") String apiKey);
    14. }

     这里需要说明一下

    1.@GET表明是GET请求,后面括号内是具体的接口地址,比如我们前面的Constant中定义了BASE_URL,那么实际上getWeather请求的地址是BASE_URL拼接上我们给的/simpleWeather/query,这是请求天气数据的完整地址

    2.(@Query("city") String cityName, @Query("key") String apiKey),这部分表明了GET请求后会拼接两个字段city和key,对应的值分别为cityName和apiKey,拼接的字段需要视具体的api而定

    5.Application中初始化Retrofit以及请求天气的接口

    1. /**
    2. * MVVM架构的应用程序类,提供全局的应用管理功能。
    3. */
    4. public class MVVMApplication extends Application {
    5. private static MVVMApplication instance;
    6. // 执行器服务,用于在后台线程中执行数据库操作或其他耗时操作
    7. public static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
    8. /**
    9. * 获取MVVMApplication的单例实例。
    10. *
    11. * @return MVVMApplication的全局唯一实例。
    12. */
    13. public static MVVMApplication getInstance() {
    14. return instance;
    15. }
    16. /**
    17. * 应用创建时调用的函数,用于初始化应用全局变量。
    18. */
    19. @Override
    20. public void onCreate() {
    21. super.onCreate();
    22. instance = this; // 初始化全局应用实例
    23. }
    24. // Retrofit实例,用于配置和创建网络请求
    25. private static Retrofit retrofit = new Retrofit.Builder()
    26. .baseUrl(WEATHER_API_URL) // 设置基础URL
    27. .addConverterFactory(GsonConverterFactory.create()) // 使用Gson进行数据转换
    28. .build();
    29. /**
    30. * 获取天气服务接口的实例,用于发起天气相关的网络请求。
    31. *
    32. * @return WeatherService接口的实例。
    33. */
    34. public static WeatherService getWeatherService() {
    35. return retrofit.create(WeatherService.class);
    36. }
    37. }

    因为我们使用的是MVVM架构,那么调用接口的逻辑肯定是要放在ViewModel层的,如下

    6.ViewModel层代码

    1. /**
    2. * 天气视图模型类,用于处理与天气相关的数据逻辑。
    3. */
    4. public class WeatherViewModel extends ViewModel {
    5. // 存储天气数据的 LiveData 对象
    6. private MutableLiveData<WeatherResponse> weatherLiveData = new MutableLiveData<>();
    7. /**
    8. * 获取天气数据的 LiveData 对象。
    9. *
    10. * @return LiveData 天气数据的 LiveData 对象。
    11. */
    12. public LiveData<WeatherResponse> getWeatherLiveData() {
    13. return weatherLiveData;
    14. }
    15. // 存储错误代码的 LiveData 对象
    16. private MutableLiveData<Integer> errorCodeLiveData = new MutableLiveData<>();
    17. /**
    18. * 获取错误代码的 LiveData 对象。
    19. *
    20. * @return LiveData 错误代码的 LiveData 对象。
    21. */
    22. public LiveData<Integer> getErrorCodeLiveData() {
    23. return errorCodeLiveData;
    24. }
    25. /**
    26. * 根据提供的城市名和 API 密钥获取天气信息。
    27. *
    28. * @param city 要查询天气的城市名。
    29. * @param apiKey 用于查询天气的 API 密钥。
    30. */
    31. public void fetchWeather(String city, String apiKey) {
    32. WeatherService service = MVVMApplication.getWeatherService();
    33. Call<WeatherResponse> call = service.getWeather(city, apiKey);
    34. EXECUTOR_SERVICE.execute(() -> {
    35. call.enqueue(new Callback<WeatherResponse>() {
    36. @Override
    37. public void onResponse(Call<WeatherResponse> call, Response<WeatherResponse> response) {
    38. if (response.isSuccessful()) {
    39. // 成功获取天气数据时,更新 LiveData
    40. weatherLiveData.postValue(response.body());
    41. } else {
    42. // 获取天气数据失败时,发布错误代码
    43. errorCodeLiveData.postValue(response.code());
    44. }
    45. }
    46. @Override
    47. public void onFailure(Call<WeatherResponse> call, Throwable t) {
    48. // 请求天气数据失败时,发布错误代码
    49. errorCodeLiveData.postValue(-1);
    50. }
    51. });
    52. });
    53. }
    54. }

    7.Activity代码

    1. /**
    2. * 天气活动类,用于展示和更新天气信息。
    3. */
    4. public class WeatherActivity extends AppCompatActivity {
    5. private ActivityWeatherBinding binding; // 数据绑定对象
    6. private WeatherViewModel viewModel; // 视图模型对象
    7. /**
    8. * 在活动创建时调用。
    9. *
    10. * @param savedInstanceState 如果活动之前被销毁,这参数包含之前的状态。如果活动没被销毁之前,这参数是null。
    11. */
    12. protected void onCreate(Bundle savedInstanceState) {
    13. super.onCreate(savedInstanceState);
    14. EdgeToEdge.enable(this); // 启用边缘到边缘的UI
    15. // 设置数据绑定
    16. binding = DataBindingUtil.setContentView(this, R.layout.activity_weather);
    17. // 设置视图的内边距,以适应系统边框
    18. ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
    19. Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
    20. v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
    21. return insets;
    22. });
    23. // 初始化视图模型
    24. viewModel = new ViewModelProvider(this).get(WeatherViewModel.class);
    25. binding.setViewModel(viewModel); // 将视图模型和绑定对象关联
    26. initObservers(); // 初始化观察者
    27. // 设置获取天气信息的点击监听器
    28. binding.btnGetWeather.setOnClickListener(v -> {
    29. viewModel.fetchWeather(CITY_NAME, WEATHER_API_KEY); // 触发获取天气数据
    30. });
    31. }
    32. /**
    33. * 初始化观察者,用于监听视图模型中的数据变化并更新UI。
    34. */
    35. private void initObservers() {
    36. // 观察实时天气数据
    37. viewModel.getWeatherLiveData().observe(this, weatherResponse -> {
    38. if (weatherResponse != null && weatherResponse.getErrorCode() == 0) {
    39. // 处理成功的天气响应,更新UI
    40. Optional.ofNullable(weatherResponse.getResult())
    41. .map(WeatherResponse.Result::getRealtime)
    42. .ifPresent(realtime -> {
    43. StringBuilder stringBuilder = new StringBuilder("长垣实时天气:" + "\n");
    44. stringBuilder.append("天气:" + realtime.getInfo() + "\n");
    45. stringBuilder.append("温度:" + realtime.getTemperature() + "\n");
    46. stringBuilder.append("湿度:" + realtime.getHumidity() + "%" + "\n");
    47. stringBuilder.append("风向:" + realtime.getDirect() + "\n");
    48. stringBuilder.append("风力:" + realtime.getPower() + "\n");
    49. stringBuilder.append("空气质量:" + realtime.getAqi() + "分" + "\n");
    50. binding.tvWeather.setText(stringBuilder.toString());
    51. });
    52. } else {
    53. // 处理失败的天气响应,显示错误信息
    54. binding.tvWeather.setText("获取天气失败");
    55. }
    56. });
    57. // 观察错误码,用于进一步处理错误情况
    58. viewModel.getErrorCodeLiveData().observe(this, errorCode -> {
    59. if (errorCode != null) {
    60. // TODO: 根据错误码进行相应的错误处理
    61. Log.i("WeatherActivity", "Error Code: " + errorCode);
    62. }
    63. });
    64. }
    65. }

    8.布局文件

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <layout xmlns:android="http://schemas.android.com/apk/res/android"
    3. xmlns:app="http://schemas.android.com/apk/res-auto"
    4. xmlns:tools="http://schemas.android.com/tools">
    5. <data>
    6. <variable
    7. name="viewModel"
    8. type="com.example.mvvmdemo.ui.weather.WeatherViewModel" />
    9. </data>
    10. <androidx.constraintlayout.widget.ConstraintLayout
    11. android:id="@+id/main"
    12. android:layout_width="match_parent"
    13. android:layout_height="match_parent"
    14. tools:context=".ui.weather.WeatherActivity">
    15. <TextView
    16. android:id="@+id/tv_weather"
    17. android:layout_width="wrap_content"
    18. android:layout_height="wrap_content"
    19. android:text="长垣实时天气:"
    20. app:layout_constraintBottom_toBottomOf="parent"
    21. app:layout_constraintEnd_toEndOf="parent"
    22. app:layout_constraintStart_toStartOf="parent"
    23. app:layout_constraintTop_toTopOf="parent" />
    24. <Button
    25. android:id="@+id/btn_get_weather"
    26. android:layout_width="wrap_content"
    27. android:layout_height="wrap_content"
    28. android:layout_marginBottom="16dp"
    29. android:text="更新天气"
    30. app:layout_constraintBottom_toBottomOf="parent"
    31. app:layout_constraintEnd_toEndOf="parent"
    32. app:layout_constraintStart_toStartOf="parent" />
    33. </androidx.constraintlayout.widget.ConstraintLayout>
    34. </layout>

    至此,对于以上获取天气预报的功能就完成了,相信大家也基本上对于Retrofit网络请求框架有了一定的了解,不过本文还没有结束,因为前面的网络请求只是GET的,还要有POST请求的范例,简单说明一下吧

    1. @POST("/simpleWeather/query")
    2. Call getWeather(@Body WeatherRequest request);
    3. // WeatherRequest 类示例
    4. public class WeatherRequest {
    5. private String city;
    6. private String key;
    7. // 构造函数、getter、setter...
    8. }

    当然,这只是举例说明POST请求的写法,实际上,大多数天气API通常使用GET方法来查询天气信息,因为这类操作通常是安全且幂等的,符合GET方法的语义。

  • 相关阅读:
    最好的蓝牙耳机是哪种?品牌蓝牙耳机排行榜
    亚马逊鲲鹏测评系统:全自动注册下单及留评
    xrandr修改分辨率与刷新率
    最大似然函数 损失函数 逻辑回归与线性回归的比较
    C++入门之引用(超详解)
    ssm基于微信小程序的警局服务管理系统
    LeetCode - #55 跳跃游戏
    关于方法重写/方法覆盖
    No6-4.从零搭建spring-cloud-alibaba微服务框架,解决微服务间的不鉴权调用等(四,no6-4)
    julia系列2:数据结构
  • 原文地址:https://blog.csdn.net/weixin_53324308/article/details/137948909