news 2026/9/7 3:56:00

基于Android Studio的备忘录App开发实战:从建项目到打包APK

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Android Studio的备忘录App开发实战:从建项目到打包APK

简介:基于 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,提供insertupdatedeletequeryAll四个方法。举例说明插入逻辑:

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条件前面加titlecontent字段时,注意需要转义%_,否则用户输入通配符会得到意外结果。这个小坑我踩过。

提醒功能:备忘录加提醒的逻辑是:给数据库增加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

模拟器调试有时候会遇到传感器、性能、输入法等和真机差异较大的情况,我强烈建议一早就用真机调试。需要做的步骤:

  1. 手机开启开发者选项:设置 → 关于手机 → 连续点击"版本号"7次,返回设置界面会多出"开发者选项";
  2. 打开USB调试;
  3. 用数据线连接电脑,手机弹出"允许USB调试"时勾选"始终允许";
  4. Android Studio 点击运行按钮,选择你的设备。

如果你用Windows,装不上驱动或者识别不了设备,可能是因为缺少对应的手机驱动。这个卸载重装手机助手类软件基本能解决,但我不推荐装360手机助手之类的东西,广告太多。直接在AS的SDK Manager里勾选Google USB Driver再安装即可。

打包APK很容易:菜单栏BuildGenerate 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工具里查看。打开方式:菜单栏ViewTool WindowsApp Inspection,选好设备,就能看到当前连接进程的数据库表格,还能直接执行SQL调试。这比去文件管理器找xx.db自己拉出来查看高效多了。

如果你想让别人看到App运行效果,可以录屏后在模拟器或真机上操作。模拟器自带的录屏功能会输出没有声音的画面,真机录屏需要各种权限,直接用AS的Screen Record工具能生成带音频的录像,很方便。

我在实际开发中总觉得"实现的代码量"和"真正踩的坑数量"不成正比,遇到问题先看logcat红色部分,不要凭感觉瞎猜。比如有些新手报错看都不看,直接上来问"为什么我的App闪退",这种问题很难直接回答。自己把报错信息复制到搜索引擎,十有八九都能找到解决方案,这才是自学之路该有的样子。

备忘录App做完之后,接下来的扩展空间还很大:比如把数据备份到云端、加指纹锁、做桌面小部件、生成PDF导出等等。我从这个项目里练到的最核心能力不是Android API背得多熟,而是"拿到需求后如何拆解、选型、实现、调试"的完整闭环。希望这篇文章也能帮你少走几步弯路,做出属于自己的那款备忘录。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 3:55:52

Coding Agent实战指南:IDE插件选择、云端环境与结对编程

最近有不少朋友问我同一个问题&#xff1a;团队把 Coding Agent 接进日常开发之后&#xff0c;Commit 数量确实上去了&#xff0c;但 Code Review 的工作量反而爆了。这个现象我太熟悉了&#xff0c;因为我自己也完整经历过一个从兴奋到怀疑、再到重新掌握主动权的循环。这篇是…

作者头像 李华
网站建设 2026/9/7 3:55:46

和利时LK系列PLC在隧道监控系统中的应用:从硬件选型到调试全解析

简介&#xff1a;基于和利时LK系列PLC的隧道监控系统是一份面向PLC/PAC工程师及隧道监控系统设计人员的PDF技术资料。内容紧密围绕长隧道与特长隧道的安全监控需求&#xff0c;系统阐述以和利时LK系列PLC为核心的综合管理方案&#xff0c;既涵盖环境监测、通风消防、照明及交通…

作者头像 李华
网站建设 2026/9/7 3:55:17

现在性价比高的AI写作辅助平台有哪些品牌?学生党亲测反馈

每到期末、毕业答辩、课题申报阶段&#xff0c;很多学生都会面临论文写作的“多米诺骨牌”难题&#xff1a;选题毫无头绪、大纲搭建逻辑混乱、正文撰写耗时长、参考文献格式出错、查重重复率偏高、AIGC检测告警、本校论文排版标准复杂。纯人工写作不仅需要从零开始构思&#xf…

作者头像 李华
网站建设 2026/9/7 3:50:03

哈工大数据结构44讲:从线性表到图,训练复杂度权衡与算法直觉

看到“哈尔滨工业大学《数据结构》全44讲&#xff5c;线性表、树、图、查找与排序”这个标题&#xff0c;很多人的第一反应是赶紧保存课件视频、找到配套的严蔚敏《数据结构&#xff08;C语言版&#xff09;》电子书&#xff0c;然后从第1讲开始倍速刷到第44讲。这个做法不能说…

作者头像 李华