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

news2025/1/18 18:47:36

关于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.引用依赖

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

3.定义Constant文件

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

4.编写天气服务接口

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

 这里需要说明一下

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以及请求天气的接口

/**
 * MVVM架构的应用程序类,提供全局的应用管理功能。
 */
public class MVVMApplication extends Application {

    private static MVVMApplication instance;
    // 执行器服务,用于在后台线程中执行数据库操作或其他耗时操作
    public static final ExecutorService EXECUTOR_SERVICE = Executors.newSingleThreadExecutor();
    
    /**
     * 获取MVVMApplication的单例实例。
     * 
     * @return MVVMApplication的全局唯一实例。
     */
    public static MVVMApplication getInstance() {
        return instance;
    }

    /**
     * 应用创建时调用的函数,用于初始化应用全局变量。
     */
    @Override
    public void onCreate() {
        super.onCreate();
        instance = this; // 初始化全局应用实例
    }

    // Retrofit实例,用于配置和创建网络请求
    private static Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(WEATHER_API_URL) // 设置基础URL
            .addConverterFactory(GsonConverterFactory.create()) // 使用Gson进行数据转换
            .build();

    /**
     * 获取天气服务接口的实例,用于发起天气相关的网络请求。
     * 
     * @return WeatherService接口的实例。
     */
    public static WeatherService getWeatherService() {
        return retrofit.create(WeatherService.class);
    }
}

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

6.ViewModel层代码

/**
 * 天气视图模型类,用于处理与天气相关的数据逻辑。
 */
public class WeatherViewModel extends ViewModel {
    // 存储天气数据的 LiveData 对象
    private MutableLiveData<WeatherResponse> weatherLiveData = new MutableLiveData<>();
    /**
     * 获取天气数据的 LiveData 对象。
     *
     * @return LiveData<WeatherResponse> 天气数据的 LiveData 对象。
     */
    public LiveData<WeatherResponse> getWeatherLiveData() {
        return weatherLiveData;
    }
    // 存储错误代码的 LiveData 对象
    private MutableLiveData<Integer> errorCodeLiveData = new MutableLiveData<>();
    /**
     * 获取错误代码的 LiveData 对象。
     *
     * @return LiveData<Integer> 错误代码的 LiveData 对象。
     */
    public LiveData<Integer> getErrorCodeLiveData() {
        return errorCodeLiveData;
    }

    /**
     * 根据提供的城市名和 API 密钥获取天气信息。
     *
     * @param city 要查询天气的城市名。
     * @param apiKey 用于查询天气的 API 密钥。
     */
    public void fetchWeather(String city, String apiKey) {
        WeatherService service = MVVMApplication.getWeatherService();
        Call<WeatherResponse> call = service.getWeather(city, apiKey);
        EXECUTOR_SERVICE.execute(() -> {
            call.enqueue(new Callback<WeatherResponse>() {
                @Override
                public void onResponse(Call<WeatherResponse> call, Response<WeatherResponse> response) {
                    if (response.isSuccessful()) {
                        // 成功获取天气数据时,更新 LiveData
                        weatherLiveData.postValue(response.body());
                    } else {
                        // 获取天气数据失败时,发布错误代码
                        errorCodeLiveData.postValue(response.code());
                    }
                }
                @Override
                public void onFailure(Call<WeatherResponse> call, Throwable t) {
                    // 请求天气数据失败时,发布错误代码
                    errorCodeLiveData.postValue(-1);
                }
            });
        });
    }
    
}

7.Activity代码

/**
 * 天气活动类,用于展示和更新天气信息。
 */
public class WeatherActivity extends AppCompatActivity {

    private ActivityWeatherBinding binding; // 数据绑定对象
    private WeatherViewModel viewModel; // 视图模型对象

