简介:基于 Android Studio 的备忘录 App 设计项目,面向初学 Android 开发的读者,清晰展示了移动应用从界面搭建到功能落地的完整路径。内容涵盖 Empty Activity 工程初始化、SQLite 数据库表设计、备忘录列表展示与增删改查、登录注册及本地提醒通知等核心环节。资源共 81 个文件,压缩包约 2.61MB,以 Java 源码、XML 布局与资源配置、PNG/WebP 图标素材为主,另含可直接安装的 APK、签名 JKS 文件与 Gradle 配置,便于导入 IDE 调试运行。已有 735 人学习下载,可作为课程设计、毕业设计或自学参考模板。借助该工程,能深入理解 Android 中 UI 与数据存储的衔接方式,并将账号鉴权、列表管理、本地通知等方案迁移到同类应用开发中。 我从头到尾做了一个基于Android Studio的备忘录App,从新建项目到生成APK装到手机上,前前后后折腾了差不多一周。这个项目看起来简单,但真做起来牵扯的东西不少:界面布局、数据存储、列表刷新、输入法交互、真机调试,每一步都有坑。如果刚学Android开发没多久,想找个完整项目练手,备忘录App确实是最合适的,业务逻辑清晰、功能边界明确,做完之后你对Activity、RecyclerView、SQLite、生命周期这些核心概念都会有比看书深刻得多的理解。
这篇文章我不会只扔出一堆代码,而是把整个设计思路、技术选型、实操过程、踩坑点全部拆开讲清楚,尤其是为什么这么做、哪些地方容易翻车。不管你是打算交课程设计,还是想给自己的作品集加一个App,都可以直接参考这套方案。
1. 项目起步:先想清楚备忘录App到底要做什么
1.1 需求拆解:不是"记录文字"这么简单
很多人一拿到这个题目,第一反应就是做个文本框加保存按钮,完事。但真正动手设计的时候会发现,用户对备忘录的预期远不止"能存字"。
我梳理下来,一个合格的备忘录App至少要满足这几个核心场景:
- 快速记录:打开App就能看到已有备忘,点击右下角按钮立刻新建,不能有冗余步骤。
- 编辑与删除:点进某条记录能修改,长按或按钮能删掉。
- 时间感知:每条备忘需要显示创建或修改时间,不然过两天看根本不知道这条是什么时候记的。
- 数据持久化:App关掉再打开,数据还在。这是备忘录和"一次性输入框"最本质的区别。
- 列表清晰:多条记录按时间排序,最新在最上面,一眼扫过去能分辨出哪条比较重要。
至于搜索、提醒、标签、置顶这些增强功能,属于加分项。我建议第一版先把基础CRUD打磨稳定,再谈加料。不要一上来就贪多,数据库删改逻辑都没跑通就想着做桌面小部件,最后只会一团乱麻。
1.2 技术选型:为什么用Java + SQLite而不是别的
Android开发常用的语言有Java和Kotlin,存储方案有SQLite、Room、SharedPreferences、甚至是直接存文件。
我的选择是Java + SQLite,不使用ORM框架。原因很简单:
- 学习阶段用纯SQL可以逼自己把数据库操作搞明白,升级到Room会容易得多;反过来先用了Room,一旦出问题可能连SQL语句报错都看不懂。
- Java在AS里资料多、提问方便,遇到问题一搜一堆解决方案,对新手友好。
- 备忘录数据是典型的结构化数据(标题、内容、时间),SQLite天然适合,SharedPreferences只适合存少量键值对,硬拿来存列表会非常别扭。
如果你Kotlin基础比较好,用Kotlin写其实完全没问题,核心逻辑不冲突。我这篇以Java为例,Kotlin换成对应语法就行。
补充一个很多人忽略的点:Android Studio默认新建项目时会因选择不同模板生成很多自动代码。我建议直接选"Empty Views Activity",不要选"Empty Activity(Compose)",因为Compose和传统XML布局语法完全不同,网上大多数旧教程都是XML布局,对新手更友好。
另外,顺手说一下"Android Studio怎么设置中文"——如果你看到满屏英文头疼,可以在AS里按Ctrl+Alt+S打开设置,在Plugins里搜索"Chinese (Simplified) Language Pack",安装后重启即可。不过我个人不建议依赖中文界面,因为查资料时网上讲的都是英文版路径,中文菜单反而对不上号,还不如趁这个机会把常用英文术语混个脸熟。
2. 界面搭建与核心代码:从空页面到能用的备忘录
2.1 布局设计:列表页 + 编辑页的双页面结构
我采用的是两个Activity的方案:MainActivity负责展示备忘录列表,EditActivity负责新建和编辑。
为什么不做一个Activity内用Fragment切换的"高级架构"?
- 两个Activity逻辑边界清晰,新手更容易掌控生命周期;
- 编辑页作为独立入口,以后想加"从外部分享文本跳转到新建备忘"之类的功能会非常方便;
- 系统返回键可以直接从编辑页退出,不用手动管理页面堆栈。
列表页的主布局我用的是RecyclerView,这个组件是现在列表展示事实标准,配合FloatingActionButton做新增按钮,视觉上也是Material Design的常见样式。
核心布局文件截图式描述一下:
<!-- activity_main.xml 核心结构 --> <RelativeLayout> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerView" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fab_add" android:layout_alignParentEnd="true" android:layout_alignParentBottom="true" /> </RelativeLayout>编辑页的布局更简单,一个大标题输入框加一个多行内容输入框,底部放保存按钮。但这里有两个非常关键的属性必须注意:
- 多行输入框设置
android:gravity="top",不然光标会垂直居中,输入的文字全挤在中间,特别丑; - 设置
android:background="@null"隐藏EditText默认下划线(如果你想做无边框风格),或者保留下划线走默认风格,看你自己审美。
2.2 数据层实现:SQLiteOpenHelper与CRUD操作
数据层是整个App的地基,我直接写一个MemoDbHelper继承SQLiteOpenHelper。
public class MemoDbHelper extends SQLiteOpenHelper { private static final String DB_NAME = "memo.db"; private static final int DB_VERSION = 1; public MemoDbHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { String sql = "CREATE TABLE memo (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "title TEXT, " + "content TEXT, " + "update_time INTEGER)"; db.execSQL(sql); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 版本升级时的处理,后面专门讲 } }这里update_time我用了INTEGER类型存毫秒时间戳,而不是用TEXT存"2025-01-01 12:00"这种字符串。原因是时间戳排序高效、可随意格式化,而且迁移到别的数据库不受格式影响。显示格式只是展示层的事,不要污染数据层。
备忘录的增删改查我封装成了一个MemoDao,提供insert、update、delete、queryAll四个方法。举例说明插入逻辑:
public long insert(Memo memo) { SQLiteDatabase db = helper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("title", memo.getTitle()); values.put("content", memo.getContent()); values.put("update_time", memo.getUpdateTime()); return db.insert("memo", null, values); }注意ContentValues是Android提供的"键值对容器",适用于单行数据写入,不要手动拼SQL字符串,那样不仅容易报错,还有SQL注入隐患。这里的insert方法返回的是新记录的行号,如果不大于0说明失败,可以配合Toast提示用户。
2.3 列表展示与点击编辑:RecyclerView适配器解析
RecyclerView的适配器是很多新手卡壳的地方。其实核心就三件事:给每条数据创建一个Item视图、把数据填进去、处理点击事件。
我通常会先定义好Item布局。列表里每条备忘显示三行:
- 标题(加粗,单行省略)
- 内容摘要(灰色小字,单行省略)
- 时间(右下角,小字)
为什么内容也要显示?因为我发现纯标题列表很容易让人想不起来这条备忘到底是什么,有摘要的话扫一眼就能定位。
适配器的核心代码片段:
public class MemoAdapter extends RecyclerView.Adapter<MemoAdapter.ViewHolder> { private List<Memo> memoList; private OnItemClickListener listener; public interface OnItemClickListener { void onItemClick(int position); void onItemLongClick(int position); } // 省略 createViewHolder 中对 item_view 的 inflate @Override public void onBindViewHolder(ViewHolder holder, int position) { Memo memo = memoList.get(position); holder.title.setText(memo.getTitle()); holder.content.setText(memo.getContent()); holder.time.setText(TimeUtils.format(memo.getUpdateTime())); holder.itemView.setOnClickListener(v -> { if (listener != null) listener.onItemClick(position); }); } }点击事件不要把position当成最终数据id,因为列表经过删除/排序后,position是动态的。正确做法是取出当前position对应的Memo对象,把它整个传给编辑页,编辑页用Memo.getId()去更新数据库。
具体跳转传值代码如下:
Intent intent = new Intent(MainActivity.this, EditActivity.class); intent.putExtra("memo", memo); // 需要 Memo 实现 Serializable 或 Parcelable startActivity(intent);Memo类要实现Serializable接口,这是最简单的一种对象序列化方式。如果你担心性能,可以用Parcelable,但备忘录这种小对象完全没必要折腾。
3. 实操过程与细节优化:让App从"能用"到"好用"
3.1 添加/编辑/删除功能的完整流程
我把整个交互流程串起来说一遍:
用户点击FloatingActionButton→ 跳转到编辑页,此时编辑页处于"新建模式"(没有传入id)→ 用户输入标题和内容 → 点击保存 → 插入数据库并返回列表页 → 列表页在onResume里刷新数据。
编辑模式呢?用户在列表页点击某一条 → 编辑页收到Memo对象 → 把标题、内容填充到输入框 → 点击保存时判断memoId != -1,走update分支。
删除我做了两种入口:列表页长按弹出确认对话框,以及编辑页里放一个删除按钮。长按删除是我个人比较推荐的方式,误触率低,交互也顺手。
尤其要注意的是保存前判断标题和内容是否都为空。我遇到过很多次,用户新建了一条空数据,列表里就出现一条空白记录,看着碍眼还删不掉(因为你点进去也找不到删除按钮)。所以保存时的防御性判断非常重要:
if (TextUtils.isEmpty(title) && TextUtils.isEmpty(content)) { Toast.makeText(this, "标题和内容不能同时为空", Toast.LENGTH_SHORT).show(); return; }时间字段不要用System.currentTimeMillis()存到onCreate里编译期常量,而要在每次保存时动态取毫秒数。因为如果编译期固定,App加了一行代码重新编译后所有记录的时间都会是一个固定值。
3.2 时间戳、搜索、提醒等增强功能的实现思路
基础CRUD顺畅之后,就能往"好用"方向打磨了。这里分享几个我实际加的增强功能思路。
时间格式化:时间戳显示成"2025-02-18 14:30"比"1740000000000"直观得多。用SimpleDateFormat就能格式化,但记得这个类有线程问题,在Adapter里频繁创建开销大,可以定义成静态变量:
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.getDefault());如果你要求列表的时间显示成"今天 14:30"、"昨天 09:12"这种相对时间,就需要自己写判断逻辑:将今天的凌晨零点作为分界线,比较前后差异。这个功能很能体现细节,我看很多成熟App都在用,建议做上。
搜索功能:不用新开Activity,直接用SearchView或者简单的EditText,在TextWatcher里根据关键词过滤内存中memoList。但是注意,如果你有很多条记录,最好在SQL上进行模糊查询而不是全部加载到内存再过滤:
SELECT * FROM memo WHERE title LIKE '%关键词%' OR content LIKE '%关键词%' ORDER BY update_time DESC在LIKE条件前面加title或content字段时,注意需要转义%和_,否则用户输入通配符会得到意外结果。这个小坑我踩过。
提醒功能:备忘录加提醒的逻辑是:给数据库增加remind_time字段 → 编辑页提供时间选择器 → 保存时调用AlarmManager设置一次性的闹钟广播 → 在BroadcastReceiver里发通知。核心代码不长:
Intent intent = new Intent(context, MemoAlarmReceiver.class); intent.putExtra("memo_id", id); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, id, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, remindTime, pendingIntent);提醒功能涉及Android 12以上的精确闹钟权限,需要动态申请SCHEDULE_EXACT_ALARM,这个权限在部分国产ROM上默认关闭,需要引导用户去系统设置里开启。如果你第一次做,建议先不要加这个功能,等CRUD稳定了再挑战。
3.3 踩坑记录:输入法遮挡、键盘弹出、空数据页面
这是我实操过程中翻车最多的地方,挑三个典型问题说。
问题一:键盘弹出来把输入框挡住。
这个在安卓上太经典了。清单文件里给EditActivity配置:
android:windowSoftInputMode="adjustResize"这样软键盘弹出时,页面会重新调整高度,输入框会自动顶上去。如果你用的是ScrollView包裹,要确保ScrollView里有足够的可滚动孩子,不然adjustResize基本无效。
adjustPan也可以解决问题,但它是直接把整个布局往上顶,体验稍差。一般推荐adjustResize。
问题二:输入框内容被自动保存但每次进来都会恢复旧数据。
这是因为Activity在屏幕旋转或内存不足重启时会发生重建,EditText自带状态恢复机制。如果你在onCreate里给输入框填充了数据,但没做逻辑判断,就会覆盖掉用户刚输入的内容。
解决办法是:新建模式下,如果输入框有已保存内容就不覆盖:
if (savedInstanceState == null) { // 首次创建才填充数据 if (memo != null) { etTitle.setText(memo.getTitle()); etContent.setText(memo.getContent()); } }这里savedInstanceState是Activity非空时的数据包,通过它判断是不是重建,非常关键。
问题三:数据为空时列表一片空白,用户以为App卡死了。
我建议加一个空视图。当memoList.size() == 0时,隐藏RecyclerView,显示一个居中ImageView加TextView,提示"还没有备忘录,点击右下角新建"。这个小细节能让整体的完成度提升一大截,界面不再冷冰冰的。
4. 常见问题与排查技巧
4.1 数据库版本升级与表结构变更
这是初学者最容易忽略,也最致命的问题。
假设你已经装了第一版App,数据库里有一张memo表,结构是(id, title, content, update_time)。后来你想加一个category字段,于是修改了建表语句。等你在手机上运行新版本,会发现App直接崩溃,日志显示:
android.database.sqlite.SQLiteException: no such column: category (code 1 SQLITE_ERROR)为什么?因为数据库是持久化的,第一次安装时onCreate如果已经执行过,后续App更新不会再重复创建表。表结构还是老样子,但你代码里却去查category字段。
解决办法就是利用onUpgrade方法。每次升级时,把数据库版本号从1改成2,在onUpgrade里执行ALTER TABLE memo ADD COLUMN category TEXT。
为了稳妥,我推荐一套几乎所有旧sqlite项目通用的"升级套路":
- 先把版本号
DB_VERSION++; - 在
onUpgrade里判断oldVersion < 2时就执行对应的ALTER; - 不要图省事
drop table再重建,因为那会清空用户所有数据,属于设计灾难。
如果要做大改动,可以先把老数据查出来备份,再删除表,创建新表,最后填回数据。这个操作要放在事务里执行,确保不出半路崩溃导致数据丢失。
4.2 RecyclerView不刷新/数据不同步
这个问题的典型表现是:从编辑页保存完返回列表页,数据没变化,必须杀掉App重进才能看到新数据。
根本原因是:你在编辑页往数据库写了数据,但列表页的memoList还是老内容,适配器不知道数据变了,自然不更新。
规避办法是统一在MainActivity.onResume()里重新查询数据库,然后调用adapter.notifyDataSetChanged()。因为每次从编辑页返回都会触发onResume,这是最省心的方案。
如果你非要只在首次onCreate里加载数据,也可以,但必须通过startActivityForResult或者ActivityResultLauncher拿返回结果再去刷新。相比之下onResume方案写代码最少、不容易漏。
还有一个细节:notifyDataSetChanged()虽然效率不高,但很多新手喜欢用notifyItemInserted局部刷新,结果position计算错了导致崩溃。对于小数据量来说,直接整体刷新,性能完全不是问题,千万别为了优化而引入更复杂的bug。
另外建议在删除数据后重新
queryAll()而不是删除memoList中对应项。因为数据库操作和内存列表操作很容易出现不同步——比如数据库删除失败但你从内存里移除了,界面正常但下次重启记录还在,这就产生了"幽灵数据"。以数据库查询结果为准是最安全的。
4.3 真机调试注意点与打包生成APK
模拟器调试有时候会遇到传感器、性能、输入法等和真机差异较大的情况,我强烈建议一早就用真机调试。需要做的步骤:
- 手机开启开发者选项:设置 → 关于手机 → 连续点击"版本号"7次,返回设置界面会多出"开发者选项";
- 打开USB调试;
- 用数据线连接电脑,手机弹出"允许USB调试"时勾选"始终允许";
- Android Studio 点击运行按钮,选择你的设备。
如果你用Windows,装不上驱动或者识别不了设备,可能是因为缺少对应的手机驱动。这个卸载重装手机助手类软件基本能解决,但我不推荐装360手机助手之类的东西,广告太多。直接在AS的SDK Manager里勾选Google USB Driver再安装即可。
打包APK很容易:菜单栏Build→Generate Signed App Bundle / APK→ 选择APK→ 新建一个Key Store并填写信息 → 选择release → 完成。生成的APK在app/release/目录下。
注意release包体积通常只有几MB,如果你打包出来还是几十MB,看看是不是minifyEnabled没打开:
buildTypes { release { minifyEnabled true shrinkResources true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } }minifyEnabled true会去掉没用的代码,shrinkResources true会去掉未引用的资源,这两步能显著减小包体,还可以防止别人通过反编译轻松看清你的逻辑(DELETE hint,虽然只是提高门槛)。
另外,在有中文路径的目录下编译有时会报各种莫名错误。如果Android Studio提示路径错误,检查项目路径是否带中文,尽量把项目放在纯英文路径下,比如D:\AndroidProjects\MemoApp。
4.4 一些提高效率的调试技巧
最后顺手分享几个让开发过程顺畅的经验。
日志不要用System.out.println,请用Log.d(TAG, "message")。在logcat里用Log输出可以带上Tag和级别过滤,排查问题时效率翻倍。System.out的输出很不显眼,还会被系统噪音淹没。
数据库调试可以直接在AS自带的App Inspection工具里查看。打开方式:菜单栏View→Tool Windows→App Inspection,选好设备,就能看到当前连接进程的数据库表格,还能直接执行SQL调试。这比去文件管理器找xx.db自己拉出来查看高效多了。
如果你想让别人看到App运行效果,可以录屏后在模拟器或真机上操作。模拟器自带的录屏功能会输出没有声音的画面,真机录屏需要各种权限,直接用AS的Screen Record工具能生成带音频的录像,很方便。
我在实际开发中总觉得"实现的代码量"和"真正踩的坑数量"不成正比,遇到问题先看logcat红色部分,不要凭感觉瞎猜。比如有些新手报错看都不看,直接上来问"为什么我的App闪退",这种问题很难直接回答。自己把报错信息复制到搜索引擎,十有八九都能找到解决方案,这才是自学之路该有的样子。
备忘录App做完之后,接下来的扩展空间还很大:比如把数据备份到云端、加指纹锁、做桌面小部件、生成PDF导出等等。我从这个项目里练到的最核心能力不是Android API背得多熟,而是"拿到需求后如何拆解、选型、实现、调试"的完整闭环。希望这篇文章也能帮你少走几步弯路,做出属于自己的那款备忘录。
本文还有配套的精品资源,点击获取