Android Studio记事本开发实战:从SQLite到RecyclerView的完整项目解析

Android Studio记事本开发实战:从SQLite到RecyclerView的完整项目解析 简介本资源是一套基于Android Studio开发的高分记事本App项目源码专为计算机相关专业本科生毕业设计及期末大作业实践打造已通过导师审核并获98分高分评价切实解决学生缺乏完整、可运行、易理解的安卓实战项目参考的痛点。压缩包共115个文件含60个XML布局与资源文件、19个Java核心逻辑代码、15个PNG图标素材、6个MP3音效资源以及Gradle构建配置、Git版本控制文件等整体大小17.56MB结构规范、模块清晰便于学习者快速掌握UI设计、数据持久化SQLite或SharedPreferences、Activity生命周期管理及基础多媒体集成等关键技能。目前已有454人学习下载源码全部本地编译通过、严格调试可直接运行附带完整工程目录与标准Android项目结构特别适合零基础到中阶开发者开展项目复现、功能拓展与代码剖析。1. 项目概述一个值得深挖的安卓记事本应用最近在整理过往的项目资料翻出了一个基于Android Studio开发的安卓记事本应用源码。这可不是一个简单的“Hello World”级别的Demo而是一个功能相对完整、架构清晰当年在课程设计或毕业设计中能拿高分的实战项目。很多初学者在学完Android基础后想找个项目练手却常常卡在“不知道从何做起”或者“做出来的东西太玩具”的尴尬境地。这个记事本项目恰好填补了这个空白——它涵盖了数据存储、UI交互、列表展示、数据增删改查等核心开发环节是理解一个标准安卓应用开发流程的绝佳样本。这个项目源码的价值不在于它实现了多么炫酷的功能而在于它完整地展示了一个合格的应用应该如何组织代码、管理数据以及处理用户交互。无论是刚入门安卓开发的新手想找一个综合性练手项目还是有一定经验的开发者想回顾基础架构的最佳实践这份源码都能提供直接的参考。接下来我将带你深入拆解这个高分记事本项目的核心设计与实现细节从环境搭建到功能实现再到代码优化和常见问题排查手把手还原一个可运行、可学习、可扩展的完整开发过程。2. 项目整体设计与架构拆解2.1 核心功能需求与设计思路一个记事本应用最核心的需求无外乎“记”和“管”。具体拆解下来主要包括以下几个功能点笔记的创建与编辑用户能够输入标题和正文内容并保存。笔记的列表展示以清晰的方式如列表展示所有已存在的笔记通常需要显示标题、部分内容摘要和创建/修改时间。笔记的查看与修改点击列表项进入详情页查看完整内容并支持编辑更新。笔记的删除支持单条删除通常还会考虑批量删除或滑动删除。数据的持久化存储应用关闭后笔记数据不能丢失。基于这些需求一个典型的设计思路是采用“单Activity 多Fragment”的架构或者经典的“多Activity”导航架构。对于记事本这类工具型应用前者单Activity配合Fragment在管理UI和状态上更为现代和灵活。数据存储方面SQLite数据库是安卓平台内置的轻量级关系型数据库非常适合存储笔记这类结构化数据id, 标题内容时间戳。我们将使用SQLiteOpenHelper来管理数据库的创建和升级。2.2 技术栈选型与工具准备这个项目主要依赖安卓原生开发技术栈无需引入复杂的第三方框架非常适合夯实基础。开发环境Android Studio简称AS。这是谷歌官方的集成开发环境提供了代码编辑、调试、性能分析和模拟器管理等全套工具。确保你安装的是较新的稳定版本如Hedgehog或Iguana。编程语言Java或Kotlin。本项目源码以Java为例进行讲解但所有设计思想同样适用于Kotlin。Kotlin现在是谷歌推荐的首选语言语法更简洁安全。核心组件Activity/Fragment作为用户界面的容器。RecyclerView用于高效展示笔记列表。这是ListView的升级版必须掌握。SQLiteOpenHelper用于管理SQLite数据库。Adapter作为RecyclerView和数据源笔记列表之间的桥梁。UI布局使用XML编写遵循Material Design设计指南能让应用看起来更专业。注意在开始编码前请务必在Android Studio中创建一个新的项目选择“Empty Activity”模板即可。确保项目的build.gradle文件中的compileSdk和targetSdk版本在API 31及以上以兼容较新的安卓设备。2.3 项目目录结构规划一个清晰的项目结构是代码可维护性的基石。在Android Studio中创建项目后我们主要关注app/src/main目录下的几个关键部分app/src/main/ ├── java/com.example.notepad/ │ ├── activity/ # 存放Activity类 (如MainActivity) │ ├── fragment/ # 存放Fragment类 (如NoteListFragment, EditNoteFragment) │ ├── adapter/ # 存放RecyclerView的适配器 (如NoteAdapter) │ ├── db/ # 存放数据库相关类 (如DatabaseHelper, NoteContract) │ ├── model/ # 存放数据模型类 (如Note) │ └── utils/ # 存放工具类 (如时间格式化工具) └── res/ ├── layout/ # 存放布局XML文件 ├── menu/ # 存放选项菜单XML文件 ├── values/ # 存放颜色、字符串、样式等资源 └── drawable/ # 存放图片资源这样的分包方式让业务逻辑activity/fragment、数据管理db/model、视图展示adapter和工具辅助utils各司其职一目了然。3. 核心模块实现详解3.1 数据模型与数据库设计一切从数据开始。我们首先定义笔记的数据模型Model。3.1.1 定义Note实体类在model包下创建Note.java。这是一个简单的POJOPlain Old Java Object类代表一条笔记。package com.example.notepad.model; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; public class Note { private long id; // 主键自增长 private String title; private String content; private long createTime; // 使用时间戳存储方便排序和计算 private long updateTime; // 构造方法、Getter和Setter方法省略... // 建议使用Android Studio的快捷键自动生成AltInsert。 // 一个实用的方法将时间戳转换为易读的字符串 public String getFormattedCreateTime() { SimpleDateFormat sdf new SimpleDateFormat(yyyy-MM-dd HH:mm, Locale.getDefault()); return sdf.format(new Date(createTime)); } }3.1.2 设计数据库表结构在db包下我们先创建一个契约类NoteContract.java用于定义表名和列名的常量避免在代码中硬编码字符串这是一个好习惯。package com.example.notepad.db; public final class NoteContract { private NoteContract() {} // 防止被实例化 public static class NoteEntry { public static final String TABLE_NAME notes; public static final String COLUMN_ID _id; public static final String COLUMN_TITLE title; public static final String COLUMN_CONTENT content; public static final String COLUMN_CREATE_TIME create_time; public static final String COLUMN_UPDATE_TIME update_time; } }3.1.3 实现DatabaseHelper接着在db包下创建DatabaseHelper.java继承SQLiteOpenHelper。package com.example.notepad.db; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class DatabaseHelper extends SQLiteOpenHelper { // 数据库信息 private static final String DATABASE_NAME notepad.db; private static final int DATABASE_VERSION 1; // 初始版本为1 // 创建表的SQL语句 private static final String SQL_CREATE_ENTRIES CREATE TABLE NoteContract.NoteEntry.TABLE_NAME ( NoteContract.NoteEntry.COLUMN_ID INTEGER PRIMARY KEY AUTOINCREMENT, NoteContract.NoteEntry.COLUMN_TITLE TEXT, NoteContract.NoteEntry.COLUMN_CONTENT TEXT, NoteContract.NoteEntry.COLUMN_CREATE_TIME INTEGER, NoteContract.NoteEntry.COLUMN_UPDATE_TIME INTEGER); // 删除表的SQL语句 private static final String SQL_DELETE_ENTRIES DROP TABLE IF EXISTS NoteContract.NoteEntry.TABLE_NAME; public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } Override public void onCreate(SQLiteDatabase db) { // 首次创建数据库时执行 db.execSQL(SQL_CREATE_ENTRIES); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 当DATABASE_VERSION增加时此方法被调用。用于迁移数据。 // 简单处理删除旧表创建新表。生产环境需要更复杂的数据迁移逻辑 db.execSQL(SQL_DELETE_ENTRIES); onCreate(db); } }实操心得onUpgrade方法在生产环境中需要谨慎处理。如果应用已发布用户本地有数据直接删表会导致数据丢失。正确的做法是根据oldVersion和newVersion逐步执行ALTER TABLE语句来升级表结构或者将旧数据备份、转移。对于学习项目简单处理是可以接受的但一定要有这个意识。3.2 用户界面UI布局实现UI是用户直接交互的部分良好的设计能提升用户体验。3.2.1 主界面布局activity_main.xml主界面通常是一个简单的容器用于承载Fragment。如果我们采用单Activity架构它可能只是一个FrameLayout。?xml version1.0 encodingutf-8? FrameLayout xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto android:idid/container android:layout_widthmatch_parent android:layout_heightmatch_parent !-- Fragment将在这里动态加载 -- /FrameLayout3.2.2 笔记列表项布局item_note.xml这是RecyclerView中每个笔记条目Item的样式。设计要简洁明了信息突出。?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightwrap_content android:orientationvertical android:padding16dp android:background?android:attr/selectableItemBackground !-- 添加点击水波纹效果 -- android:clickabletrue android:focusabletrue TextView android:idid/tv_note_title android:layout_widthmatch_parent android:layout_heightwrap_content android:textSize18sp android:textStylebold android:maxLines1 android:ellipsizeend / TextView android:idid/tv_note_content_preview android:layout_widthmatch_parent android:layout_heightwrap_content android:textSize14sp android:textColorandroid:color/darker_gray android:maxLines2 android:ellipsizeend android:layout_marginTop4dp / TextView android:idid/tv_note_time android:layout_widthmatch_parent android:layout_heightwrap_content android:textSize12sp android:textColorandroid:color/darker_gray android:layout_marginTop8dp / /LinearLayout3.2.3 编辑笔记界面布局fragment_edit_note.xml这个界面包含两个EditText用于标题和内容以及保存按钮。?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical android:padding16dp EditText android:idid/et_note_title android:layout_widthmatch_parent android:layout_heightwrap_content android:hint请输入标题 android:textSize20sp android:textStylebold android:maxLines1 android:inputTypetextCapSentences / EditText android:idid/et_note_content android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 android:hint开始记录... android:gravitytop android:textSize16sp android:inputTypetextMultiLine android:minLines10 / Button android:idid/btn_save_note android:layout_widthmatch_parent android:layout_heightwrap_content android:text保存笔记 android:layout_marginTop16dp / /LinearLayout3.3 数据操作层CRUD实现有了数据库和模型我们需要一个“仓库”类来统一管理所有数据操作这样Activity/Fragment就不需要直接操作数据库细节代码更清晰。在db包下创建NoteRepository.java。package com.example.notepad.db; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import com.example.notepad.model.Note; import java.util.ArrayList; import java.util.List; public class NoteRepository { private DatabaseHelper dbHelper; public NoteRepository(Context context) { dbHelper new DatabaseHelper(context); } // 增 (Create) public long insertNote(Note note) { SQLiteDatabase db dbHelper.getWritableDatabase(); ContentValues values new ContentValues(); values.put(NoteContract.NoteEntry.COLUMN_TITLE, note.getTitle()); values.put(NoteContract.NoteEntry.COLUMN_CONTENT, note.getContent()); long currentTime System.currentTimeMillis(); values.put(NoteContract.NoteEntry.COLUMN_CREATE_TIME, currentTime); values.put(NoteContract.NoteEntry.COLUMN_UPDATE_TIME, currentTime); long newRowId db.insert(NoteContract.NoteEntry.TABLE_NAME, null, values); db.close(); // 记得关闭数据库连接 return newRowId; // 返回新插入行的ID-1表示失败 } // 查 (Read All) public ListNote getAllNotes() { ListNote noteList new ArrayList(); SQLiteDatabase db dbHelper.getReadableDatabase(); // 定义要查询的列 String[] projection { NoteContract.NoteEntry.COLUMN_ID, NoteContract.NoteEntry.COLUMN_TITLE, NoteContract.NoteEntry.COLUMN_CONTENT, NoteContract.NoteEntry.COLUMN_CREATE_TIME, NoteContract.NoteEntry.COLUMN_UPDATE_TIME }; // 按修改时间倒序排列最新的在最前面 String sortOrder NoteContract.NoteEntry.COLUMN_UPDATE_TIME DESC; Cursor cursor db.query( NoteContract.NoteEntry.TABLE_NAME, projection, null, // WHERE 子句null表示查询所有 null, // WHERE 子句的参数 null, // GROUP BY null, // HAVING sortOrder ); // 遍历Cursor将数据转换为Note对象 while (cursor.moveToNext()) { Note note new Note(); note.setId(cursor.getLong(cursor.getColumnIndexOrThrow(NoteContract.NoteEntry.COLUMN_ID))); note.setTitle(cursor.getString(cursor.getColumnIndexOrThrow(NoteContract.NoteEntry.COLUMN_TITLE))); note.setContent(cursor.getString(cursor.getColumnIndexOrThrow(NoteContract.NoteEntry.COLUMN_CONTENT))); note.setCreateTime(cursor.getLong(cursor.getColumnIndexOrThrow(NoteContract.NoteEntry.COLUMN_CREATE_TIME))); note.setUpdateTime(cursor.getLong(cursor.getColumnIndexOrThrow(NoteContract.NoteEntry.COLUMN_UPDATE_TIME))); noteList.add(note); } cursor.close(); db.close(); return noteList; } // 查 (Read Single by ID) public Note getNoteById(long id) { SQLiteDatabase db dbHelper.getReadableDatabase(); String selection NoteContract.NoteEntry.COLUMN_ID ?; String[] selectionArgs { String.valueOf(id) }; Cursor cursor db.query(...); // 类似getAllNotes但加上selection和selectionArgs Note note null; if (cursor.moveToFirst()) { note new Note(); // ... 从cursor填充note对象 } cursor.close(); db.close(); return note; } // 改 (Update) public int updateNote(Note note) { SQLiteDatabase db dbHelper.getWritableDatabase(); ContentValues values new ContentValues(); values.put(NoteContract.NoteEntry.COLUMN_TITLE, note.getTitle()); values.put(NoteContract.NoteEntry.COLUMN_CONTENT, note.getContent()); values.put(NoteContract.NoteEntry.COLUMN_UPDATE_TIME, System.currentTimeMillis()); String selection NoteContract.NoteEntry.COLUMN_ID ?; String[] selectionArgs { String.valueOf(note.getId()) }; int count db.update( NoteContract.NoteEntry.TABLE_NAME, values, selection, selectionArgs ); db.close(); return count; // 返回受影响的行数 } // 删 (Delete) public int deleteNoteById(long id) { SQLiteDatabase db dbHelper.getWritableDatabase(); String selection NoteContract.NoteEntry.COLUMN_ID ?; String[] selectionArgs { String.valueOf(id) }; int deletedRows db.delete(NoteContract.NoteEntry.TABLE_NAME, selection, selectionArgs); db.close(); return deletedRows; } }注意事项数据库操作特别是写操作是耗时的绝对不能在主线程UI线程中执行否则会导致应用无响应ANR。我们稍后会在介绍Adapter和界面交互时讨论如何在线程中处理。3.4 列表展示与适配器Adapter实现RecyclerView是展示列表的核心而Adapter是其灵魂。3.4.1 创建NoteAdapter在adapter包下创建NoteAdapter.java。它负责将ListNote中的数据绑定到item_note.xml布局上。package com.example.notepad.adapter; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.example.notepad.R; import com.example.notepad.model.Note; import java.util.List; public class NoteAdapter extends RecyclerView.AdapterNoteAdapter.NoteViewHolder { private ListNote mNoteList; private OnItemClickListener mListener; // 定义点击事件的接口 public interface OnItemClickListener { void onItemClick(Note note); void onItemLongClick(Note note); // 可选的用于长按删除 } public NoteAdapter(ListNote noteList, OnItemClickListener listener) { this.mNoteList noteList; this.mListener listener; } NonNull Override public NoteViewHolder onCreateViewHolder(NonNull ViewGroup parent, int viewType) { // 加载列表项布局 View itemView LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_note, parent, false); return new NoteViewHolder(itemView); } Override public void onBindViewHolder(NonNull NoteViewHolder holder, int position) { Note currentNote mNoteList.get(position); // 绑定数据到ViewHolder的视图上 holder.tvTitle.setText(currentNote.getTitle()); holder.tvContentPreview.setText(getContentPreview(currentNote.getContent())); holder.tvTime.setText(currentNote.getFormattedCreateTime()); // 设置点击事件 holder.itemView.setOnClickListener(v - { if (mListener ! null) { mListener.onItemClick(currentNote); } }); // 设置长按事件例如触发删除对话框 holder.itemView.setOnLongClickListener(v - { if (mListener ! null) { mListener.onItemLongClick(currentNote); return true; // 消费长按事件 } return false; }); } Override public int getItemCount() { return mNoteList null ? 0 : mNoteList.size(); } // 一个辅助方法用于生成内容预览取前50个字符 private String getContentPreview(String content) { if (content null || content.isEmpty()) { return (无内容); } return content.length() 50 ? content.substring(0, 50) ... : content; } // 更新数据的方法 public void setNotes(ListNote notes) { mNoteList notes; notifyDataSetChanged(); // 通知RecyclerView数据已变更刷新UI } // ViewHolder内部类缓存视图引用避免频繁findViewById static class NoteViewHolder extends RecyclerView.ViewHolder { TextView tvTitle; TextView tvContentPreview; TextView tvTime; NoteViewHolder(NonNull View itemView) { super(itemView); tvTitle itemView.findViewById(R.id.tv_note_title); tvContentPreview itemView.findViewById(R.id.tv_note_content_preview); tvTime itemView.findViewById(R.id.tv_note_time); } } }3.4.2 在Fragment中使用RecyclerView在fragment包下创建NoteListFragment.java它是笔记列表的主界面。package com.example.notepad.fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.example.notepad.R; import com.example.notepad.adapter.NoteAdapter; import com.example.notepad.db.NoteRepository; import com.example.notepad.model.Note; import java.util.ArrayList; import java.util.List; public class NoteListFragment extends Fragment implements NoteAdapter.OnItemClickListener { private RecyclerView mRecyclerView; private NoteAdapter mAdapter; private ListNote mNoteList new ArrayList(); private NoteRepository mRepository; Nullable Override public View onCreateView(NonNull LayoutInflater inflater, Nullable ViewGroup container, Nullable Bundle savedInstanceState) { View rootView inflater.inflate(R.layout.fragment_note_list, container, false); mRecyclerView rootView.findViewById(R.id.recycler_view_notes); return rootView; } Override public void onViewCreated(NonNull View view, Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); mRepository new NoteRepository(requireContext()); // 设置RecyclerView的布局管理器线性布局垂直方向 mRecyclerView.setLayoutManager(new LinearLayoutManager(getContext())); // 初始化适配器 mAdapter new NoteAdapter(mNoteList, this); mRecyclerView.setAdapter(mAdapter); // 加载数据 loadNotes(); } private void loadNotes() { // 注意数据库查询是IO操作应该在子线程进行。 // 这里为了简化先在主线程演示。实际项目务必使用AsyncTask, ThreadPool, 或Room等库的异步查询。 new Thread(() - { ListNote notes mRepository.getAllNotes(); // 回到主线程更新UI requireActivity().runOnUiThread(() - { mNoteList.clear(); mNoteList.addAll(notes); mAdapter.setNotes(mNoteList); // 通知适配器数据变化 }); }).start(); } Override public void onItemClick(Note note) { // 点击笔记项跳转到编辑页面并传递笔记ID EditNoteFragment editFragment EditNoteFragment.newInstance(note.getId()); // 使用FragmentManager进行切换这里假设主Activity支持Fragment替换 requireActivity().getSupportFragmentManager().beginTransaction() .replace(R.id.container, editFragment) .addToBackStack(null) // 加入返回栈方便用户返回列表 .commit(); } Override public void onItemLongClick(Note note) { // 长按笔记项弹出删除确认对话框 new android.app.AlertDialog.Builder(requireContext()) .setTitle(删除笔记) .setMessage(确定要删除\ note.getTitle() \吗) .setPositiveButton(删除, (dialog, which) - deleteNote(note.getId())) .setNegativeButton(取消, null) .show(); } private void deleteNote(long id) { new Thread(() - { int deleted mRepository.deleteNoteById(id); if (deleted 0) { requireActivity().runOnUiThread(this::loadNotes); // 删除成功后刷新列表 } }).start(); } }对应的fragment_note_list.xml布局文件很简单就是一个RecyclerView。?xml version1.0 encodingutf-8? androidx.recyclerview.widget.RecyclerView xmlns:androidhttp://schemas.android.com/apk/res/android android:idid/recycler_view_notes android:layout_widthmatch_parent android:layout_heightmatch_parent /3.5 编辑与保存功能实现3.5.1 创建EditNoteFragment在fragment包下创建EditNoteFragment.java用于创建新笔记或编辑已有笔记。package com.example.notepad.fragment; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import com.example.notepad.R; import com.example.notepad.db.NoteRepository; import com.example.notepad.model.Note; public class EditNoteFragment extends Fragment { private static final String ARG_NOTE_ID note_id; private EditText mEtTitle, mEtContent; private Button mBtnSave; private NoteRepository mRepository; private long mNoteId -1; // -1 表示新建否则表示编辑现有笔记 // 创建Fragment实例的工厂方法便于传递参数 public static EditNoteFragment newInstance(long noteId) { EditNoteFragment fragment new EditNoteFragment(); Bundle args new Bundle(); args.putLong(ARG_NOTE_ID, noteId); fragment.setArguments(args); return fragment; } Override public void onCreate(Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); mRepository new NoteRepository(requireContext()); if (getArguments() ! null) { mNoteId getArguments().getLong(ARG_NOTE_ID, -1); } } Nullable Override public View onCreateView(NonNull LayoutInflater inflater, Nullable ViewGroup container, Nullable Bundle savedInstanceState) { View rootView inflater.inflate(R.layout.fragment_edit_note, container, false); mEtTitle rootView.findViewById(R.id.et_note_title); mEtContent rootView.findViewById(R.id.et_note_content); mBtnSave rootView.findViewById(R.id.btn_save_note); return rootView; } Override public void onViewCreated(NonNull View view, Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); // 如果是编辑模式加载已有笔记数据 if (mNoteId ! -1) { loadNoteData(mNoteId); } mBtnSave.setOnClickListener(v - saveNote()); } private void loadNoteData(long id) { new Thread(() - { Note note mRepository.getNoteById(id); if (note ! null) { requireActivity().runOnUiThread(() - { mEtTitle.setText(note.getTitle()); mEtContent.setText(note.getContent()); }); } }).start(); } private void saveNote() { String title mEtTitle.getText().toString().trim(); String content mEtContent.getText().toString().trim(); if (title.isEmpty()) { Toast.makeText(getContext(), 标题不能为空, Toast.LENGTH_SHORT).show(); return; } final Note note new Note(); note.setTitle(title); note.setContent(content); new Thread(() - { boolean success; if (mNoteId -1) { // 新建 long newId mRepository.insertNote(note); success newId ! -1; } else { // 更新 note.setId(mNoteId); int updated mRepository.updateNote(note); success updated 0; } final boolean finalSuccess success; requireActivity().runOnUiThread(() - { if (finalSuccess) { Toast.makeText(getContext(), 保存成功, Toast.LENGTH_SHORT).show(); // 保存成功后返回上一个Fragment列表页 requireActivity().getSupportFragmentManager().popBackStack(); } else { Toast.makeText(getContext(), 保存失败, Toast.LENGTH_SHORT).show(); } }); }).start(); } }4. 项目集成、优化与问题排查4.1 主Activity集成与导航现在我们需要一个主Activity来承载这些Fragment。修改MainActivity.java。package com.example.notepad.activity; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; import com.example.notepad.R; import com.example.notepad.fragment.NoteListFragment; public class MainActivity extends AppCompatActivity { Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化时加载笔记列表Fragment if (savedInstanceState null) { getSupportFragmentManager().beginTransaction() .replace(R.id.container, new NoteListFragment()) .commit(); } } }同时在AndroidManifest.xml中确保主Activity已正确声明。activity android:name.activity.MainActivity android:exportedtrue intent-filter action android:nameandroid.intent.action.MAIN / category android:nameandroid.intent.category.LAUNCHER / /intent-filter /activity4.2 性能优化与进阶思考一个基础功能完成的应用距离“高分项目”还有距离。以下是一些优化和进阶方向异步处理与线程安全我们上面在Fragment中直接使用了new Thread()这在简单场景下可行但不利于管理和生命周期感知。应使用AsyncTask已废弃、ExecutorService线程池或更现代的Kotlin协程、RxJava以及LiveData配合ViewModel来优雅地处理异步任务和UI更新。使用Room持久化库SQLiteOpenHelper和原生SQL操作比较繁琐且易错。谷歌推荐使用Room库它是SQLite的抽象层能提供编译时SQL检查、方便的ORM映射和与LiveData/RxJava的无缝集成。这是现代安卓开发数据持久化的标准做法。列表性能优化DiffUtil在RecyclerView.Adapter的setNotes方法中使用DiffUtil来计算数据差异并局部更新而不是粗暴地notifyDataSetChanged()可以极大提升列表更新效率。图片处理如果笔记支持图片务必使用Glide或Picasso等图片加载库它们能自动处理缓存、压缩和生命周期。用户体验优化空状态视图当列表为空时显示一个友好的提示如“还没有笔记点击右下角按钮创建”。搜索功能在NoteRepository中增加根据标题或内容搜索笔记的方法。笔记分类/标签扩展数据库表结构支持为笔记添加标签或分类。数据备份/导出提供将笔记导出为文本文件或JSON格式的功能。4.3 常见问题与排查技巧实录在开发过程中你几乎一定会遇到下面这些问题。这里记录了我的踩坑经验和解决方法。问题1RecyclerView不显示数据或列表为空。排查步骤检查数据源在loadNotes()方法中在子线程查询后打印notes列表的size()确认数据库确实有数据。检查Adapter绑定在onBindViewHolder中打印currentNote的信息看数据是否正确传递到了ViewHolder。检查布局文件确认item_note.xml中的TextView的id与ViewHolder中findViewById使用的id完全一致区分大小写。检查RecyclerView设置确认在Fragment中为RecyclerView设置了LayoutManager(setLayoutManager)。根本原因十有八九是数据没加载到或者Adapter没有正确关联数据和视图。问题2应用运行后直接崩溃报错“Database not open”。排查步骤检查DatabaseHelper的onCreate和onUpgrade方法中的SQL语句是否有语法错误如缺少逗号、括号。确保只在需要的时候如执行CRUD操作前调用getWritableDatabase()或getReadableDatabase()并在操作完成后及时调用db.close()。注意不要在多个线程中共享同一个SQLiteDatabase实例。解决方案在NoteRepository的每个方法中按“打开数据库 - 操作 - 关闭数据库”的流程进行。对于更复杂的管理可以考虑使用单例模式管理一个全局的SQLiteOpenHelper实例但关闭数据库的逻辑要小心。问题3编辑笔记后返回列表列表没有实时刷新。原因NoteListFragment的loadNotes()方法只在onViewCreated中调用了一次。编辑保存后返回列表Fragment并没有重新加载数据。解决方案方案A简单在NoteListFragment的onResume()生命周期方法中也调用loadNotes()。这样每次从编辑页返回列表都会刷新。方案B推荐使用ViewModelLiveData。让笔记列表数据成为一个被观察的LiveData。当在编辑页保存数据后通过更新ViewModel中的数据源自动通知NoteListFragment更新UI。这是更符合架构规范的做法。问题4在子线程更新UI时偶尔会报“View not attached to window”错误。原因当Fragment或Activity已经销毁例如用户快速返回但子线程的任务完成后仍尝试调用runOnUiThread来更新UI就会抛出此异常。解决方案在调用UI更新前检查Fragment/Activity是否还“活着”。if (isAdded() getActivity() ! null) { // 对于Fragment getActivity().runOnUiThread(...); }或者使用viewLifecycleOwner在Fragment的onViewCreated之后可用来观察LiveData它能自动处理生命周期问题。问题5项目在较新版本的Android Studio上编译报错提示“Manifest merger failed”。原因通常是因为AndroidManifest.xml中的配置与某些依赖库的配置冲突或者compileSdk/targetSdk版本设置有问题。解决方案打开项目根目录和app模块的build.gradle文件确保compileSdk和targetSdk版本一致且足够新如33。在AndroidManifest.xml的application标签中添加tools:replaceandroid:label,android:theme, ...属性具体属性根据错误提示而定。在终端运行./gradlew clean然后./gradlew build查看更详细的错误信息。这个记事本项目麻雀虽小五脏俱全。从零开始实现它你能系统地走一遍安卓应用开发的核心流程需求分析、UI设计、数据建模、数据库操作、列表展示、界面导航和事件处理。理解了这些你就具备了开发大多数工具类安卓应用的基础能力。在把它运行起来之后我建议你不要止步于此尝试去实现我上面提到的“进阶思考”中的一两个功能比如用Room替换原生SQLite或者给笔记加上搜索栏这个过程会让你对安卓开发有更深的理解。本文还有配套的精品资源点击获取