下面开始正文:
在移动互联网时代,用户身份验证是绝大多数应用的基础模块。从电商平台到社交网络,从企业内部 OA 到金融服务,所有需要保护用户数据隐私和个性化体验的产品,都离不开登录与注册功能。一个完善的登录/注册系统,不仅需要实现基本的账号与密码校验,还需兼顾安全性、易用性、可扩展性和性能。本教程将使用原生 Android 技术栈,以 Java 语言为主,从零开始构建一个完整的登录与注册模块,涵盖:
需求分析与系统架构
前端 UI 设计与交互
本地表单验证与输入校验
网络请求与接口调用(RESTful API)
后端简单模拟(可替换为真实服务器)
安全策略(加密、Token、HTTPS、防重放)
本地持久化(SharedPreferences、数据库)
整体项目结构与模块化
完整、整合版代码,含 Activity/Fragment、Adapter、ViewModel(MVVM)、Retrofit+OkHttp、Room、Data Binding 等
代码解读:方法职责与调用流程
性能优化与 UI 动画
测试策略:单元测试、UI 测试
发布与运维:混淆、CI/CD 集成
本教程全文超 10000 字,注释详尽,适合作为技术博客参考或企业内部培训资料。
Activity/Fragment:承载 UI 和交互逻辑;
ViewModel(Jetpack MVVM):持有 UI 数据,并与 Repository 通信;
Repository:负责数据获取,分为远程数据源(网络)和本地数据源(数据库、缓存);
数据绑定(Data Binding / View Binding):简化布局与代码的耦合;
LiveData / StateFlow:LifeCycle 关联的数据可观察者,用于 UI 更新;
注册:用户输入用户名、邮箱/手机、密码;本地校验 → 提交到服务器 → 服务器校验唯一性 → 创建账号 → 返回 Token;
登录:用户输入账号与密码 → 客户端本地校验 → 提交到服务器 → 服务器校验凭证 → 返回 Token + 用户信息;
Token 管理:保存到安全存储(EncryptedSharedPreferences / Keystore);后续网络请求带上 Token;
退出登录:清理本地存储,跳转登录页;
HTTPS:使用 SSL/TLS 加密通道,保护传输数据安全;
Retrofit + OkHttp:构建网络请求框架,支持拦截器、日志、超时重试;
Interceptor:
添加公共请求头(Token、Content-Type);
日志拦截(打印请求与响应);
输入加密:
本地进行 MD5、SHA-256 等单向哈希(不可逆);
或使用 AES 对称加密传输;
防重放攻击:在请求中添加时间戳与随机数,服务器进行校验;
SharedPreferences:轻量级键值对,适合存储 Token、配置;
EncryptedSharedPreferences:AndroidX 提供,自动加密存储;
Room:ORM 框架,管理本地用户数据缓存、登录状态历史记录;
DataStore:Jetpack 新一代数据存储方案,基于 Kotlin 协程和 Flow;
实时校验:通过 TextWatcher
监听输入,判断格式合法性(非空、长度、正则)并给出即时反馈;
按钮状态控制:当表单合法时,启用“登录/注册”按钮,否则禁用并显示提示;
错误提示:使用 TextInputLayout.setError()
展示错误信息;
加载状态:网络请求期间显示 ProgressBar
或 Dialog
,防止重复点击;
UI 动画:按钮点击、页面转场平滑动画,提升交互体验;
功能模块划分
UI 层:LoginActivity
、RegisterActivity
,或统一 AuthFragment
;
ViewModel 层:AuthViewModel
,持有 LiveData:loginState
、registerState
;
Repository 层:AuthRepository
,封装 Retrofit 网络请求,返回 Result
数据层:UserDao
(Room),TokenDao
;
网络层:ApiService
接口,配置地址、路径、请求体;
流程图
用户输入 → 表单校验 → ViewModel调用Repository →
Repository发起网络请求 →
成功:保存Token到EncryptedSharedPreferences & Room → 更新loginState(成功)
失败:更新loginState(失败, 错误信息)
核心技术选型
Retrofit 2:注解式 API 声明,支持 Gson/Kotlinx Serialization;
OkHttp Interceptor:全局日志与 Token 注入;
Room:自动生成 SQLite 操作代码;
EncryptedSharedPreferences:系统级加密存储;
LiveData + Data Binding:自动管理生命周期与 UI 刷新;
安全要点
敏感字段不本地明文存储;
所有网络请求强制 HTTPS;
使用最新安全库,如 Google BoringSSL;
防止 SQL 注入:Room 已自动防护;
app/
├─ java/
│ └─ com.example.auth/
│ ├─ ui/
│ │ ├─ LoginActivity.java
│ │ ├─ RegisterActivity.java
│ │ └─ AuthFragment.kt
│ ├─ viewmodel/
│ │ └─ AuthViewModel.java
│ ├─ repository/
│ │ └─ AuthRepository.java
│ ├─ network/
│ │ ├─ ApiService.java
│ │ └─ RetrofitClient.java
│ ├─ data/
│ │ ├─ AppDatabase.java
│ │ ├─ User.java
│ │ └─ UserDao.java
│ └─ util/
│ ├─ Validators.java
│ └─ SecurityUtil.java
└─ res/
├─ layout/
│ ├─ activity_login.xml
│ └─ activity_register.xml
└─ values/
├─ strings.xml
└─ colors.xml
4.2 布局文件:activity_login.xml
4.3 网络层:ApiService.java
package com.example.auth.network;
import com.example.auth.data.User;
import com.example.auth.network.request.LoginRequest;
import com.example.auth.network.request.RegisterRequest;
import com.example.auth.network.response.AuthResponse;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.POST;
public interface ApiService {
@POST("/api/auth/login")
Call login(@Body LoginRequest body);
@POST("/api/auth/register")
Call register(@Body RegisterRequest body);
}
4.4 Retrofit 客户端:RetrofitClient.java
package com.example.auth.network;
import android.content.Context;
import com.example.auth.util.SecurityUtil;
import java.util.concurrent.TimeUnit;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
public class RetrofitClient {
private static Retrofit retrofit;
public static Retrofit getInstance(Context ctx) {
if (retrofit == null) {
HttpLoggingInterceptor logInterceptor = new HttpLoggingInterceptor();
logInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(logInterceptor)
.addInterceptor(new Interceptor() {
@Override public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.header("Content-Type", "application/json")
.header("Authorization", "Bearer " + SecurityUtil.getToken(ctx))
.build();
return chain.proceed(request);
}
})
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.build();
retrofit = new Retrofit.Builder()
.baseUrl("https://api.yourdomain.com")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build();
}
return retrofit;
}
}
4.5 数据层:AppDatabase.java & UserDao.java
package com.example.auth.data;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import android.content.Context;
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
private static AppDatabase instance;
public abstract UserDao userDao();
public static synchronized AppDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "app_db")
.fallbackToDestructiveMigration()
.build();
}
return instance;
}
}
package com.example.auth.data;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
@Dao
public interface UserDao {
@Insert
void insert(User user);
@Query("SELECT * FROM user WHERE username = :uname LIMIT 1")
User findByUsername(String uname);
}
4.6 实体类:User.java
package com.example.auth.data;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
@Entity(tableName = "user")
public class User {
@PrimaryKey(autoGenerate = true)
public int id;
public String username;
public String token;
// 可扩展字段:email、avatar、roles 等
}
4.7 请求/响应模型
// LoginRequest.java
public class LoginRequest {
public String username;
public String password; // 已在客户端 MD5/SHA256 加密
}
// RegisterRequest.java
public class RegisterRequest {
public String username;
public String password;
public String confirmPassword;
}
// AuthResponse.java
public class AuthResponse {
public boolean success;
public String message;
public String token;
public User user;
}
4.8 仓库层:AuthRepository.java
package com.example.auth.repository;
import android.content.Context;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.example.auth.data.AppDatabase;
import com.example.auth.data.User;
import com.example.auth.data.UserDao;
import com.example.auth.network.ApiService;
import com.example.auth.network.RetrofitClient;
import com.example.auth.network.request.LoginRequest;
import com.example.auth.network.request.RegisterRequest;
import com.example.auth.network.response.AuthResponse;
import com.example.auth.util.SecurityUtil;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class AuthRepository {
private ApiService api;
private UserDao userDao;
private Context ctx;
public AuthRepository(Context context) {
this.ctx = context.getApplicationContext();
api = RetrofitClient.getInstance(ctx).create(ApiService.class);
userDao = AppDatabase.getInstance(ctx).userDao();
}
public LiveData> login(String uname, String pwd) {
final MutableLiveData> result = new MutableLiveData<>();
// 本地检查
if (uname.isEmpty() || pwd.isEmpty()) {
result.setValue(Result.error("用户名或密码不能为空"));
return result;
}
LoginRequest req = new LoginRequest();
req.username = uname;
req.password = SecurityUtil.hashPassword(pwd);
api.login(req).enqueue(new Callback() {
@Override public void onResponse(Call call, Response resp) {
AuthResponse body = resp.body();
if (body != null && body.success) {
// 保存Token
SecurityUtil.saveToken(ctx, body.token);
// 保存用户到本地
User u = body.user;
u.token = body.token;
userDao.insert(u);
result.setValue(Result.success(u));
} else {
result.setValue(Result.error(body != null ? body.message : "登录失败"));
}
}
@Override public void onFailure(Call call, Throwable t) {
result.setValue(Result.error("网络错误:" + t.getMessage()));
}
});
return result;
}
public LiveData> register(String uname, String pwd, String confirmPwd) {
final MutableLiveData> result = new MutableLiveData<>();
// 本地校验
if (uname.isEmpty() || pwd.isEmpty() || confirmPwd.isEmpty()) {
result.setValue(Result.error("请完整填写注册信息"));
return result;
}
if (!pwd.equals(confirmPwd)) {
result.setValue(Result.error("两次输入的密码不一致"));
return result;
}
RegisterRequest req = new RegisterRequest();
req.username = uname;
req.password = SecurityUtil.hashPassword(pwd);
req.confirmPassword = SecurityUtil.hashPassword(confirmPwd);
api.register(req).enqueue(new Callback() {
@Override public void onResponse(Call call, Response resp) {
AuthResponse body = resp.body();
if (body != null && body.success) {
SecurityUtil.saveToken(ctx, body.token);
User u = body.user;
u.token = body.token;
userDao.insert(u);
result.setValue(Result.success(u));
} else {
result.setValue(Result.error(body != null ? body.message : "注册失败"));
}
}
@Override public void onFailure(Call call, Throwable t) {
result.setValue(Result.error("网络错误:" + t.getMessage()));
}
});
return result;
}
}
布局与 Data Binding:双向绑定输入框内容到 ViewModel,自动管理生命周期;
网络请求:Retrofit + OkHttp Interceptor 注入 Token 与公共头;
本地存储:Room 管理用户数据,EncryptedSharedPreferences 存储 Token;
表单验证:Validators 工具类统一校验逻辑;
安全策略:SecurityUtil 对密码进行哈希与加密,避免明文存储;
项目收获:系统掌握 Android MVVM 架构、网络层搭建、安全认证流程与本地存储;
性能优化:请求合并、缓存策略(OkHttp Cache)、页面预加载等;
测试策略:单元测试(JUnit + Mockito)、UI 测试(Espresso)、接口契约测试(Pact);
CI/CD:集成 GitHub Actions / Jenkins 自动构建与发布;
未来扩展:社交登录(OAuth)、验证码登录(短信/邮件)、生物认证(指纹/面部识别)、动态权限校验。