安卓登录学习笔记

1. 背景与目标 (Background and Goal)

背景:
我们要创建一个用户登录界面。用户输入用户名和密码,点击“登录”按钮。应用会显示一个加载中的“圈圈”(ProgressBar),然后模拟一个耗时2秒的网络请求。根据请求结果,界面会显示“登录成功”或“登录失败”的消息。

核心挑战:

  1. 在等待网络响应的2秒内,如果用户旋转屏幕,应用不能崩溃,加载状态不能丢失(“圈圈”必须继续转),用户输入的内容也不能丢失。
  2. 整个应用的组件(网络服务、数据仓库、ViewModel等)需要被优雅地组织和管理,而不是在Activity里手动创建一大堆对象。

我们的目标:
通过这个例子,你将看到:

  • Hilt 如何像一个总管家,自动创建并“注入”所有需要的对象。
  • RepositoryApiService 如何使用**回调(Callback)**来处理异步的网络请求。
  • ViewModel 如何作为UI的“大脑”,管理所有UI状态(加载状态、错误信息、用户输入),并轻松应对屏幕旋转,保证数据和状态不丢失。
  • Activity 如何变得非常“轻量”,只负责展示 ViewModel 提供的数据和发送用户指令。

2. 登场角色与职责

  • AuthCallback (接口): “汇报合同”。定义了登录任务的结果必须如何汇报(成功或失败)。
  • AuthApiService (模拟的网络服务): “远程服务器接口人”。负责执行真正的(模拟的)登录网络请求,并在完成后,通过 AuthCallback 汇报结果。
  • AuthRepository (数据仓库): “业务逻辑层”。作为 ViewModel 和数据源之间的中间人,它负责调用 AuthApiService
  • LoginViewModel (UI的“大脑”): “状态管理器”。持有所有UI需要的数据和状态(加载中?成功/失败信息?),处理用户的登录请求,并在屏幕旋转后存活下来。
  • LoginActivity (UI界面): “视图展示层”。一个“傻瓜”界面,只负责显示 LoginViewModel 给它的数据,和告诉 LoginViewModel “用户点击了登录按钮”。

数据流向:

  • 指令流: Activity -> ViewModel -> Repository -> ApiService
  • 结果流: ApiService --(Callback)–> Repository --(Callback)–> ViewModel --(LiveData)–> Activity

3. 代码实现:一步步构建

第1步:定义“合同”和“网络工人”

AuthCallback.java

public interface AuthCallback {void onSuccess(String successMessage);void onFailure(String errorMessage);
}

AuthApiService.java

// 这个类模拟与远程服务器的通信
@Singleton // 整个应用共享一个实例
public class AuthApiService {@Inject // Hilt知道如何创建它public AuthApiService() {}// 模拟登录,这是一个异步操作public void login(String username, String password, AuthCallback callback) {System.out.println("【API服务】: 开始向服务器发送登录请求...");// 用Handler模拟2秒的网络延迟new Handler(Looper.getMainLooper()).postDelayed(() -> {if (username.equals("admin") && password.equals("123456")) {// 登录成功,通过callback通知调用者System.out.println("【API服务】: 服务器响应:登录成功!");callback.onSuccess("欢迎回来, " + username);} else {// 登录失败,通过callback通知调用者System.out.println("【API服务】: 服务器响应:用户名或密码错误!");callback.onFailure("用户名或密码错误");}}, 2000);}
}
第2步:创建“业务逻辑层”

AuthRepository.java

@Singleton
public class AuthRepository {private final AuthApiService apiService;@Injectpublic AuthRepository(AuthApiService apiService) {this.apiService = apiService;}// Repository的方法,它接收ViewModel的指令,并把callback传递给下一层public void login(String username, String password, AuthCallback callback) {System.out.println("【仓库层】: 收到登录请求,正在转交给API服务...");apiService.login(username, password, callback);}
}
第3步:创建 Hilt 的“制造手册”

di/module/AppModule.java

@Module
@InstallIn(SingletonComponent.class)
public class AppModule {// 因为 AuthApiService 和 AuthRepository 的构造函数都被@Inject标记了,// Hilt可以自动创建它们,所以这个Module暂时可以是空的。// 如果它们来自第三方库,我们就在这里写 @Provides 方法。
}

讲解:在这个例子中,因为我们的类构造函数很简单,都用了 @Inject,所以 Hilt 可以自动处理。但保留这个文件是好的实践,未来可以用来提供 Room, Retrofit 等复杂对象。