    /**
     * 在活动创建时调用。
     *
     * @param savedInstanceState 如果活动之前被销毁,这参数包含之前的状态。如果活动没被销毁之前,这参数是null。
     */
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this); // 启用边缘到边缘的UI
        // 设置数据绑定
        binding = DataBindingUtil.setContentView(this, R.layout.activity_weather);

        // 设置视图的内边距,以适应系统边框
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        // 初始化视图模型
        viewModel = new ViewModelProvider(this).get(WeatherViewModel.class);
        binding.setViewModel(viewModel); // 将视图模型和绑定对象关联
        initObservers(); // 初始化观察者

        // 设置获取天气信息的点击监听器
        binding.btnGetWeather.setOnClickListener(v -> {
            viewModel.fetchWeather(CITY_NAME, WEATHER_API_KEY); // 触发获取天气数据
        });
    }

    /**
     * 初始化观察者,用于监听视图模型中的数据变化并更新UI。
     */
    private void initObservers() {
        // 观察实时天气数据
        viewModel.getWeatherLiveData().observe(this, weatherResponse -> {
            if (weatherResponse != null && weatherResponse.getErrorCode() == 0) {
                // 处理成功的天气响应,更新UI
                Optional.ofNullable(weatherResponse.getResult())
                        .map(WeatherResponse.Result::getRealtime)
                        .ifPresent(realtime -> {
                            StringBuilder stringBuilder = new StringBuilder("长垣实时天气:" + "\n");
                            stringBuilder.append("天气:" + realtime.getInfo() + "\n");
                            stringBuilder.append("温度:" + realtime.getTemperature() + "\n");
                            stringBuilder.append("湿度:" + realtime.getHumidity() + "%" + "\n");
                            stringBuilder.append("风向:" + realtime.getDirect() + "\n");
                            stringBuilder.append("风力:" + realtime.getPower() + "\n");
                            stringBuilder.append("空气质量:" + realtime.getAqi() + "分" + "\n");
                            binding.tvWeather.setText(stringBuilder.toString());
                        });
            } else {
                // 处理失败的天气响应,显示错误信息
                binding.tvWeather.setText("获取天气失败");
            }
        });

        // 观察错误码,用于进一步处理错误情况
        viewModel.getErrorCodeLiveData().observe(this, errorCode -> {
            if (errorCode != null) {
                // TODO: 根据错误码进行相应的错误处理
                Log.i("WeatherActivity", "Error Code: " + errorCode);
            }
        });
    }
}

8.布局文件

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="viewModel"
            type="com.example.mvvmdemo.ui.weather.WeatherViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/main"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ui.weather.WeatherActivity">

        <TextView
            android:id="@+id/tv_weather"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="长垣实时天气:"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/btn_get_weather"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            android:text="更新天气"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

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

@POST("/simpleWeather/query")
Call<WeatherResponse> getWeather(@Body WeatherRequest request);

// WeatherRequest 类示例
public class WeatherRequest {
    private String city;
    private String key;

    // 构造函数、getter、setter...
}

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1622614.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

模块三:二分——162.寻找峰值

文章目录 题目描述算法原理解法一&#xff1a;暴力查找解法二&#xff1a;二分查找 代码实现解法一&#xff1a;暴力查找解法二&#xff1a;CJava 题目描述 题目链接&#xff1a;162.寻找峰值 根据题意&#xff0c;需要使用O(log N)的时间复杂度来解决&#xff0c;得出本道题…

HTTP协议的总结

参考 https://www.runoob.com/http/http-tutorial.html 1.简介 HTTP&#xff08;超文本传输协议&#xff0c;Hypertext Transfer Protocol&#xff09;是一种用于从网络传输超文本到本地浏览器的传输协议。它定义了客户端与服务器之间请求和响应的格式。HTTP 工作在 TCP/IP 模…

javaWeb项目-邮票鉴赏系统功能介绍

项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;springboot、SSM、vue、MYSQL、MAVEN 数据库工具&#xff1a;Navicat、SQLyog 1、Java技术 Java 程…

《Linux运维实战:基于银河麒麟V10+鲲鹏920CPU部署DM8数据库主备集群》

总结&#xff1a;整理不易&#xff0c;如果对你有帮助&#xff0c;可否点赞关注一下&#xff1f; 更多详细内容请参考&#xff1a;Linux运维实战总结 一、安装前准备 1.1、硬件环境 数据守护集群安装部署前需要额外注意网络环境和磁盘 IO 配置情况&#xff0c;其他环境配置项建…

大春资料分析刷题班

大春资料分析刷题班&#xff0c;以其独特的教学方法和丰富的实战经验&#xff0c;深受广大学员喜爱。课程中&#xff0c;大春老师不仅深入剖析资料分析题的解题技巧&#xff0c;还结合大量真题进行实战演练&#xff0c;让学员们在刷题中不断提升解题速度和准确率。同时&#xf…

DHCP Relay配置与抓包

前言&#xff1a;DHCP请求报文是以广播包方式发送的&#xff0c;当DHCP服务器与DHCP客户端不在同一网段时&#xff0c;就需要在三层网关设备配置DHCP中继功能 。 为能更好理解DHCP Relay功能&#xff0c;建议先看看DHCP Server的内容 https://blog.csdn.net/weixin_58574637…

element -ui 横向时间轴,时间轴悬浮对应日期

效果&#xff1a; <el-tabs v-model"activeName" type"card" tab-click"handleClick"><el-tab-pane label"周期性巡视" name"zqxxs" key"zqxxs" class"scrollable-tab-pane"><div v-if…

WEP、WPA、WPA2 和 WPA3:区别和说明

无线网络安全是保持在线安全的一个重要因素。通过不安全的链路或网络连接到互联网是一种安全风险&#xff0c;可能会导致数据丢失、帐户凭据泄露&#xff0c;以及他人在您的网络上安装恶意软件。必须使用适当的 Wi-Fi 安全措施 - 但在这样做时&#xff0c;也必须了解不同的无线…

