ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Android登录页实战:Material Design+EncryptedSharedPreferences+Retrofit

Android登录页实战:Material Design+EncryptedSharedPreferences+Retrofit 简介本资源是一份面向计算机专业本科生的Android应用开发毕业设计参考项目聚焦登录模块UI实现与基础交互逻辑帮助学生快速掌握仿主流App登录页的完整开发流程。压缩包共52个文件含9个XML布局文件如activity_login.xml定义界面结构、3个Java源码文件实现登录事件响应与数据校验、24个PNG/JPG图片资源用于图标与背景、8个class字节码及1个可直接安装的APK演示包整体仅169KB轻量易学。已有467人学习下载适合课程设计、毕设选题或Android入门实战。读者可直接运行APK体验效果通过源码理解EditText输入处理、Button点击响应、SharedPreferences记住密码等核心实践并结合res资源目录与src代码结构系统梳理Android项目标准分层架构与UI-逻辑分离设计思想。1. 这不是套壳UI而是毕业设计里最常被低估的「登录流程闭环」实战很多计算机专业学生拿到“Android仿QQ微信登录页面”这个毕设题目时第一反应是不就是画几个输入框按钮但真正跑通一个能对接真实账号体系、支持密码/验证码双模式、带状态反馈和本地缓存的登录页往往卡在第三周——不是布局写不出来而是点击登录后onClickListener里该调什么接口、怎么处理ActivityResultLauncher返回、如何安全存密码哪怕只是明文模拟、为什么TextInputLayout的错误提示总不显示……这些细节才是企业面试官看简历时会点开你 GitHub 仓库重点 inspect 的部分。本文面向已掌握Activity生命周期、能写RecyclerView但对ViewModelLiveData协作还不熟练的准毕业生用 Android Studio Flamingo2023.2.1为基准从零构建一个可编译、可调试、可扩展的登录页工程所有代码适配 Android 14API 34兼容性要求不依赖任何第三方 UI 框架只用 Material Design Components 原生组件。2. 用 Material Design Components 在本地跑通最小可交互登录页2.1 为什么选 Material Design Components 而非自定义 View毕业设计评审中UI 规范性占分权重常被忽视。QQ 和微信当前 Android 版本均基于 Material You 设计语言重构其核心特征包括动态颜色适配DynamicColors.applyToActivityIfAvailable()、TextInputLayout内置的浮动标签与错误提示联动、MaterialButton的涟漪反馈与禁用态自动灰度。若用纯LinearLayoutEditText手动实现需额外处理焦点获取时的边框高亮、输入错误时的红字提示动画、密码可见性切换图标状态同步——而TextInputLayout一行app:helperText请输入6-16位密码就能绑定editText.setError()触发完整动效。更重要的是MDCC 组件默认启用android:importantForAccessibilityyes满足无障碍评审硬性要求避免答辩时被问“你的登录页盲人用户怎么操作”。提示不要在build.gradle中引入com.google.android.material:material:1.10.0后直接使用com.google.android.material.textfield.TextInputLayout必须确认项目compileSdk≥ 33且targetSdk设置为 34。低于此版本会导致app:boxStrokeColor属性解析失败界面渲染为空白。2.2 创建带双输入域一键登录的 Activity 布局以下 XML 是经实测可在 Android 14 模拟器上 100% 渲染的最小可用结构关键点在于TextInputLayout的嵌套层级与app:hintTextAppearance的显式声明!-- res/layout/activity_login.xml -- LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical android:padding24dp com.google.android.material.textfield.TextInputLayout android:idid/til_phone stylestyle/Widget.Material3.TextInputLayout.OutlinedBox android:layout_widthmatch_parent android:layout_heightwrap_content android:layout_marginTop48dp app:hintTextAppearancestyle/TextAppearance.Material3.TitleMedium app:prefixText app:startIconDrawabledrawable/ic_phone app:startIconContentDescription手机号 com.google.android.material.textfield.TextInputEditText android:idid/et_phone android:layout_widthmatch_parent android:layout_heightwrap_content android:hint手机号 android:inputTypephone android:maxLength11 / /com.google.android.material.textfield.TextInputLayout com.google.android.material.textfield.TextInputLayout android:idid/til_password stylestyle/Widget.Material3.TextInputLayout.OutlinedBox android:layout_widthmatch_parent android:layout_heightwrap_content android:layout_marginTop16dp app:hintTextAppearancestyle/TextAppearance.Material3.TitleMedium app:passwordToggleEnabledtrue app:passwordToggleTintcolor/selector_password_toggle app:startIconDrawabledrawable/ic_lock app:startIconContentDescription密码 com.google.android.material.textfield.TextInputEditText android:idid/et_password android:layout_widthmatch_parent android:layout_heightwrap_content android:hint密码 android:inputTypetextPassword / /com.google.android.material.textfield.TextInputLayout com.google.android.material.button.MaterialButton android:idid/btn_login android:layout_widthmatch_parent android:layout_heightwrap_content android:layout_marginTop32dp android:text登 录 android:textSize16sp app:cornerRadius12dp / /LinearLayout参数说明与踩坑点app:prefixText用于在输入框前固定显示手机emoji避免使用app:startIconDrawable时图标与文字间距不可控app:passwordToggleTintcolor/selector_password_toggle必须指向一个ColorStateList资源而非单色值否则切换可见性时图标无颜色变化android:inputTypephone自动触发数字键盘但需配合android:maxLength11防止输入超长号码导致后续校验逻辑崩溃stylestyle/Widget.Material3.TextInputLayout.OutlinedBox是 Material 3 标准样式若项目theme未继承Theme.Material3.DayNight需在themes.xml中显式设置item nametextInputStylestyle/Widget.Material3.TextInputLayout.OutlinedBox/item。2.3 在 LoginActivity 中绑定事件并实现基础校验逻辑仅布局完成无法通过毕设中期检查——必须让按钮点击产生可观察行为。以下 Java 代码片段兼容 Kotlin 项目可直接转译展示了如何在onCreate()中完成视图绑定、空值校验及 Toast 反馈// java/com/example/login/LoginActivity.java public class LoginActivity extends AppCompatActivity { private TextInputLayout tilPhone, tilPassword; private TextInputEditText etPhone, etPassword; private MaterialButton btnLogin; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); // View binding使用 findViewById避免 ButterKnife 等过时库 tilPhone findViewById(R.id.til_phone); tilPassword findViewById(R.id.til_password); etPhone findViewById(R.id.et_phone); etPassword findViewById(R.id.et_password); btnLogin findViewById(R.id.btn_login); btnLogin.setOnClickListener(v - { // 清除上次错误状态 tilPhone.setError(null); tilPassword.setError(null); String phone etPhone.getText().toString().trim(); String password etPassword.getText().toString().trim(); // 基础格式校验毕业设计阶段无需接入短信验证 if (phone.isEmpty()) { tilPhone.setError(手机号不能为空); return; } if (phone.length() ! 11 || !phone.matches(^1[3-9]\\d{9}$)) { tilPhone.setError(请输入正确的11位手机号); return; } if (password.isEmpty()) { tilPassword.setError(密码不能为空); return; } if (password.length() 6 || password.length() 16) { tilPassword.setError(密码长度6-16位); return; } // 校验通过后的模拟登录实际项目应替换为 Retrofit 调用 Toast.makeText(this, 登录请求已发出, Toast.LENGTH_SHORT).show(); // 此处可启动 LoadingDialog 或设置 btnLogin.setEnabled(false) }); } }关键逻辑说明tilPhone.setError(null)必须在每次点击前调用否则上次错误提示会残留手机号正则^1[3-9]\\d{9}$覆盖国内主流号段13x/14x/15x/17x/18x/19x比简单length11更严谨Toast是毕业设计允许的轻量反馈方式若需更专业体验应在res/values/drawables.xml中定义loading_progress动画并替换btnLogin.setText(登录中...)此处未使用ViewModel是因单 Activity 场景下状态管理需求低但若后续扩展注册页或找回密码页必须将校验逻辑抽离至LoginViewModel。3. 实现密码本地存储与自动填充解决「下次自动登录」需求3.1 使用 EncryptedSharedPreferences 安全存储凭证QQ/微信登录页的核心体验之一是“记住密码”复选框。毕业设计中若用SharedPreferences明文存密码会被答辩老师直接扣分。Android Jetpack 提供的EncryptedSharedPreferences是官方推荐方案它基于AndroidKeyStore生成密钥密文存储于/data/data/package_name/shared_prefs/下即使设备 root 也无法直接读取。首先在app/build.gradle添加依赖implementation androidx.security:security-crypto:1.1.0-alpha03然后在LoginActivity中初始化加密存储实例private EncryptedSharedPreferences encryptedPrefs; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // ... 布局绑定代码 try { MasterKey masterKey new MasterKey.Builder(this) .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) .build(); encryptedPrefs new EncryptedSharedPreferences.Builder( this, login_credentials, masterKey, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM) .build(); } catch (GeneralSecurityException | IOException e) { e.printStackTrace(); Toast.makeText(this, 加密存储初始化失败, Toast.LENGTH_LONG).show(); } }参数说明MasterKey.KeyScheme.AES256_GCM是当前最安全的密钥生成方案替代已废弃的RSA_ECB_PKCS1PaddingPrefKeyEncryptionScheme.AES256_SIV保证键名加密防止通过文件名推断存储内容若build报错No implementation found for long android.security.keystore.AndroidKeyStoreKeyGeneratorSpi.engineGenerateKey, 需确认模拟器 API 级别 ≥ 28Android 9且启用硬件加速。3.2 构建「记住密码」复选框与状态同步逻辑在布局文件activity_login.xml中添加复选框置于MaterialButton上方com.google.android.material.checkbox.MaterialCheckBox android:idid/cb_remember android:layout_widthwrap_content android:layout_heightwrap_content android:layout_marginTop16dp android:text记住密码 android:textSize14sp /在onCreate()中绑定状态监听MaterialCheckBox cbRemember findViewById(R.id.cb_remember); cbRemember.setOnCheckedChangeListener((buttonView, isChecked) - { if (isChecked) { // 登录成功后保存凭证 String phone etPhone.getText().toString().trim(); String password etPassword.getText().toString().trim(); encryptedPrefs.edit() .putString(phone, phone) .putString(password, password) .apply(); } else { // 清除存储 encryptedPrefs.edit() .remove(phone) .remove(password) .apply(); } });关键验证步骤首次登录勾选“记住密码”杀进程重启 App在onCreate()中添加以下代码自动填充if (encryptedPrefs.contains(phone)) { String savedPhone encryptedPrefs.getString(phone, ); String savedPassword encryptedPrefs.getString(password, ); etPhone.setText(savedPhone); etPassword.setText(savedPassword); cbRemember.setChecked(true); }使用adb shell进入/data/data/your.package.name/shared_prefs/目录执行ls -l应看到login_credentials.xml文件权限为-rw-rw----且文件内容为 Base64 编码密文证实加密生效。4. 接入网络请求与状态管理让登录页真正「活」起来4.1 用 Retrofit 2.9.0 替代 HttpURLConnection 实现登录 API 调用毕业设计若仅停留在本地校验会被质疑工程能力。接入真实 HTTP 请求是分水岭。Retrofit 因其链式调用和CallT异步模型比原生OkHttpClient更适合教学场景。在app/build.gradle中添加implementation com.squareup.retrofit2:retrofit:2.9.0 implementation com.squareup.retrofit2:converter-gson:2.9.0定义登录响应数据类LoginResponse.javapublic class LoginResponse { public int code; // 0成功1密码错误2账号不存在 public String message; public String token; // 后续请求需携带的认证令牌 public UserInfo user; // 用户基本信息 public static class UserInfo { public String nickname; public String avatarUrl; } }创建 Retrofit 实例建议放在Application类中全局持有public class MyApplication extends Application { public static Retrofit retrofit; Override public void onCreate() { super.onCreate(); OkHttpClient client new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .build(); retrofit new Retrofit.Builder() .baseUrl(https://api.example.com/) // 替换为实际后端地址 .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); } }在LoginActivity中发起登录请求private void performLogin(String phone, String password) { LoginService service MyApplication.retrofit.create(LoginService.class); CallLoginResponse call service.login(phone, password); call.enqueue(new CallbackLoginResponse() { Override public void onResponse(CallLoginResponse call, ResponseLoginResponse response) { if (response.isSuccessful() response.body() ! null) { LoginResponse result response.body(); if (result.code 0) { // 登录成功跳转主页面并保存 token SharedPreferences prefs getSharedPreferences(auth, MODE_PRIVATE); prefs.edit().putString(token, result.token).apply(); Intent intent new Intent(LoginActivity.this, MainActivity.class); startActivity(intent); finish(); } else { // 服务端返回错误 tilPassword.setError(result.message); } } else { tilPassword.setError(网络请求失败请检查网络); } } Override public void onFailure(CallLoginResponse call, Throwable t) { tilPassword.setError(连接服务器超时); } }); }接口定义LoginService.javapublic interface LoginService { FormUrlEncoded POST(auth/login) CallLoginResponse login( Field(phone) String phone, Field(password) String password ); }注意FormUrlEncoded表示以application/x-www-form-urlencoded格式提交与 QQ/微信 Web 登录接口一致若后端要求 JSON改用Body注解并传入JsonObject。4.2 使用 ViewModel LiveData 解耦 UI 与业务逻辑当登录逻辑复杂化如增加短信验证码、人脸识别分支Activity会迅速臃肿。ViewModel 是毕业设计进阶必备技能。创建LoginViewModelpublic class LoginViewModel extends ViewModel { private MutableLiveDataString loginStatus new MutableLiveData(); public LiveDataString getLoginStatus() { return loginStatus; } public void login(String phone, String password) { // 此处调用 Retrofit成功时 postValue(success)失败时 postValue(error:xxx) // 具体实现同上节 Retrofit 调用但移出 Activity } }在LoginActivity中观察状态LoginViewModel viewModel new ViewModelProvider(this).get(LoginViewModel.class); viewModel.getLoginStatus().observe(this, status - { if (success.equals(status)) { startActivity(new Intent(this, MainActivity.class)); finish(); } else if (status.startsWith(error:)) { tilPassword.setError(status.substring(6)); } });优势体现屏幕旋转时ViewModel不重建避免重复请求LiveData自动绑定生命周期无需手动removeObserver后续扩展「忘记密码」功能时只需在LoginViewModel中新增resetPassword()方法Activity 层无修改。5. 毕业设计答辩高频问题应对与真机调试技巧5.1 针对「仿QQ微信」的差异化设计答辩话术评审老师常问“你这个页面和QQ/微信有什么区别”——这不是让你承认抄袭而是考察产品思维。准备三个层次的回答视觉层指出 QQ 登录页顶部有“QQ号/手机号/邮箱”三入口微信只有手机号而你的设计采用微信风格但增加了「游客登录」快捷入口在MaterialButton下方加TextView体现对多场景覆盖的思考交互层QQ 输入手机号后自动触发运营商识别显示“中国移动”图标你的实现通过TelephonyManager获取 SIM 卡信息并动态设置tilPhone.setStartIconDrawable()代码位于onResume()中架构层QQ 使用自研网络框架微信用MMKV存储而你选择EncryptedSharedPreferencesRetrofit是因 Jetpack 组件更易教学、文档更全符合毕业设计“掌握主流技术栈”的目标。5.2 真机调试必查的 5 个致命项检查项命令/操作失败表现修复方案AndroidManifest.xml 权限声明adb shell dumpsys package your.package.name | grep permissionjava.lang.SecurityException: Permission denied确保uses-permission android:nameandroid.permission.INTERNET /存在且未被注释targetSdkVersion 兼容性adb shell getprop ro.build.version.sdk对比build.gradleAndroid 12 设备点击无响应将targetSdk设为 34compileSdk同步升级HTTPS 证书信任adb logcat | grep javax.net.ssl.SSLHandshakeExceptionRetrofit 请求返回SSLHandshakeException后端使用 Lets Encrypt 证书或开发阶段在OkHttpClient.Builder()中添加sslSocketFactory信任所有证书仅限测试资源文件命名规范aapt dump badging app-debug.apk | grep resourceResources$NotFoundException确认ic_phone.xml等 vector drawable 文件名全小写、无下划线且android:srcdrawable/ic_phone路径正确签名配置一致性jarsigner -verify -verbose -certs app-debug.apk安装时提示“已存在同名应用”确保 Debug 签名与之前安装包一致或卸载旧版再安装5.3 用 adb 命令快速验证登录状态持久化无需打开 App 反复操作一条命令验证「记住密码」是否生效# 查看加密存储文件是否存在 adb shell ls -l /data/data/com.example.login/shared_prefs/login_credentials.xml # 导出加密文件到电脑需 root adb shell su -c cat /data/data/com.example.login/shared_prefs/login_credentials.xml encrypted.xml # 检查 SharedPreferences 中的 token 是否写入 adb shell run-as com.example.login cat shared_prefs/auth.xml若auth.xml中包含string nametokeneyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.../string证明认证状态已持久化这是答辩时展示「技术深度」的关键证据。最后在LoginActivity的onDestroy()中添加日志埋点Override protected void onDestroy() { super.onDestroy(); Log.d(LoginActivity, Activity destroyed. Login flow completed.); }运行adb logcat -s LoginActivity即可实时监控登录流程生命周期比 Toast 更专业地呈现你的调试能力。本文还有配套的精品资源点击获取
返回列表