Android通讯录开发实战:SQLite+ListView完整实现(附源码)

张开发
2026/5/1 17:57:07 15 分钟阅读

分享文章

Android通讯录开发实战:SQLite+ListView完整实现(附源码)
Android通讯录开发实战从SQLite到ListView的完整实现在移动应用开发领域数据存储与展示是最基础也最核心的功能之一。对于Android开发者而言掌握SQLite数据库操作与ListView控件的使用就像厨师掌握刀工一样重要。本文将带你从零开始构建一个功能完整的通讯录应用不仅涵盖数据库的增删改查操作还会深入探讨如何优雅地在界面上展示这些数据。1. 项目架构设计与环境准备一个典型的Android通讯录应用通常包含以下几个核心组件数据层负责联系人信息的持久化存储使用SQLite数据库业务逻辑层处理用户交互和数据处理逻辑展示层通过ListView展示联系人列表1.1 开发环境配置确保你的开发环境满足以下要求// build.gradle (Module) 最低配置 android { compileSdkVersion 30 defaultConfig { minSdkVersion 21 targetSdkVersion 30 } }提示建议使用Android Studio 4.0以上版本以获得更好的开发体验1.2 项目文件结构我们的通讯录项目将包含以下关键文件├── app/ │ ├── src/main/ │ │ ├── java/com/example/contacts/ │ │ │ ├── ContactContract.java # 数据库契约类 │ │ │ ├── DatabaseHelper.java # 数据库帮助类 │ │ │ ├── ContactAdapter.java # 自定义适配器 │ │ │ └── MainActivity.java # 主界面逻辑 │ │ └── res/ │ │ ├── layout/ │ │ │ ├── activity_main.xml # 主界面布局 │ │ │ └── contact_item.xml # 列表项布局 │ │ └── drawable/ # 图标资源2. SQLite数据库实现2.1 数据库表设计通讯录应用的核心是联系人信息的存储。我们设计一个简单的contacts表字段名类型说明_idINTEGER主键自增长nameTEXT联系人姓名phoneTEXT联系电话emailTEXT电子邮箱(可选)对应的SQL创建语句// DatabaseHelper.java Override public void onCreate(SQLiteDatabase db) { final String SQL_CREATE_CONTACTS_TABLE CREATE TABLE ContactContract.ContactEntry.TABLE_NAME ( ContactContract.ContactEntry._ID INTEGER PRIMARY KEY AUTOINCREMENT, ContactContract.ContactEntry.COLUMN_NAME TEXT NOT NULL, ContactContract.ContactEntry.COLUMN_PHONE TEXT NOT NULL, ContactContract.ContactEntry.COLUMN_EMAIL TEXT);; db.execSQL(SQL_CREATE_CONTACTS_TABLE); }2.2 数据库操作封装良好的数据库操作应该封装在独立的类中。以下是增删改查的典型实现public class ContactDbHelper { // 插入联系人 public long insertContact(String name, String phone, String email) { SQLiteDatabase db this.getWritableDatabase(); ContentValues values new ContentValues(); values.put(COLUMN_NAME, name); values.put(COLUMN_PHONE, phone); values.put(COLUMN_EMAIL, email); long newRowId db.insert(TABLE_NAME, null, values); db.close(); return newRowId; } // 查询所有联系人 public Cursor getAllContacts() { SQLiteDatabase db this.getReadableDatabase(); return db.query( TABLE_NAME, null, // 所有列 null, // WHERE条件 null, // WHERE参数 null, // GROUP BY null, // HAVING COLUMN_NAME ASC // 排序 ); } }注意数据库操作完成后务必调用close()释放资源避免内存泄漏3. ListView与数据绑定3.1 自定义Adapter实现为了更灵活地控制列表项的展示我们创建自定义Adapterpublic class ContactAdapter extends CursorAdapter { public ContactAdapter(Context context, Cursor c) { super(context, c, 0); } Override public View newView(Context context, Cursor cursor, ViewGroup parent) { return LayoutInflater.from(context) .inflate(R.layout.contact_item, parent, false); } Override public void bindView(View view, Context context, Cursor cursor) { TextView nameView view.findViewById(R.id.contact_name); TextView phoneView view.findViewById(R.id.contact_phone); String name cursor.getString( cursor.getColumnIndex(ContactContract.ContactEntry.COLUMN_NAME)); String phone cursor.getString( cursor.getColumnIndex(ContactContract.ContactEntry.COLUMN_PHONE)); nameView.setText(name); phoneView.setText(phone); } }3.2 列表项点击事件处理为ListView添加点击事件实现联系人详情查看功能listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { Override public void onItemClick(AdapterView? parent, View view, int position, long id) { Cursor cursor (Cursor) parent.getItemAtPosition(position); String name cursor.getString(cursor.getColumnIndex(COLUMN_NAME)); String phone cursor.getString(cursor.getColumnIndex(COLUMN_PHONE)); // 显示联系人详情对话框 showContactDetailDialog(name, phone); } });4. 性能优化与最佳实践4.1 数据库事务优化批量操作时使用事务可以显著提升性能public void importContacts(ListContact contacts) { SQLiteDatabase db this.getWritableDatabase(); db.beginTransaction(); try { for (Contact contact : contacts) { ContentValues values new ContentValues(); values.put(COLUMN_NAME, contact.getName()); values.put(COLUMN_PHONE, contact.getPhone()); db.insert(TABLE_NAME, null, values); } db.setTransactionSuccessful(); } finally { db.endTransaction(); db.close(); } }4.2 ViewHolder模式优化列表性能在自定义Adapter中使用ViewHolder模式static class ViewHolder { TextView nameView; TextView phoneView; ImageView avatarView; } Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView null) { convertView LayoutInflater.from(context) .inflate(R.layout.contact_item, parent, false); holder new ViewHolder(); holder.nameView convertView.findViewById(R.id.contact_name); holder.phoneView convertView.findViewById(R.id.contact_phone); holder.avatarView convertView.findViewById(R.id.contact_avatar); convertView.setTag(holder); } else { holder (ViewHolder) convertView.getTag(); } // 绑定数据到ViewHolder Contact contact getItem(position); holder.nameView.setText(contact.getName()); holder.phoneView.setText(contact.getPhone()); return convertView; }4.3 异步加载数据避免在主线程执行耗时操作private class LoadContactsTask extends AsyncTaskVoid, Void, Cursor { Override protected Cursor doInBackground(Void... voids) { return dbHelper.getAllContacts(); } Override protected void onPostExecute(Cursor cursor) { adapter.swapCursor(cursor); } }5. 扩展功能实现5.1 搜索功能实现为通讯录添加实时搜索功能searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { Override public boolean onQueryTextSubmit(String query) { return false; } Override public boolean onQueryTextChange(String newText) { Cursor filteredCursor dbHelper.searchContacts(newText); adapter.swapCursor(filteredCursor); return true; } });对应的数据库搜索方法public Cursor searchContacts(String query) { SQLiteDatabase db this.getReadableDatabase(); return db.query( TABLE_NAME, null, COLUMN_NAME LIKE ? OR COLUMN_PHONE LIKE ?, new String[]{% query %, % query %}, null, null, COLUMN_NAME ASC ); }5.2 联系人图片处理存储和显示联系人头像// 保存图片到数据库 public long addContactWithImage(String name, String phone, Bitmap image) { SQLiteDatabase db this.getWritableDatabase(); ContentValues values new ContentValues(); // 将Bitmap转换为字节数组 ByteArrayOutputStream stream new ByteArrayOutputStream(); image.compress(Bitmap.CompressFormat.PNG, 100, stream); byte[] byteArray stream.toByteArray(); values.put(COLUMN_NAME, name); values.put(COLUMN_PHONE, phone); values.put(COLUMN_IMAGE, byteArray); return db.insert(TABLE_NAME, null, values); } // 从数据库读取图片 public Bitmap getContactImage(long id) { SQLiteDatabase db this.getReadableDatabase(); Cursor cursor db.query( TABLE_NAME, new String[]{COLUMN_IMAGE}, _id ?, new String[]{String.valueOf(id)}, null, null, null ); if (cursor ! null cursor.moveToFirst()) { byte[] imageBytes cursor.getBlob(0); return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length); } return null; }在实际项目中我发现使用数据库存储大量图片并不是最佳实践。更推荐的做法是将图片保存到文件系统数据库中只存储图片路径。这样可以避免数据库膨胀提高查询效率。

更多文章