全新消费理念:探索消费增值的奥秘与价值

亲爱的朋友们&#xff0c;你们好&#xff01;今天我要和大家分享一种新颖的消费模式——消费增值&#xff0c;它能让我们的每一次消费都充满价值&#xff01; 在传统消费观念中&#xff0c;我们支付金钱&#xff0c;获得商品或服务&#xff0c;然后这些就逐渐淡出我们的生活。然…

WebSocket connection to ‘ws://10.151.2.241:8080/ws‘ failed:

在vue3项目中出现以下错误 这个错误表明在尝试建立到 ws://10.151.18.185:8080/ws 的WebSocket连接时失败了。WebSocket是一种用于实现双向通信的协议&#xff0c;这种错误通常发生在以下情况下&#xff1a; 1. 服务器不可达&#xff1a;可能服务器 10.151.18.185 不可用&…

FA-128晶振用于医疗设备

血糖仪已成为家庭常用的医疗设备,日本爱普生晶振公司生产的2016封装,32MHz贴片晶振可完美应用于医疗器械血糖仪,此款晶振订货型号为X1E000251005900晶振,型号为FA-128,负载电容分8PF,精度10PPM,其尺寸参数为2.0x1.6x0.5mm,符合ROHS标准且无铅,具有封装尺寸超小,高精度,频率范围…

【Python爬虫】爬取淘宝商品数据——新手教程

大数据时代&#xff0c; 数据收集不仅是科学研究的基石&#xff0c; 更是企业决策的关键。 然而&#xff0c;如何高效地收集数据 成了摆在我们面前的一项重要任务。 本文将为你揭示&#xff0c; 一系列实时数据采集方法&#xff0c; 助你在信息洪流中&#xff0c; 找到…

【前端缓存】localStorage是同步还是异步的?为什么?

写在开头 点赞 收藏 学会 首先明确一点&#xff0c;localStorage是同步的 一、首先为什么会有这样的问题 localStorage 是 Web Storage API 的一部分&#xff0c;它提供了一种存储键值对的机制。localStorage 的数据是持久存储在用户的硬盘上的&#xff0c;而不是内存。这意…

JavaScript实现代码雨

一、功能描述 使用canvas实现一个代码雨的功能&#xff0c;炫一个~~~ 二、上码 html <canvas id"canvas"></canvas> js let canvas document.querySelector(canvas);let ctx canvas.getContext(2d);// screen.availWidth:可视区域的宽度canvas.width…

解决 uniapp uni.getLocation 定位经纬度不准问题

【问题描述】 直接使用uni.getLocation获取经纬度不准确&#xff0c;有几百米的偏移。 【解决办法】 加偏移量 //加偏移 let x longitude let y latitude let x_pi (3.14159265358979324 * 3000.0) / 180.0 let z Math.sqrt(x * x y * y) 0.00002 * Math.sin(y * x_pi)…

时装购物系统,基于 SpringBoot+Vue+MySql 开发的前后端分离的时装购物系统分析设计与实现

目录 一. 前言 二. 功能模块 2.1. 管理员功能模块 2.2. 用户功能模块 2.3. 前台首页功能模块 三. 部分代码实现 四. 源码下载 一. 前言 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的…

回归预测 | Matlab实现ESN回声状态网络的多输入单输出回归预测

回归预测 | Matlab实现ESN回声状态网络的多输入单输出回归预测 目录 回归预测 | Matlab实现ESN回声状态网络的多输入单输出回归预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现ESN回声状态网络的多输入单输出回归预测&#xff08;完整源码和数据)&#x…

盲人安全过马路:科技赋能,独立出行不再难

作为一位资深记者&#xff0c;我长期关注特殊群体的生活现状与科技助力下的改善举措。今天&#xff0c;我要讲述的是盲人朋友在独立出行&#xff0c;尤其是过马路时面临的挑战&#xff0c;以及一款叫做蝙蝠避障的创新辅助应用如何通过实时避障与拍照识别功能&#xff0c;显著提…

Github仓库每日更新京东、淘宝、天猫各品类优惠券

1、⚠️ ⚠️ 每次都是最新的&#xff0c;不保留历史文档&#xff0c;每天批量更新 1 &#xff5e; 3 次&#xff0c;都是精选&#xff0c;钱难赚&#xff0c;屎难吃&#xff0c;能省则省&#xff0c;看到合适的及时上车。 2、Gitee仓库地址 和 Github仓库地址 同步更新。 3、…

一键智能改写文案怎么做,4个方法教你轻松搞定

文案在我们的生活中随处可见&#xff0c;所以文案的重要性也是很大的。而对于文案创作者来说&#xff0c;改写文案是工作中必不可少的任务。但人工手动改写文案是一件非常消耗时间与精力的工作&#xff0c;因此&#xff0c;一键智能改写文案成了创作者们最适合的方法&#xff0…