第4. 创建 UI 的“大脑” (ViewModel)

LoginViewModel.java

@HiltViewModel // 1. 标记为Hilt管理的ViewModel
public class LoginViewModel extends ViewModel {private final AuthRepository authRepository;// 2. 定义UI所需的所有状态,并用LiveData包裹private final MutableLiveData<Boolean> _isLoading = new MutableLiveData<>(false);public final LiveData<Boolean> isLoading = _isLoading;private final MutableLiveData<String> _loginResult = new MutableLiveData<>();public final LiveData<String> loginResult = _loginResult;@Inject // 3. Hilt会自动注入AuthRepositorypublic LoginViewModel(AuthRepository authRepository) {this.authRepository = authRepository;}// 4. 定义一个方法,供UI层调用以触发登录逻辑public void onLoginClicked(String username, String password) {System.out.println("【ViewModel】: 收到UI的登录点击事件!");_isLoading.setValue(true); // 更新状态为“正在加载”// 5. 调用Repository,并提供一个匿名的Callback实现来处理结果authRepository.login(username, password, new AuthCallback() {@Overridepublic void onSuccess(String successMessage) {System.out.println("【ViewModel】: 收到成功回调!正在更新UI状态...");_loginResult.postValue(successMessage);_isLoading.postValue(false); // 更新状态为“加载结束”}@Overridepublic void onFailure(String errorMessage) {System.out.println("【ViewModel】: 收到失败回调!正在更新UI状态...");_loginResult.postValue(errorMessage);_isLoading.postValue(false); // 更新状态为“加载结束”}});}
}
第5步:创建“轻量”的 UI 界面

activity_login.xml (布局文件)

<LinearLayout ...><EditText android:id="@+id/username_input" ... /><EditText android:id="@+id/password_input" ... /><Button android:id="@+id/login_button" android:text="登录" ... /><ProgressBar android:id="@+id/loading_spinner" android:visibility="gone" ... />
</LinearLayout>

LoginActivity.java

@AndroidEntryPoint
public class LoginActivity extends AppCompatActivity {private LoginViewModel viewModel;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_login);// 用标准方式获取Hilt准备好的ViewModelviewModel = new ViewModelProvider(this).get(LoginViewModel.class);EditText usernameInput = findViewById(R.id.username_input);EditText passwordInput = findViewById(R.id.password_input);Button loginButton = findViewById(R.id.login_button);ProgressBar loadingSpinner = findViewById(R.id.loading_spinner);// --- UI 操作:只负责通知 ViewModel ---loginButton.setOnClickListener(v -> {String username = usernameInput.getText().toString();String password = passwordInput.getText().toString();viewModel.onLoginClicked(username, password);});// --- UI 观察:只负责根据 ViewModel 的数据更新自己 ---viewModel.isLoading.observe(this, isLoading -> {System.out.println("【Activity】: 观察到isLoading状态变为 " + isLoading);loadingSpinner.setVisibility(isLoading ? View.VISIBLE : View.GONE);loginButton.setEnabled(!isLoading);});viewModel.loginResult.observe(this, resultMessage -> {System.out.println("【Activity】: 观察到loginResult变化: " + resultMessage);Toast.makeText(this, resultMessage, Toast.LENGTH_SHORT).show();});}
}

4. 运行与分析:屏幕旋转的考验

  1. 输入正确的用户名(“admin”)和密码(“123456”),点击登录。
    • 日志会显示指令从 Activity -> ViewModel -> Repository -> ApiService 传递。
    • UI上,登录按钮会变灰,加载“圈圈”开始旋转。
  2. 在2秒的等待时间内,立刻旋转手机屏幕!
    • LoginActivity 被销毁并重建。
    • LoginViewModel 还活着,它的 _isLoading 状态依然是 true
    • 新的 LoginActivity 创建后,重新获取到同一个 ViewModel 实例
    • viewModel.isLoading.observe(...) 被重新设置,它立刻收到了当前的 true 值,所以加载“圈圈”依然在旋转,按钮依然是灰色。UI状态完美恢复!
  3. 2秒结束后…
    • AuthApiService 的回调被触发,最终调用到 ViewModel 里的 onSuccess
    • ViewModel 调用 _isLoading.postValue(false)_loginResult.postValue("欢迎回来...")
    • 即使是在新的 Activity 实例中,它的 Observer 也能立刻收到这两个更新。
    • UI上,加载“圈圈”消失,按钮恢复,并弹出一个“登录成功”的 Toast

