简介面向Android初学者的实战工程基于Android Studio开发演示了借助SharedPreferences保存QQ密码的完整流程涵盖数据读写、简单加密解密、EditText输入与按钮事件监听等核心知识点特别适合理解移动应用安全存储的基础思路。压缩包内共941个文件体积仅6.31MB主要包含PNG界面切图、XML布局与配置说明、Java源代码另有可安装运行的APK安装包、依赖库JAR以及Gradle构建脚本目录结构清晰兼顾源码阅读与直接调试。当前已有2258人学习下载。通过该工程可快速掌握轻量级存储机制并借鉴其界面搭建与事件监听的写法在密码加密部分项目给出了基础实现思路可启发开发者进一步结合系统Keystore或第三方加密库提升安全性。整体来看这是一款小而完整的入门案例既能帮助新手熟悉Android工程结构也可作为课程练习或自学参考。1. 这个项目表面上“简单”实际练的是Android数据层的三条基本功聊到“android实战 保存QQ密码”这个工程很多刚入门的朋友第一反应是不就是写两个EditText加一个CheckBox点一下按钮把数据存起来吗这话对了一半。如果目标是照着敲一遍代码跑起来那确实不算难但如果你把它当成一个正经的android studio源程序工程来做你会发现它在逼你打通三个Android开发里绕不开的基本功界面与状态联动、事件驱动的数据流、以及SharedPreferences的正确使用姿势。先说一个很多教程没点透的事实SharedPreferences是Android平台面试里出现频率非常高的知识点但真正能把它讲清楚、用对的开发者没那么多。它不只是“存一个开关”“存一个token”的简单盒子它的提交机制、文件路径、多进程边界、异步刷盘行为都直接影响App在真实场景下的表现。而这个QQ密码保存项目恰好是理解这些机制的最小可运行载体。再聊聊“保存QQ密码”这个业务需求本身。拿QQ登录页来类比用户名输入框、密码输入框、记住密码勾选框、登录按钮——这个交互模板几乎适用于所有App的登录模块。你把“QQ密码”换成“邮箱密码”“购物平台登录信息”项目骨架完全不用变。所以这个项目的核心价值不是“能存QQ密码”而是让你掌握“怎么把一个需要持久化的输入场景做成一个健壮、可扩展的本地存储模块”。学完这个工程等于顺手拿下了几十个真实业务页面的基础逻辑。我在这篇文章里不会只给你贴一段能跑的代码。我会把工程从零到一的搭建过程拆开讲包括为什么要用SharedPreferences而不是数据库、commit()和apply()到底该选谁、为什么你明明点了“记住密码”但重启App之后一片空白、以及真实项目中为什么没人会直接明文存QQ密码。这些都是实际开发中会踩到、而文档里通常不会写明白的东西。2. 为什么是SharedPreferences先分清“配置存储”和“数据存储”两种场景2.1 一个QQ登录页背后藏着两种完全不同的存储需求假设你要实现一个完整的QQ登录功能仔细想一下“记住密码”这个勾选框背后有多少数据需要处理账号字符串、密码字符串、记住状态布尔值。这些数据的特点是结构固定、体量极小、与用户配置强相关、需要随App启动随时读取。这类数据在Android里有非常明确的归属——本地配置型数据官方推荐的轻量方案就是SharedPreferences。那为什么不直接上SQLite数据库这是很多新手会走的弯路。数据库解决的是结构化、关系型、可查询的大规模数据问题比如聊天记录、好友列表、订单表。用一个完整的数据库引擎去存两个字符串等于拿卡车运一瓶水功能上没问题成本和复杂度完全不成比例。数据库还需要维护SQLiteOpenHelper、建表语句、升级逻辑对一个“记住密码”场景来说属于严重过度设计。也不建议用文件File直接读写。文件方案需要你自己处理序列化格式、并发写入冲突、异常关闭导致的数据损坏这些都是SharedPreferences内部已经替你做好的脏活。换句话说SharedPreferences是Android在“轻量配置存储”这个维度上给的标准答案你不需要重新发明轮子。2.2 SharedPreferences的底层逻辑一个带缓存层的XML文件SharedPreferences本质是一个自动维护的XML文件存放在App私有目录下的shared_prefs文件夹里。以这个QQ密码项目为例你执行getSharedPreferences(qq_login, MODE_PRIVATE)之后系统会创建或读取/data/data/你的包名/shared_prefs/qq_login.xml这个文件。文件内容长这样?xml version1.0 encodingutf-8 standaloneyes ? map string nameqq_account123456789/string string nameqq_passwordmypassword123/string boolean nameremember_password valuetrue / /map理解了这个XML结构很多问题就自然通了。比如为什么SharedPreferences只能存基本数据类型因为XML序列化器原生支持的就只有String、int、float、long、boolean这些。想存一个对象你得手动转成String格式通常是JSON序列化。再比如为什么SharedPreferences适合存“配置”而“不适合”存大量业务数据因为每次getString()读取时如果缓存未命中系统会执行一次完整的文件解析把整个XML加载进内存。文件越大加载越慢。你存几十个字段没问题存几万条记录就会明显卡顿。这也是判断一个场景该不该用SharedPreferences的黄金标准如果这个数据文件的大小超过几百KB就该考虑数据库了。2.3 关键设计为什么用MODE_PRIVATE而不用其他模式在android studio里创建SharedPreferences实例时第二参数通常固定写MODE_PRIVATE。很多教程一句话带过但这里其实有历史包袱。早期的Android版本还提供MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE分别允许其他应用读取或修改你的配置数据——想想都知道这是多大的安全隐患一个恶意应用可以读取你的QQ密码文件或者直接篡改你的登录配置。从Android 4.2开始这两个模式已经被废弃从Android 7.0API 24开始使用这两个模式会直接抛出安全异常。所以现在的项目里MODE_PRIVATE不是“可选项”而是唯一正解。它的含义是这个文件只能被你这个应用访问其他应用没有权限读取。结合前文提到的私有目录路径这套机制从操作系统层保证了你保存的账号密码不会随便被别的App拖走。3. 完整工程实现从新建项目到跑通“保存-读取-回显”闭环3.1 第一步规划界面布局别急着写代码我是从新建一个空的Empty Views Activity项目开始的包名按你自己的习惯来Minimum SDK选API 21以上就够了因为这个工程不会用到任何高版本专属API。项目建好之后先想清楚一件事界面上需要几个控件它们之间是什么关系。以QQ登录页为模板我们需要一个账号输入框EditText提示文案“请输入QQ号码”一个密码输入框EditText提示文案“请输入密码”并且要设置inputTypetextPassword保证输入内容被打码显示一个“记住密码”CheckBox一个“登录”Button考虑完善一点的话再加一个TextView用于显示当前保存状态这样你在演示的时候能直观看到“保存中…”“已保存”“已勾选自动填充”这些反馈。如果想让界面更像真实登录页可以给根布局换成LinearLayout并设置gravitycenter再限制一下输入框的宽度比例视觉上会更协调。布局文件activity_main.xml的骨架长这样LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical android:gravitycenter_horizontal android:padding32dp EditText android:idid/et_account android:layout_widthmatch_parent android:layout_heightwrap_content android:hint请输入QQ号码 android:inputTypenumber / EditText android:idid/et_password android:layout_widthmatch_parent android:layout_heightwrap_content android:hint请输入密码 android:inputTypetextPassword / CheckBox android:idid/cb_remember android:layout_widthmatch_parent android:layout_heightwrap_content android:text记住密码 / Button android:idid/btn_login android:layout_widthmatch_parent android:layout_heightwrap_content android:text登录 / TextView android:idid/tv_status android:layout_widthmatch_parent android:layout_heightwrap_content android:text当前状态未保存任何数据 android:paddingTop16dp / /LinearLayout这里有一个实用的小细节账号输入框的inputType我建议设成number因为QQ号是纯数字这样弹出的键盘就是数字键盘用户的输入成本会降低很多。如果你要做的场景是不定类型的账号比如邮箱或手机号那应该改成textEmailAddress或phone。3.2 第二步写一个独立的存储工具类别把逻辑全堆在Activity里最影响项目后期维护性的一个决定就是存储逻辑放在哪里。把SharedPreferences的操作直接写在MainActivity里当然能跑但你会发现两个问题第一如果以后项目里其他页面也要读取当前登录状态你只能复制粘贴同一段代码第二一旦存储逻辑有变更比如要从SharedPreferences切换成DataStore你得把所有Activity都翻一遍。所以我会建议在这个源程序工程里直接建一个PreferencesManager类把所有跟配置读写相关的操作都收拢到类内部。这个类的设计很简单核心就是暴露几个业务方法保存登录数据、读取已保存的数据、清除数据。public class PreferencesManager { private static final String PREF_NAME qq_login_prefs; private static final String KEY_ACCOUNT qq_account; private static final String KEY_PASSWORD qq_password; private static final String KEY_REMEMBER remember_password; private final SharedPreferences preferences; public PreferencesManager(Context context) { preferences context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE); } public void saveLoginInfo(String account, String password, boolean remember) { SharedPreferences.Editor editor preferences.edit(); editor.putString(KEY_ACCOUNT, account); editor.putString(KEY_PASSWORD, password); editor.putBoolean(KEY_REMEMBER, remember); editor.apply(); } public String getAccount() { return preferences.getString(KEY_ACCOUNT, ); } public String getPassword() { return preferences.getString(KEY_PASSWORD, ); } public boolean isRememberPassword() { return preferences.getBoolean(KEY_REMEMBER, false); } }代码很简单但注意看两个地方。第一常量集中定义在类顶部后边如果SharedPreferences的字段名要调整只需要在一个位置改动。第二saveLoginInfo()方法接收的参数里我把remember布尔值也存进去了这是很多人会忽略的一点——如果只存账号密码而不存勾选状态重启App时你就无法判断“该不该自动填充密码框”。3.3 第三步MainActivity里完成“保存-读取-回显”三步联动接下来是MainActivity的核心逻辑。它的任务有三个启动时读取保存的数据并回填界面用户点击登录按钮时根据勾选状态决定是否保存动态更新状态提示。启动回填部分最关键的坑在于必须在setContentView之后、用户看到界面之前完成数据填充。否则用户会先看到空白输入框然后才“眼睁睁”看着数据被填进去体验非常奇怪。public class MainActivity extends AppCompatActivity { private EditText etAccount; private EditText etPassword; private CheckBox cbRemember; private TextView tvStatus; private PreferencesManager preferencesManager; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); etAccount findViewById(R.id.et_account); etPassword findViewById(R.id.et_password); cbRemember findViewById(R.id.cb_remember); tvStatus findViewById(R.id.tv_status); preferencesManager new PreferencesManager(this); restoreSavedData(); setupLoginButton(); } private void restoreSavedData() { if (preferencesManager.isRememberPassword()) { etAccount.setText(preferencesManager.getAccount()); etPassword.setText(preferencesManager.getPassword()); cbRemember.setChecked(true); tvStatus.setText(已读取本地保存的登录信息); } else { tvStatus.setText(上次未勾选记住密码不做自动填充); } } private void setupLoginButton() { findViewById(R.id.btn_login).setOnClickListener(new View.OnClickListener() { Override public void onClick(View v) { String account etAccount.getText().toString().trim(); String password etPassword.getText().toString().trim(); boolean remember cbRemember.isChecked(); if (account.isEmpty() || password.isEmpty()) { tvStatus.setText(账号和密码不能为空); return; } if (remember) { preferencesManager.saveLoginInfo(account, password, true); tvStatus.setText(登录成功账号密码已保存到本地); } else { preferencesManager.saveLoginInfo(account, , false); tvStatus.setText(登录成功未记住密码); } } }); } }这段代码里有值得琢磨的细节在未勾选“记住密码”的分支里我没有单纯选择“不保存”而是故意调用了一次saveLoginInfo(account, , false)。这么做的意图是当用户之前勾选过“记住密码”后来取消了勾选并再次登录时如果不主动清掉密码数据SharedPreferences里残留的旧密码就会在下次启动时被restoreSavedData()读取出来造成“明明取消了记住密码还在”的诡异现象。显式写入一个空字符串和false本质上是做了一次状态覆盖保证本地记录与用户最新意图一致。3.4 第四步跑起来看效果并验证真实的文件写入到这里工程已经可以运行了。你输入账号密码、勾选“记住密码”、点击登录然后杀掉App重新打开输入框应该能恢复刚才填的内容。验证方式除了“重启App看回显”还有一个更底层的检查手段通过Android Studio自带的Device File Explorer查看SharedPreferences对应的XML文件是否真实写入。路径是/data/data/你的包名/shared_prefs/qq_login_prefs.xml。展开这个文件你会看到前文展示的那个map结构。这步验证很有价值它能让你从“代码层面相信存储生效”进化到“亲眼看到持久化文件的实体存在”对理解整个数据持久化链路非常有帮助。4. 工程里的三个经典坑保存不生效、数据被重置、勾选状态丢失4.1 坑一用了commit()还是apply()选错之后的诡异表现很多新手在保存时拿不准该用commit()还是apply()。两者的区别从方法名就能嗅到一点味道commit()是同步的它会立刻把内存中的修改写入磁盘并返回一个布尔值告诉你写入是否成功apply()是异步的它会先更新内存中的值然后安排一个异步任务去执行磁盘写入不返回结果。那什么时候必须用commit()只有一个场景此后需要立刻依赖写入结果比如写入失败时需要给用户一个明确提示又或者你在Service里执行保存操作后紧接着要杀掉进程怕异步写入来不及落盘。除此之外日常开发一律推荐apply()。因为它在异步写入不会阻塞主线程。尤其是当你在onPause()或onStop()里调用保存逻辑时如果文件较大、设备性能较差同步的commit()会让页面退出看起来“卡一下”而apply()能让保存操作顺滑完成。但apply()也有一个隐蔽问题它不保证“瞬时”落盘。如果你在调用apply()之后马上killProcess()极端情况下数据可能还没写进去。这就是为什么很多真机测试中用户“保存完立刻强杀App”重启后数据丢失——写盘任务被进程死亡打断了。这个坑在实际工程里很常见排查半天最后发现是“时序”问题而不是代码逻辑问题。4.2 坑二Activity被系统回收导致数据“丢失”的假象另一种数据“丢失”跟SharedPreferences完全无关而是Activity生命周期的问题。比如用户把App切到后台系统内存不足时回收了Activity。用户回到AppActivity重新走onCreate()如果此时你在onCreate()里做了回填理论上会重新读取SharedPreferences数据应该还在。但如果你的回填逻辑依赖了内存中的某个临时状态变量比如先判断isDataLoaded这样一个只在一次会话里存在的字段就会跳过回填逻辑表现成“明明保存了但打开却是空的”。这不是SharedPreferences丢数据而是你的代码没有在Activity重建时正确执行初始化。排查思路也很直接把回填逻辑放在onCreate()里并且不依赖任何非持久化的内存字段。4.3 坑三账号密码都存了但勾选框状态没恢复第三个坑特别隐蔽也很容易在演示时翻车——你只存了账号密码却没存“记住密码”的状态。重启App后你发现账号密码都正常回填了但“记住密码”勾选框是空白的更麻烦的是当用户取消勾选并退出时逻辑上会触发“清除密码”的分支。如果之前的勾选状态没有被正确恢复用户看到的界面和他记忆中的设置不一致就会产生“程序出Bug了”的观感。解决方式就是前文代码里展示的那样把boolean remember也作为一项数据保存回填时先读它再根据它决定是否填充账号密码。这是一个经典的“状态与数据一致性”问题看起来小实则直接影响功能完整性。5. 安全边界真实项目里谁都不会明文存QQ密码5.1 为什么SharedPreferences不能直接用于密码存储到了这个项目的最后一部分我必须泼一盆冷水如果你打算把“明文保存密码”这套逻辑原封不动搬到真实商业项目里那等于是给安全团队送人头。原因有三层。第一层是数据可见性。虽然/data/data/目录默认隔离普通用户无法直接访问但在Root设备上任何应用都可以读取其他应用的数据文件。手机一旦Root你存在SharedPreferences里的密码字符串就像写在透明纸上的字一翻就能看到。第二层是备份泄露。Android系统允许应用数据在开启云备份时上传到云端明文密码进入备份通道等于扩大了攻击面。第三层是撞库风险。很多用户习惯在不同平台用同一个密码一旦本地明文数据被拖走攻击者可以用来尝试登录用户的邮箱、支付、社交账号危害被成倍放大。5.2 学完记住密码后可以往哪些方向升级这个项目做完之后如果你想继续向真实项目靠拢有三个升级方向值得研究。第一个方向是加密存储。使用Android Keystore系统生成或保存密钥配合AES加密算法对密码进行加密后再存入SharedPreferences。读取时先解密再使用。这样即使文件被拖走攻击者拿到的也是密文。第二个方向是不存原文只存令牌。真实登录场景下更常见的做法是服务器端返回一个Token客户端只保存Token而不是保存密码本身。密码只在用户主动登录那一刻被使用之后一律用Token维持会话。第三个方向是转向Jetpack DataStore。它是官方在2020年之后力推的SharedPreferences继任者基于协程和Flow天然支持异步操作但仍然只适合承载这类轻量配置数据安全性并不会因为换存储框架而提升。换句话说这个项目的核心价值在于帮你建立“本地持久化”的完整认知框架。存什么、怎么存、存在哪、怎么保证数据与状态一致这些问题想通了后续不管换成加密方案还是DataStore你都能在很短时间内迁移过去。最后分享一个我个人的操作习惯做完这个工程后我会习惯性地用Device File Explorer去检查一次生成的XML文件看看实际落盘内容跟自己预期是否一致。很多同学写完代码只盯着界面看却忽略了“数据到底以什么形态存储”这层认知。当你亲眼看到那个map结构里的每一项数据时你对SharedPreferences的理解才真正从一个抽象概念变成一个触手可及的东西。这步做完这个项目才算真正闭环。本文还有配套的精品资源点击获取