很多初学者学 Android 第一件事就是打开一本教材背四大组件、记生命周期回调结果概念背了一堆真到打开 Android Studio 写项目时还是不知道怎么把界面和逻辑串起来。这其实是学习路径设计的问题。如果换成“先抛出一个能运行、能看到效果的小任务”再围绕任务去补充知识点学习效率会高很多。在童长飞老师的任务驱动式 Android 移动开发基础教学体系中核心思路正是这个每个阶段只做一个可验证的任务用任务结果带动知识记忆。本文结合这套思路以“迷你记事本 App”为例把 Android 开发基础拆成 5 个连贯任务从界面搭建、页面跳转、列表展示到 SQLite 数据存储和增删改查逐步完成一个可运行、可打包的真实小程序。无论你是刚接触 Android 的学生还是想带项目的老师这套流程都值得完整走一遍。1. 为什么学 Android 要从任务驱动开始1.1 传统学习的痛点传统 Android 入门的顺序一般是先讲抽象类、接口、四大组件概念再讲布局文件结构、控件属性最后才让学生动手写一个 Demo。这种路径的问题在于知识被拆得太碎没有“主线任务”牵引。学生听的时候觉得都会但合上教材之后仍然不知道一个 App 从创建到运行到底经历了什么。任务驱动式教学最大的不同是让“完成一个真实功能”成为学习的锚点。每学一个知识点都是为了解决面前这个任务里出现的具体问题。比如“为什么页面跳转要写 Intent”“为什么列表数据变了界面要刷新”——这些问题不是老师硬塞的而是学生在完成任务时自然会遇到的。1.2 任务驱动式设计原则在童长飞老师的任务驱动式教学案例中任务设计遵循几个基本原则任务可运行每一步做完都能生成一个可安装、可点击的项目而不是只写完一段孤立代码。难度阶梯化第一个任务只做静态界面第二个任务加入事件和跳转第三个任务引入数据列表逐步增加复杂度。旧知识在新任务中复现后面任务会反复用到前面的技能让知识在重复使用中固化。每个任务解决一个真实问题不是“写一个布局”而是“做一个登录界面”不是“学一下数据库 API”而是“把记事内容存下来并显示出来”。这种设计思路非常适合基础教学也适合自学者给自己安排学习计划。1.3 本文案例概览本文要完成的迷你记事本 App由以下 5 个任务组成任务核心内容掌握技能任务 1创建工程与登录界面工程结构、XML 布局、控件绑定、Toast任务 2登录跳转与页面生命周期Intent、Activity 跳转、生命周期与日志任务 3记事列表展示RecyclerView、Adapter、ViewHolder、LayoutManager任务 4SQLite 数据存储SQLiteOpenHelper、ContentValues、Cursor、CRUD任务 5添加、编辑、删除记事Dialog 交互、Adapter 回调、列表刷新每个任务都以前一个任务为基础最终得到一个具备“登录 → 查看列表 → 新增 → 编辑 → 删除”完整闭环的 App。2. 开发环境准备2.1 软件与版本说明本文的代码运行环境如下版本可根据你本机实际情况调整重点演示配置思路和代码写法。操作系统Windows 10 / macOS / Linux 均可 IDEAndroid Studio推荐使用 2023.1 及以上版本 JDKJDK 17 Android SDKcompileSdk 34minSdk 24 构建工具Gradle 8.2 左右Android Studio 自动管理 开发语言Java 或 Kotlin本文以 Java 为主需要注意Android Studio 的版本更新速度很快不同版本生成的工程模板略有差异。如果你的 Gradle 版本或 SDK 版本不同一般不影响本文代码只需在创建工程时选择 Empty Views Activity空视图 Activity模板即可。2.2 创建项目打开 Android Studio点击 New Project选择 Empty Views Activity填写项目信息NameNotebook Package namecom.example.notebook Save location自定义 LanguageJava Minimum SDKAPI 24Android 7.0创建完成后建议先运行一次空工程确保模拟器或真机环境正常。本文后续所有文件路径都以这个项目为基础。2.3 项目结构说明创建完成后需要重点关注以下文件app/ ├── src/main/ │ ├── java/com/example/notebook/ │ │ ├── LoginActivity.java │ │ ├── MainActivity.java │ │ ├── Note.java │ │ ├── NoteAdapter.java │ │ ├── DBHelper.java │ │ └── NoteDao.java │ ├── res/ │ │ ├── layout/ │ │ │ ├── activity_login.xml │ │ │ ├── activity_main.xml │ │ │ └── item_note.xml │ │ └── values/ │ └── AndroidManifest.xml └── build.gradleModule 级别的配置后面的任务会按顺序往这个结构里填充内容。第一个任务只涉及登录界面的两个文件。3. 任务 1创建登录界面3.1 任务目标第一个任务要做的事情很单纯显示一个登录界面包含用户名输入框、密码输入框和登录按钮。用户在界面上输入内容后点击按钮能收到提示。这个任务的任务点集中在认识布局文件、理解 LinearLayout 布局、掌握 EditText 和 Button 的基本用法。完成之后你会觉得 Android 界面其实就是用 XML 描述出来的视图结构。3.2 编写登录布局文件路径app/src/main/res/layout/activity_login.xml?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:gravitycenter_horizontal android:padding24dp TextView android:layout_widthwrap_content android:layout_heightwrap_content android:layout_marginTop60dp android:text迷你记事本 android:textSize28sp android:textStylebold / EditText android:idid/et_username android:layout_widthmatch_parent android:layout_heightwrap_content android:layout_marginTop30dp android:hint请输入用户名 android:inputTypetext / EditText android:idid/et_password android:layout_widthmatch_parent android:layout_heightwrap_content android:layout_marginTop16dp android:hint请输入密码 android:inputTypetextPassword / Button android:idid/btn_login android:layout_widthmatch_parent android:layout_heightwrap_content android:layout_marginTop24dp android:text登录 / /LinearLayout这段布局的核心结构是根布局是 LinearLayoutorientationvertical表示子控件从上到下垂直排列。EditText 是输入框inputTypetextPassword表示密码输入时显示为圆点。每个控件都设置了id用于在 Java 代码中通过findViewById找到它。在基础教学中这里还有一个容易被忽略的细节wrap_content让控件包裹内容match_parent让控件撑满父容器。如果对这两个属性理解不透后面做复杂布局时会出现控件“莫名消失”或“撑满整个屏幕”的问题。3.3 编写登录页面代码文件路径app/src/main/java/com/example/notebook/LoginActivity.javapackage com.example.notebook; import android.os.Bundle; import android.text.TextUtils; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; public class LoginActivity extends AppCompatActivity { private EditText etUsername; private EditText etPassword; private Button btnLogin; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); etUsername findViewById(R.id.et_username); etPassword findViewById(R.id.et_password); btnLogin findViewById(R.id.btn_login); btnLogin.setOnClickListener(v - { String username etUsername.getText().toString().trim(); String password etPassword.getText().toString().trim(); if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) { Toast.makeText(LoginActivity.this, 用户名和密码不能为空, Toast.LENGTH_SHORT).show(); return; } if (admin.equals(username) 123456.equals(password)) { Toast.makeText(LoginActivity.this, 登录成功, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(LoginActivity.this, 用户名或密码错误, Toast.LENGTH_SHORT).show(); } }); } }这段代码做了三件事通过setContentView把 XML 布局加载到页面中。通过findViewById绑定控件实例。给登录按钮设置点击监听在监听中判断输入是否为空、账号密码是否正确。这里要强调一个安全边界把账号密码写死在代码里仅仅是教学演示用来体现“输入校验”和“点击事件”的完整流程。真实项目中登录校验必须放到服务端完成客户端不能保存明文密码。3.4 修改启动入口要让系统启动时先打开登录页需要修改AndroidManifest.xmlactivity android:name.LoginActivity android:exportedtrue intent-filter action android:nameandroid.intent.action.MAIN / category android:nameandroid.intent.category.LAUNCHER / /intent-filter /activity同时把 MainActivity 的入口标签移除否则应用启动时会同时存在两个可启动 Activity系统会弹窗让你选择启动哪个不符合预期。运行效果是输入为空点击登录提示“用户名和密码不能为空”输入 admin / 123456提示“登录成功”其他组合提示“用户名或密码错误”。这时候第一个任务就算完成了。你已经完成了 Android 开发中最基础也是最重要的“界面 → 绑定 → 事件”闭环。4. 任务 2页面跳转与 Activity 生命周期4.1 任务目标任务 1 运行后即使登录成功也只是弹一个 Toast没有进入真正的 App 主页。任务 2 的核心任务就是解决“登录成功后如何跳到主页面”的问题。这里会引入两个重要概念Intent 和 Activity 生命周期。4.2 创建主页面布局文件路径app/src/main/res/layout/activity_main.xml?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 TextView android:idid/tv_welcome android:layout_widthmatch_parent android:layout_heightwrap_content android:text欢迎使用迷你记事本 android:textSize20sp / androidx.recyclerview.widget.RecyclerView android:idid/rv_notes android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 android:layout_marginTop12dp / /LinearLayout这里提前加入了 RecyclerView 容器但它现在还没有数据。任务 3 会专门填充它。主页面布局中使用了layout_weight1让 RecyclerView 占满剩余空间这个用法在基础教学里很常见建议顺手掌握。4.3 修改 MainActivity文件路径app/src/main/java/com/example/notebook/MainActivity.javapackage com.example.notebook; import android.os.Bundle; import android.util.Log; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; public class MainActivity extends AppCompatActivity { private static final String TAG MainActivity; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tvWelcome findViewById(R.id.tv_welcome); String username getIntent().getStringExtra(username); if (username ! null) { tvWelcome.setText(欢迎使用迷你记事本 username); } Log.d(TAG, onCreate 执行); } Override protected void onStart() { super.onStart(); Log.d(TAG, onStart 执行); } Override protected void onResume() { super.onResume(); Log.d(TAG, onResume 执行); } Override protected void onPause() { super.onPause(); Log.d(TAG, onPause 执行); } Override protected void onStop() { super.onStop(); Log.d(TAG, onStop 执行); } Override protected void onDestroy() { super.onDestroy(); Log.d(TAG, onDestroy 执行); } }这段代码的重点是生命周期方法。每个方法里都加入了 Log方便在 Logcat 面板观察 Activity 从创建到销毁的过程。如果你不理解生命周期请务必亲手跑一次然后在模拟器上点击返回键再重新进入页面对比日志输出顺序。4.4 在登录页中实现跳转回到 LoginActivity在登录校验通过的地方加入 Intent 跳转if (admin.equals(username) 123456.equals(password)) { Toast.makeText(LoginActivity.this, 登录成功, Toast.LENGTH_SHORT).show(); Intent intent new Intent(LoginActivity.this, MainActivity.class); intent.putExtra(username, username); startActivity(intent); finish(); }注意两点putExtra用来携带登录用户名主页面通过getIntent().getStringExtra(username)读取。finish()表示关闭当前 LoginActivity 页面。因为登录成功后用户不应该再通过返回键回到登录页。同时在 LoginActivity 顶部补上 importimport android.content.Intent;任务 2 完成后你理解了 Activity 的“创建-启动-显示-暂停-停止-销毁”完整过程也掌握了 Intent 的显式用法。这个知识点在 Android 开发里属于高频核心后续所有功能页面之间协作都依赖它。5. 任务 3用 RecyclerView 展示记事列表5.1 任务目标登录成功并进入主页面后页面上只有一行欢迎文字没有实际内容。任务 3 的核心目标是在 RecyclerView 中展示一组记事内容。这个任务会引入三大概念实体类Model、Adapter 适配器、ViewHolder 复用。这是 Android 列表开发的基础中的基础。5.2 创建记事实体类文件路径app/src/main/java/com/example/notebook/Note.javapackage com.example.notebook; public class Note { private int id; private String content; private String createTime; public Note() { } public Note(String content, String createTime) { this.content content; this.createTime createTime; } public int getId() { return id; } public void setId(int id) { this.id id; } public String getContent() { return content; } public void setContent(String content) { this.content content; } public String getCreateTime() { return createTime; } public void setCreateTime(String createTime) { this.createTime createTime; } }实体类的本质就是“一条数据的模型”。后面从数据库读取数据时每条查询结果都会封装成一个 Note 对象放入 List再交给 Adapter 显示。5.3 创建列表条目布局文件路径app/src/main/res/layout/item_note.xml?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 TextView android:idid/tv_content android:layout_widthmatch_parent android:layout_heightwrap_content android:textSize16sp android:text记事内容 / TextView android:idid/tv_time android:layout_widthmatch_parent android:layout_heightwrap_content android:layout_marginTop6dp android:textSize12sp android:textColor#999999 android:text创建时间 / /LinearLayout这个布局将来会作为列表中的每一行被反复创建。为了让视觉上区分不同条目可以在根布局中设置一个背景色比如android:background#F5F5F5。5.4 编写 Adapter文件路径app/src/main/java/com/example/notebook/NoteAdapter.javapackage com.example.notebook; 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 java.util.List; public class NoteAdapter extends RecyclerView.AdapterNoteAdapter.NoteViewHolder { public interface OnItemClickListener { void onItemClick(Note note); void onItemLongClick(Note note); } private final ListNote noteList; private OnItemClickListener listener; public NoteAdapter(ListNote noteList) { this.noteList noteList; } public void setOnItemClickListener(OnItemClickListener listener) { this.listener listener; } NonNull Override public NoteViewHolder onCreateViewHolder(NonNull ViewGroup parent, int viewType) { View view LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_note, parent, false); return new NoteViewHolder(view); } Override public void onBindViewHolder(NonNull NoteViewHolder holder, int position) { Note note noteList.get(position); holder.tvContent.setText(note.getContent()); holder.tvTime.setText(note.getCreateTime()); holder.itemView.setOnClickListener(v - { if (listener ! null) { listener.onItemClick(note); } }); holder.itemView.setOnLongClickListener(v - { if (listener ! null) { listener.onItemLongClick(note); } return true; }); } Override public int getItemCount() { return noteList.size(); } static class NoteViewHolder extends RecyclerView.ViewHolder { TextView tvContent; TextView tvTime; NoteViewHolder(NonNull View itemView) { super(itemView); tvContent itemView.findViewById(R.id.tv_content); tvTime itemView.findViewById(R.id.tv_time); } } }Adapter 在这里起“桥梁”作用它把一组 Note 数据逐一绑定到 item_note 布局上。onCreateViewHolder 负责创建每一行的视图容器onBindViewHolder 负责把数据填充到控件上。ViewHolder 的作用是缓存控件实例避免滑动列表时反复查找控件这是 RecyclerView 能流畅滑动的关键。5.5 在 MainActivity 中组装列表先把假数据写入 MainActivitypackage com.example.notebook; import android.os.Bundle; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private RecyclerView rvNotes; private NoteAdapter noteAdapter; private ListNote noteList new ArrayList(); Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tvWelcome findViewById(R.id.tv_welcome); String username getIntent().getStringExtra(username); if (username ! null) { tvWelcome.setText(欢迎使用迷你记事本 username); } rvNotes findViewById(R.id.rv_notes); // 任务 3先用模拟数据任务 4 会替换为数据库数据 noteList.add(new Note(9 点开会讨论项目排期, 2025-01-10 08:30)); noteList.add(new Note(下午完成接口联调, 2025-01-10 10:20)); noteList.add(new Note(记得给老师发作业, 2025-01-10 14:05)); noteAdapter new NoteAdapter(noteList); rvNotes.setLayoutManager(new LinearLayoutManager(this)); rvNotes.setAdapter(noteAdapter); } }运行项目登录进入主页后就能看到三条模拟记事。如果看不到列表最常见的原因就是忘写rvNotes.setLayoutManager(...)。RecyclerView 没有 LayoutManager 时不会报错但也不会显示任何内容——这个问题在任务驱动教学中很有价值因为它逼着学生去查“为什么列表不显示”。6. 任务 4用 SQLite 保存记事数据6.1 任务目标任务 3 的列表数据是写死在代码里的重启 App 后数据就没了。任务 4 的核心任务是把记事保存到本地 SQLite 数据库启动时从数据库读取历史数据。这是 Android 基础教学中最重要的数据存储任务之一。6.2 编写数据库帮助类文件路径app/src/main/java/com/example/notebook/DBHelper.javapackage com.example.notebook; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import androidx.annotation.Nullable; public class DBHelper extends SQLiteOpenHelper { private static final String DB_NAME notebook.db; private static final int DB_VERSION 1; public DBHelper(Nullable Context context) { super(context, DB_NAME, null, DB_VERSION); } Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE TABLE IF NOT EXISTS note ( id INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT NOT NULL, create_time TEXT NOT NULL)); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL(DROP TABLE IF EXISTS note); onCreate(db); } }SQLiteOpenHelper 是 Android 内置的数据库帮助类。onCreate 在数据库第一次创建时执行负责建表onUpgrade 在版本号增大时执行教学案例中直接删表重建但生产项目必须采用合理的迁移方案否则用户数据会全部丢失。6.3 编写数据访问层文件路径app/src/main/java/com/example/notebook/NoteDao.javapackage com.example.notebook; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import java.util.ArrayList; import java.util.List; public class NoteDao { private final DBHelper dbHelper; public NoteDao(Context context) { dbHelper new DBHelper(context); } public long insert(Note note) { SQLiteDatabase db dbHelper.getWritableDatabase(); ContentValues values new ContentValues(); values.put(content, note.getContent()); values.put(create_time, note.getCreateTime()); long rowId db.insert(note, null, values); db.close(); return rowId; } public ListNote queryAll() { ListNote list new ArrayList(); SQLiteDatabase db dbHelper.getReadableDatabase(); Cursor cursor db.query(note, null, null, null, null, null, id DESC); while (cursor.moveToNext()) { Note note new Note(); note.setId(cursor.getInt(cursor.getColumnIndexOrThrow(id))); note.setContent(cursor.getString(cursor.getColumnIndexOrThrow(content))); note.setCreateTime(cursor.getString(cursor.getColumnIndexOrThrow(create_time))); list.add(note); } cursor.close(); db.close(); return list; } public int delete(int id) { SQLiteDatabase db dbHelper.getWritableDatabase(); int rows db.delete(note, id ?, new String[]{String.valueOf(id)}); db.close(); return rows; } public int update(Note note) { SQLiteDatabase db dbHelper.getWritableDatabase(); ContentValues values new ContentValues(); values.put(content, note.getContent()); values.put(create_time, note.getCreateTime()); int rows db.update(note, values, id ?, new String[]{String.valueOf(note.getId())}); db.close(); return rows; } }这里用了标准的 SQLite 增删改查接口。值得提醒的是db.insert、db.delete、db.update都带有参数化查询能力推荐使用?占位符传参不要拼接 SQL 字符串避免 SQL 注入风险。基础教学中如果养成了拼接 SQL 的习惯到了后端开发或生产环境会非常危险。6.4 修改 MainActivity 接入数据库任务 4 中MainActivity 要做两处改动把写死的模拟数据替换成数据库查询结果。在界面中加入“新增记事”按钮点击后弹窗输入内容并写入数据库。先修改布局把按钮加到顶部文件路径app/src/main/res/layout/activity_main.xmlButton android:idid/btn_add android:layout_widthmatch_parent android:layout_heightwrap_content android:text新增记事 /注意按钮要放在 TextView 下方。然后修改 MainActivitypackage com.example.notebook; import android.app.AlertDialog; import android.os.Bundle; import android.text.TextUtils; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Locale; public class MainActivity extends AppCompatActivity { private RecyclerView rvNotes; private NoteAdapter noteAdapter; private final ListNote noteList new ArrayList(); private NoteDao noteDao; private Button btnAdd; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tvWelcome findViewById(R.id.tv_welcome); String username getIntent().getStringExtra(username); if (username ! null) { tvWelcome.setText(欢迎使用迷你记事本 username); } rvNotes findViewById(R.id.rv_notes); btnAdd findViewById(R.id.btn_add); noteDao new NoteDao(this); // 第一次进入时加载数据库中的全部数据 noteList.addAll(noteDao.queryAll()); noteAdapter new NoteAdapter(noteList); rvNotes.setLayoutManager(new LinearLayoutManager(this)); rvNotes.setAdapter(noteAdapter); btnAdd.setOnClickListener(v - showAddDialog()); } private void showAddDialog() { EditText editText new EditText(this); editText.setHint(请输入记事内容); new AlertDialog.Builder(this) .setTitle(新增记事) .setView(editText) .setPositiveButton(保存, (dialog, which) - { String content editText.getText().toString().trim(); if (TextUtils.isEmpty(content)) { Toast.makeText(this, 内容不能为空, Toast.LENGTH_SHORT).show(); return; } String time new SimpleDateFormat(yyyy-MM-dd HH:mm, Locale.getDefault()) .format(new Date()); Note note new Note(content, time); long rowId noteDao.insert(note); if (rowId ! -1) { note.setId((int) rowId); noteList.add(0, note); noteAdapter.notifyItemInserted(0); rvNotes.scrollToPosition(0); Toast.makeText(this, 保存成功, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, 保存失败, Toast.LENGTH_SHORT).show(); } }) .setNegativeButton(取消, null) .show(); } }到这里任务 4 就形成了一个完整闭环进入页面读取数据库 → 显示记事列表 → 点击新增按钮 → 弹窗输入内容 → 写入数据库 → 列表立即刷新。这里有一个细节值得在教学中强调新增成功后不应该重新查一次数据库而是直接把新 Note 插入 List 的头部再调用notifyItemInserted(0)局部刷新。这样既避免重复查询也避免整个列表闪烁。7. 任务 5编辑与删除记事7.1 任务目标任务 4 完成了数据的写入和读取但用户还无法修改或删除已有的记事。任务 5 的最终目标是单击某条记事弹出编辑对话框长按某条记事弹出删除确认框。这是整个迷你记事本项目的收尾任务完成后App 就是完整的增删改查CRUD应用。7.2 在 MainActivity 中注册点击事件在 MainActivity 的 onCreate 中给 Adapter 设置监听器noteAdapter.setOnItemClickListener(new NoteAdapter.OnItemClickListener() { Override public void onItemClick(Note note) { showEditDialog(note); } Override public void onItemLongClick(Note note) { showDeleteDialog(note); } });7.3 实现编辑对话框private void showEditDialog(Note note) { EditText editText new EditText(this); editText.setText(note.getContent()); new AlertDialog.Builder(this) .setTitle(编辑记事) .setView(editText) .setPositiveButton(更新, (dialog, which) - { String content editText.getText().toString().trim(); if (TextUtils.isEmpty(content)) { Toast.makeText(this, 内容不能为空, Toast.LENGTH_SHORT).show(); return; } note.setContent(content); note.setCreateTime(new SimpleDateFormat(yyyy-MM-dd HH:mm, Locale.getDefault()) .format(new Date())); int rows noteDao.update(note); if (rows 0) { int position noteList.indexOf(note); noteAdapter.notifyItemChanged(position); Toast.makeText(this, 更新成功, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, 更新失败, Toast.LENGTH_SHORT).show(); } }) .setNegativeButton(取消, null) .show(); }编辑对话框复用了新增对话框的思路区别在于编辑时要把原内容回显到 EditText 中更新后调用notifyItemChanged(position)刷新当前条目。这里要注意noteList.indexOf(note)依赖 Note 对象重写的 equals 方法。由于我们并没有重写 equals所以 indexOf 实际使用的是对象引用比较。在任务驱动式教学中可以借此引出一个知识延伸——实际项目中更稳妥的方式是直接用note.getId()遍历查找位置。7.4 实现删除对话框private void showDeleteDialog(Note note) { new AlertDialog.Builder(this) .setTitle(删除确认) .setMessage(确定要删除这条记事吗) .setPositiveButton(删除, (dialog, which) - { int rows noteDao.delete(note.getId()); if (rows 0) { int position noteList.indexOf(note); noteList.remove(position); noteAdapter.notifyItemRemoved(position); Toast.makeText(this, 删除成功, Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, 删除失败, Toast.LENGTH_SHORT).show(); } }) .setNegativeButton(取消, null) .show(); }删除是很敏感的操作尤其在生产环境中任何删除动作都必须有二次确认。这里用 AlertDialog 弹出确认框就是最典型的做法。7.5 完整运行验证运行 App完整流程如下打开登录页输入 admin / 123456 登录。主页面展示数据库里的历史记事。点击“新增记事”输入内容保存成功后列表顶部立即出现新数据。单击任意记事弹窗编辑内容确认后列表对应条目更新。长按任意记事弹窗确认删除列表移除该条目。杀掉 App 重新打开数据仍然存在因为已经持久化到 SQLite。任务 5 完成后整个迷你记事本从“界面展示”到“本地数据持久化”已经全部跑通。你已经拥有了一个具备增删改查能力的 Android 小程序。8. 常见问题与排查思路在任务驱动式教学过程中学生最容易在以下几个节点卡住。整理成排查清单方便对照问题现象常见原因解决思路登录按钮点击无效布局中 Button 被其他控件遮挡或监听器未正常绑定检查 id 是否一致检查findViewById是否返回 null应用启动时弹出选择界面Manifest 中多个 Activity 声明了 LAUNCHER 入口只保留一个入口 Activity 的 intent-filterRecyclerView 列表不显示忘记设置 LayoutManager调用rvNotes.setLayoutManager(new LinearLayoutManager(this))列表数据更新后界面不变化修改 List 后未调用 Adapter 刷新方法使用notifyItemInserted或notifyDataSetChanged数据库查询返回空数据库文件未创建成功或表名/字段名不匹配查看日志确认建表 SQL核对 Cursor 字段名删除后列表位置错乱直接按 index 删除而未同步 List 与 Adapter先删除 List 数据再notifyItemRemoved(position)MainActivity 报空指针登录跳转时未传 username 参数检查intent.putExtra与getStringExtra的 key 是否一致数据库升级后用户数据丢失onUpgrade 中直接 DROP TABLE生产项目中应做表结构迁移而不是删表重建如果出现其它问题最有效的排查方式是看 Logcat。Android 应用崩溃时的异常堆栈通常会直接指出错在哪一行代码沿着堆栈向上查绝大多数问题都能定位。9. 最佳实践与工程建议9.1 包结构合理划分随着任务推进文件会越来越多。建议在com.example.notebook下建立分包activity存放 LoginActivity、MainActivity adapter存放 NoteAdapter dao存放 NoteDao、DBHelper model存放 Note任务驱动式教学中很多学生习惯把所有类都写在同一个包下短期内没问题但项目一旦超过 10 个文件维护成本就会剧增。从第一个项目开始就按职责分包是成本最低的工程化习惯。9.2 登录逻辑的安全边界本文登录功能使用本地硬编码校验只能用于教学演示。真实项目中要注意密码不允许明文保存应加密存储最好由服务端统一管理。登录状态应使用 Token 机制而不是反复传输账号密码。客户端只负责展示和交互权限校验必须放在服务端。App 中不要保存敏感信息到 SharedPreferences 明文区域。如果你只学 Android 基础可以先不深入 Token但必须建立“客户端校验不安全”的意识。9.3 SQLite 与 Room 的选型本文使用 SQLiteOpenHelper 是出于教学目的它逻辑透明、容易理解底层。但从工程实践看Android 官方更推荐使用 RoomRoom 在 SQLite 之上封装了编译期 SQL 检查和协程支持能显著减少样板代码。建议学习路径是先把 SQLite 搞明白再上手 Room这样即使 Room 出了奇怪问题你也知道底层发生了什么。9.4 数据库操作线程问题本文的数据库操作都在主线程完成对于记事本这种小数据量场景没有问题。但真实项目中的数据库查询、网络请求都不能在主线程执行否则会触发NetworkOnMainThreadException或导致界面卡顿。基础阶段建议先掌握 Handler、AsyncTask 的历史演进再了解协程与线程池。9.5 日志规范生命周期观察里用了Log.d。实战项目中日志要分级使用Log.v/Log.d开发调试细节。Log.i关键业务节点。Log.w/Log.e警告和错误。应用发布前建议把 Debug 日志全部关闭避免泄漏内部实现细节。10. 总结与下一步学习建议通过这 5 个任务你已经完整走通了 Android 开发中最核心的一条主线从 XML 布局搭建界面到 Java 代码绑定控件与事件再到 Intent 页面跳转、RecyclerView 数据列表展示最后用 SQLite 完成数据持久化和增删改查。这条路线正是童长飞老师任务驱动式教学体系中反复强调的“最小可运行闭环”比按目录从第一章背到最后一章要有效得多。整个迷你记事本应用虽然简单却覆盖了一款真实 App 的基础骨架登录入口、主页容器、数据模型、数据适配层、本地存储层、交互反馈。如果你能不看笔记独立把这个项目从头到尾再写一遍而不是复制代码说明你已经真正掌握了这些基础知识点。下一步可以继续挑战这些方向使用 RecyclerView 多类型条目让列表支持不同的布局样式。接入 Room 数据库替代手写 SQLiteOpenHelper。使用 Fragment 重构主页了解单 Activity 多 Fragment 的架构。学习 ViewModel 和 LiveData让界面在屏幕旋转时不丢失数据。增加“搜索记事”功能练习模糊查询和列表过滤。如果本文对你有帮助可以收藏备用后续进阶案例会继续围绕任务驱动式思路展开。动手把代码敲一遍遇到报错就对照上面的排查清单祝你在 Android 开发的学习路上少走弯路。