结论:在这个例子中,Hilt 负责组装,Callback 负责异步通信,ViewModel 负责状态保持。三者结合,构成了一套非常强大、健壮、且易于测试的现代安卓应用架构,轻松解决了异步操作和屏幕旋转带来的各种难题。

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

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

相关文章

Git(三):分支管理

文章目录 Git(三)&#xff1a;分支管理理解分支创建分支切换分支合并分支删除分支合并冲突分支管理策略分支策略Bug分支删除临时分支 Git(三)&#xff1a;分支管理 理解分支 本章介绍Git的杀手级功能之一&#xff1a;分支 分支就 是科幻电影里面的平行宇宙&#xff0c;当你正…

电子电气架构 --- 电气架构基础(汽车电子)

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 做到欲望极简,了解自己的真实欲望,不受外在潮流的影响,不盲从,不跟风。把自己的精力全部用在自己。一是去掉多余,凡事找规律,基础是诚信;二是…

RestClient 功能介绍、完整使用示例演示, 和RestTemplate、WebClient 对比

RestClient功能介绍 RestClient是Spring Framework 6.1版本引入的同步HTTP客户端&#xff0c;旨在替代老旧的RestTemplate&#xff0c;提供更现代、流畅的API设计。其核心特点包括&#xff1a; 流畅API&#xff08;Fluent API&#xff09;&#xff1a; 支持链式调用&#xff0…

VM经常遇见的运行慢几种情况、以及设置方法

大家好,我是东哥说-MES 启动虚拟机是提示如下内容 “无法打开内核设备“\.\VMCIDev\VMX”: 操作成功完成。是否在安装 VMware Workstation 后重新引导? 模块“DevicePowerOn”启动失败。 未能启动虚拟机。” 2.用记事本打开安装目录下TIA Portal STEP7 Prof Safety WinCC …

【C++语法】类和对象(4)——日期类和const成员函数

6.类和对象&#xff08;4&#xff09; 文章目录 6.类和对象&#xff08;4&#xff09;回顾简单日期类的实现代码补充&#xff1a;前置与后置的重载区别补充&#xff1a;关于流插入运算符&#xff08;<<&#xff09;的解释拓展&#xff1a;仿照流插入操作符(<<)的作…

当凌晨的键盘声,遇见黎明的星光​

地铁玻璃映出你困倦的脸&#xff0c;耳机里的音乐循环到第 17 遍&#xff0c;早高峰的人群像沙丁鱼罐头般挤压着你。这是你每天雷打不动的三小时通勤路&#xff0c;从城市边缘到写字楼林立的 CBD&#xff0c;窗外的风景换了四季&#xff0c;而你始终困在摇晃的车厢里&#xff0…

Web Worker技术详解与应用场景

我们来详细探讨一下 Web Worker。它是现代 Web 开发中解决 JavaScript 单线程限制、提升应用性能和响应能力的关键技术。 核心问题&#xff1a;JavaScript 的单线程模型 浏览器 UI 线程&#xff08;主线程&#xff09;&#xff1a;JavaScript 在浏览器中默认运行在单个线程&a…

React Next快速搭建前后端全栈项目并部署至Vercel

很好&#xff0c;你是想搞清楚Next.js 的后端结构和传统 Node Express 的区别对比&#xff0c;我来整理一套结构化、精准、对面试有说服力的解答&#xff0c;并附示意结构图。 01Next vs Express 、## ⚡️1️⃣ Next.js 后端是怎么构建的 Next.js 在默认情况下本身就集成后…

【T宝客户项目解决过程】01-模型训练

1 项目需求描述 博主自己开了一家T宝店&#xff0c;有一个客户有这个需求&#xff1a;有一大堆图像&#xff0c;大概有10多万张图&#xff0c;都是比较小尺寸的图。各种类型都有&#xff0c;我们想要通过将不同类型发图像进行分开&#xff0c;如何实现呢&#xff1f; 2 思路 …

如何在中将网络改为桥接模式并配置固定IP地址

在使用服务器搭建虚拟机的过程中&#xff0c;我们发现有许多场景需要将虚拟机的网络配置为桥接模式&#xff0c;并为其设置固定的IP地址。为了帮助大家更高效地进行网络配置&#xff0c;提升虚拟机的连接稳定性和管理便捷性&#xff0c;我们总结了这篇指南&#xff0c;介绍如何…

