ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android 联系人简易通讯录:SQLite 数据库 + Menu 菜单实战配置

Android 联系人简易通讯录:SQLite 数据库 + Menu 菜单实战配置 1. 从零搭一个能跑起来的 Android 通讯录很多刚学 Android 的朋友学完 Activity 和 ListView 之后最想做的就是「能存数据」的小项目而通讯录几乎是所有人的第一选择。它足够小小到一个下午就能跑通又足够完整完整到把 SQLite 建表、增删改查、Menu 菜单、对话框交互这些核心知识点全串了一遍。这篇就围绕 Android 联系人简易通讯录用 SQLite 数据库加 Menu 菜单把整套配置和代码给你铺开你照着敲就能在模拟器上看到联系人列表动起来。先说清楚这个项目能做什么一个列表页展示所有联系人右上角菜单点一下弹出「添加联系人」对话框长按某一条联系人弹出上下文菜单可以删除或修改。数据全部落在本地 SQLite 里关掉 App 再打开数据还在。适合谁适合已经会写 Activity、用过 ListView但还没真正把 SQLiteOpenHelper 用起来的人。热词里的 android、通讯录、数据库、SQLite、menu 这几个点下面会一个一个落地。我踩过的坑先提前说SimpleCursorAdapter 的构造参数里列名数组和控件 id 数组必须一一对应顺序错了不会报错但界面会显示错位或者空白这个后面排障部分会细讲。2. 前置准备TaoToken 与工程环境在动手写代码之前先把两件事准备好一个是工程环境一个是后面调试模型相关逻辑时可能用到的接口配置。工程环境这块Android Studio 新建一个 Empty Views Activity 项目即可语言选 Java最低 SDK 建议 24 以上避免老版本 SimpleCursorAdapter 的兼容问题。包名随意本文示例用com.example.song.lx_day13_contact。如果你在写代码过程中想让模型帮你解释某段 SQL 或者生成建表语句可以先把 TaoToken 的 API Key 配好。它的接入文档写得很清楚注册后在控制台创建 Key 就行。地址我放在这里方便你直接对照官网入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 基址https://taotoken.net/api控制台创建 Keyhttps://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI Keys 管理https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content注意Key 只用于你本地调试和模型对话不要硬编码进提交到公开仓库的代码里建议放在 local.properties 或环境变量中读取。环境准备好之后我们进入正题。整个项目其实就四个文件最关键DBHelper继承 SQLiteOpenHelper、MainActivity、菜单资源 XML、列表项布局。下面逐个给。3. 可复制配置建表、DBHelper 与 Menu 资源3.1 SQLiteOpenHelper 建表语句先写数据库帮助类。表名用 contact字段设计成_id自增主键、name、sex、number。注意主键列名用_id这是 SimpleCursorAdapter 能正常工作的硬性要求它默认就找这个名字。package com.example.song.lx_day13_contact; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; public class OpenHelper extends SQLiteOpenHelper { public static final String NAME contacts.db; public static final int VERSION 1; public OpenHelper(Context context) { super(context, NAME, null, VERSION); } Override public void onCreate(SQLiteDatabase db) { db.execSQL(create table if not exists contact( _id integer primary key autoincrement, name text, sex text, number text)); } Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (newVersion oldVersion) { db.execSQL(drop table if exists contact); onCreate(db); } } }这里onUpgrade用的是最粗暴的「删表重建」适合入门阶段。真实项目里这样会丢数据后面第 5 节会讲更稳妥的版本迁移写法。3.2 菜单资源 XML 配置菜单分两种右上角的选项菜单OptionsMenu和长按列表项弹出的上下文菜单ContextMenu。在res/menu目录下建两个文件。选项菜单main.xml只放一个「添加」?xml version1.0 encodingutf-8? menu xmlns:androidhttp://schemas.android.com/apk/res/android item android:idid/action_add android:title添加联系人 android:showAsActionalways / /menu上下文菜单contextmenu_listview_main.xml放删除和修改?xml version1.0 encodingutf-8? menu xmlns:androidhttp://schemas.android.com/apk/res/android item android:idid/action_delete android:title删除 / item android:idid/action_update android:title修改 / /menu3.3 列表项与对话框布局列表项list_item.xml三个 TextView 横排?xml version1.0 encodingutf-8? LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightwrap_content android:orientationhorizontal android:padding12dp TextView android:idid/tv_name android:layout_width0dp android:layout_heightwrap_content android:layout_weight1 / TextView android:idid/tv_sex android:layout_width0dp android:layout_heightwrap_content android:layout_weight1 / TextView android:idid/tv_phoneNumber android:layout_width0dp android:layout_heightwrap_content android:layout_weight2 / /LinearLayout对话框dialog_view.xml三个 EditText?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 EditText android:idid/et_name android:layout_widthmatch_parent android:layout_heightwrap_content android:hint姓名 / EditText android:idid/et_sex android:layout_widthmatch_parent android:layout_heightwrap_content android:hint性别 / EditText android:idid/et_phone android:layout_widthmatch_parent android:layout_heightwrap_content android:hint电话 / /LinearLayout3.4 MainActivity 核心逻辑MainActivity 里把数据库初始化、列表赋值、菜单回调串起来。关键点registerForContextMenu(listView)注册长按菜单listView.setEmptyView(empty)设置空数据提示。private void initDataBase() { OpenHelper helper new OpenHelper(this); db helper.getReadableDatabase(); } private void initData() { cursor db.rawQuery(select * from contact, null); SimpleCursorAdapter adapter new SimpleCursorAdapter( this, R.layout.list_item, cursor, new String[]{name, sex, number}, new int[]{R.id.tv_name, R.id.tv_sex, R.id.tv_phoneNumber}, CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER); listView.setAdapter(adapter); }添加联系人的逻辑放在onOptionsItemSelected里用 AlertDialog 加载dialog_view确定时把三个 EditText 的值塞进 ContentValues再db.insert(contact, null, values)插入成功就重新查一次刷新列表。删除和修改放在onContextItemSelected里。删除直接db.execSQL(delete from contact where _id ?, new Object[]{id})修改先按 id 查出当前值回填到对话框点确定时用db.update(contact, contentValues, _id?, new String[]{id })。4. 验证请求模拟器上跑通增删改查代码写完点运行在模拟器上按下面步骤验证每一步都要看到预期结果再往下走。第一步启动 App。列表是空的应该显示空视图提示文字。如果列表区域一片白、连提示都没有说明setEmptyView没生效或者 empty 控件没找到。第二步点右上角「添加联系人」。弹出对话框输入「张三 / 男 / 13800000000」点确定。预期Toast 提示「添加成功」列表立刻出现这一条。如果列表没刷新检查initData()有没有在插入成功后调用。第三步再添加两条比如「李四 / 女 / 13900000001」「王五 / 男 / 13700000002」。预期列表按插入顺序显示三条。第四步长按「李四」这一条。预期弹出上下文菜单有「删除」「修改」两项。点「修改」对话框里应该已经回填了李四的当前信息把电话改成 13900000009点修改。预期列表里李四的电话变了。第五步长按「王五」点「删除」。预期这一条从列表消失剩下两条。第六步彻底关掉 App从最近任务划掉重新打开。预期剩下的两条联系人还在。这一步是验证数据真的落到了 SQLite而不是只存在内存里。如果你在验证过程中想确认数据库文件到底建没建、表结构对不对可以用 Android Studio 的 Device File Explorer路径在/data/data/你的包名/databases/contacts.db导出后用 SQLite 工具打开看。这个习惯能帮你快速定位「数据没存进去」还是「存进去了没查出来」。5. 本篇常见错排查5.1 列表显示空白或错位最常见的原因是 SimpleCursorAdapter 的列名数组和控件 id 数组顺序不一致。比如列名写的是{name,sex,number}控件 id 却写成{tv_sex, tv_name, tv_phoneNumber}编译不报错但显示全乱。对照检查这两个数组一一对应。另一个原因是查询结果里没有_id列。SimpleCursorAdapter 强制要求 Cursor 包含_id所以建表时主键必须叫_id查询时也别用select name, sex, number把主键漏掉。5.2 长按没有弹出上下文菜单检查两点一是registerForContextMenu(listView)有没有调用二是onCreateContextMenu里有没有getMenuInflater().inflate(...)。少任何一个长按都不会有反应。另外如果 ListView 的 item 里有可点击控件比如 Button会抢走长按事件入门阶段列表项尽量只用 TextView。5.3 数据库版本升级后崩溃onUpgrade里如果只写了 drop 和 onCreate升级时数据会全丢但不会崩。真正会崩的情况是你改了表结构却忘了把 VERSION 加一导致 onCreate 不会重新执行新字段不存在插入时报no such column。记住只要改了建表语句VERSION 必须加一。更稳妥的迁移写法是按版本号逐步 ALTEROverride public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion 2) { db.execSQL(alter table contact add column email text); } }这样老用户升级时数据保留只是多了一列。5.4 插入成功但列表不刷新db.insert返回 -1 表示失败返回行号表示成功。如果成功但列表没变多半是没重新执行查询。SimpleCursorAdapter 配合FLAG_REGISTER_CONTENT_OBSERVER在部分场景能自动感知变化但最稳的做法还是插入/更新/删除后手动调一次initData()重新查。别偷懒省这一步。6. 继续往下走把调试和模型接入串起来到这里一个能增删改查、带菜单交互的 Android 通讯录就跑通了。你会发现真正卡人的从来不是建表语句本身而是 Cursor 和 Adapter 之间的对应关系、菜单事件的注册时机、数据库版本号这些细节。如果你在排障时想让模型帮你分析某段报错日志或者想让它根据你的表结构生成对应的增删改查方法可以走 API Keys 加接入文档这条线把 Key 配好之后直接调API Keys 管理https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content如果你只是想先验证某个模型对 SQL 语句的理解对不对用模型对话页面直接问就行不用写代码模型对话https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content要是你打算把这个通讯录继续扩展成带搜索、分组、批量导入的完整项目或者想让它配合 Agent 做长期的编码辅助那更适合用 Coding Plan把上下文和任务连续性管起来Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content最后留一个实用建议把onUpgrade从「删表重建」改成按版本 ALTER 之后再给 contact 表加一个created_at字段记录创建时间列表按时间倒序排。这个小改动能让你真正理解数据库版本管理和查询排序比单纯抄一遍增删改查有价值得多。
返回列表