强化学习 - 基于策略的Reinforce算法

&#x1f3af; REINFORCE 策略梯度算法推导&#xff08;完整&#xff09; 1. 目标函数定义 我们希望最大化策略的期望回报&#xff1a; J ( θ ) E τ ∼ π θ [ R ( τ ) ] J(\theta) \mathbb{E}_{\tau \sim \pi_\theta} \left[ R(\tau) \right] J(θ)Eτ∼πθ​​[R(τ…

Windows Sever Core安装及常用命令

一、Windows Sever Core 在安装 Windows Server 的过程中&#xff0c;可以选择“Server Core”&#xff08;核心安装&#xff09;这种没有图形用户界面&#xff08;GUI&#xff09;的安装方式。这种模式下&#xff0c;Windows Server 主要通过命令行或远程管理进行配置和维护&…

Java 单元测试实战:以“两数之和”为例,讲透测试思维

&#x1f31f;Java 单元测试实战&#xff1a;以“两数之和”为例&#xff0c;讲透测试思维 在 Java 开发中&#xff0c;单元测试不仅是验证功能正确的手段&#xff0c;更是衡量开发者是否具备“测试思维”的标志。今天我们通过一个最简单的功能——“两数之和”来系统讲解如何…

Bootstrap 5学习教程,从入门到精通,Bootstrap 5 提示框(Tooltips) 语法知识点及案例(21)

Bootstrap 5 提示框(Tooltips) 语法知识点及案例 一、提示框语法知识点 1. 基本概念 提示框(Tooltips)是当用户悬停在元素上时显示的小浮动标签&#xff0c;用于提供额外信息。 2. 核心属性 data-bs-toggle"tooltip" - 标识元素为提示框触发器title - 提示框显示…

设计模式实战指南:从源码解析到Java后端架构的艺术

&#x1f3af; 设计模式实战指南&#xff1a;从源码解析到Java后端架构的艺术 概述 本文档基于设计模式分类&#xff0c;详细介绍Java后端开发中各种设计模式的实际应用场景&#xff0c;结合Spring、MyBatis、Redis等主流框架的源码分析&#xff0c;帮助开发者深入理解设计模…

Python Arrow 库详解:更智能的日期时间处理

1. 安装与基本用法 安装 Arrow pip install arrow基本使用示例 import arrow# 获取当前时间 now arrow.now() print(now) # 输出: 2023-07-15T14:30:45.12345608:00# 创建特定时间 dt arrow.get(2023-07-15 14:30:00, YYYY-MM-DD HH:mm:ss) print(dt) # 输出: 2023-07-15T…

大家电破渠道困局,小家电攻用户体验,云徙有何解法?

中国家电行业正经历深刻转型。 自2018年市场规模触及8400亿顶峰后&#xff0c;行业从增量竞争转向存量博弈。与此同时&#xff0c;线上渠道在2023年首次以58%的占比超越线下&#xff0c;其中扫地机器人等小家电品类线上渗透率突破90%。消费需求也在同步重构——从家庭场景向个…

DMDPC多副本数据分布测试

需求&#xff1a;测试建表和插入数据是否会根据分布列进行自动分发。 验证方法&#xff1a;1&#xff09;准备基础环境&#xff1a;创建用户和表空间。2&#xff09;创建数据分布测试表&#xff0c;并插入数据。3&#xff09;查询指定分区数据&#xff0c;验证数据分布情况。 …

Qt/C++开发监控GB28181系统/rtp解包/jrtplib库的使用/同时支持udp和tcp被动和主动三种方式解包

一、前言说明 通过sip协议仅仅是交互&#xff0c;音视频数据的收发最终并不是通过sip传输的&#xff0c;而是通过将数据打包成rtp的格式再通过udp或者tcp通信的&#xff0c;sip协议仅仅是告知对方待会要往哪里发数据&#xff0c;是udp还是tcp。由于数据都是rtp包格式&#xff…

集群聊天服务器---muduo库的使用

使用 C 和 muduo 网络库来实现一个简单的聊天服务器和客户端。 服务器端&#xff1a; class chatServer { public:// 初始化TcpServerchatServer(muduo::net::EventLoop *loop,const muduo::net::InetAddress &listenAddr): _server(loop, listenAddr, "chatServer&…