手把手教你Android编程:按钮点击界面跳转数据库存数据完整实例解析新手从零上手实战案例
嘿,小伙伴!欢迎来到Android开发的世界。第一次打开Android Studio是不是感觉像在看天书?别慌,今天咱们就用一个最接地气的小项目,带你把按钮点击、界面跳转、数据库存储这三件事从头到尾捋明白。
我当年学Android的时候,也是对着满屏的XML和Java代码发懵,后来发现只要有一个完整能跑的小案例,后面的路就顺了。今天就用这个”通讯录小助手”项目,带你亲手把一套流程跑通。
先建项目,别急着写代码
打开Android Studio,新建一个项目。选”Empty Views Activity”,语言选Java(Kotlin后面可以学,Java入门更直观),最低API选19就行,够用。项目起名叫”ContactHelper”。
建好之后,你会看到项目结构里有两个主要文件:activity_main.xml 和 MainActivity.java。先别看别的,咱们一步一步来。
第一个界面:让用户输入名字和电话
双击打开 res/layout/activity_main.xml,把里面的内容清空,换成下面这个布局。别怕,我会解释每一行的作用。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="24dp"
android:background="#F5F5F5">
<!-- 标题 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="📋 联系人小助手"
android:textSize="24sp"
android:textStyle="bold"
android:textColor="#333333"
android:gravity="center"
android:layout_marginBottom="32dp"/>
<!-- 姓名输入框 -->
<EditText
android:id="@+id/etName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入姓名"
android:inputType="text"
android:padding="14dp"
android:background="#FFFFFF"
android:elevation="2dp"
android:layout_marginBottom="16dp"/>
<!-- 电话输入框 -->
<EditText
android:id="@+id/etPhone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入电话号码"
android:inputType="phone"
android:padding="14dp"
android:background="#FFFFFF"
android:elevation="2dp"
android:layout_marginBottom="24dp"/>
<!-- 保存按钮 -->
<Button
android:id="@+id/btnSave"
android:layout_width="match_parent"
android:layout_height="52dp"
android:text="💾 保存到数据库"
android:textSize="16sp"
android:textColor="#FFFFFF"
android:background="#4CAF50"
android:layout_marginBottom="12dp"/>
<!-- 查看联系人按钮 -->
<Button
android:id="@+id/btnView"
android:layout_width="match_parent"
android:layout_height="52dp"
android:text="👀 查看已存联系人"
android:textSize="16sp"
android:textColor="#FFFFFF"
android:background="#2196F3"/>
</LinearLayout>
布局写完后,运行一下模拟器看看效果。两个输入框、两个按钮,整整齐齐。这个界面就是你的”工作台”。
建数据库帮助类:SQLite的”翻译官”
Android内置了SQLite数据库,不用额外装任何东西。咱们写一个 DbHelper 类来管理它。在 java 目录下右键 → New → Java Class,名字叫 DbHelper。
package com.example.contacthelper;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.util.ArrayList;
import java.util.List;
// 继承SQLiteOpenHelper,这是Android官方推荐的数据库管理方式
public class DbHelper extends SQLiteOpenHelper {
// 数据库名和版本,后期升级时改version就行
private static final String DATABASE_NAME = "contacts.db";
private static final int DATABASE_VERSION = 1;
// 表名和字段名,用常量管理避免写错
private static final String TABLE_CONTACTS = "contacts";
private static final String COLUMN_ID = "id";
private static final String COLUMN_NAME = "name";
private static final String COLUMN_PHONE = "phone";
// 建表SQL语句,注意id是自增主键
private static final String CREATE_TABLE =
"CREATE TABLE " + TABLE_CONTACTS + " (" +
COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
COLUMN_NAME + " TEXT NOT NULL, " +
COLUMN_PHONE + " TEXT NOT NULL" +
")";
// 构造方法,传入Context,数据库第一次打开时自动创建
public DbHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
// 第一次创建数据库时调用,执行建表语句
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
}
// 数据库版本升级时调用,暂时先用不上
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_CONTACTS);
onCreate(db);
}
// ===== 核心操作:存数据 =====
// 把姓名和电话封存在ContentValues里,像打包快递一样
public long saveContact(String name, String phone) {
SQLiteDatabase db = this.getWritableDatabase(); // 获取可写的数据库
ContentValues values = new ContentValues(); // 创建"打包盒"
values.put(COLUMN_NAME, name); // 放进姓名
values.put(COLUMN_PHONE, phone); // 放进电话
// insert方法返回新行的id,返回-1表示失败
long result = db.insert(TABLE_CONTACTS, null, values);
db.close(); // 记得关闭,省资源
return result;
}
// ===== 核心操作:查数据 =====
// 查全部联系人,返回一个字符串列表,每条格式是"张三 13800138000"
public List<String> getAllContacts() {
List<String> contactList = new ArrayList<>();
SQLiteDatabase db = this.getReadableDatabase();
// 查询整张表,按id倒序,最新的在前面
Cursor cursor = db.query(
TABLE_CONTACTS,
new String[]{COLUMN_ID, COLUMN_NAME, COLUMN_PHONE},
null, null, null, null,
COLUMN_ID + " DESC"
);
// 遍历结果集,把每条记录拼成字符串
if (cursor != null) {
while (cursor.moveToNext()) {
int id = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_ID));
String name = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME));
String phone = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_PHONE));
contactList.add(id + " | " + name + " | " + phone);
}
cursor.close(); // 用完关闭
}
db.close();
return contactList;
}
}
这个类是整件事的”后台仓库”。getWritableDatabase() 和 getReadableDatabase() 的区别很简单:写入时要用可写的,查询时用可读的就行。ContentValues 是Android专门用来封装键值对的类,比直接拼SQL字符串安全得多,能防止SQL注入。
第二个界面:显示存好的联系人
回到布局,右键 res/layout → New → Layout resource file,起名叫 activity_contact_list。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
android:background="#FAFAFA">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="📇 已保存的联系人"
android:textSize="20sp"
android:textStyle="bold"
android:textColor="#333333"
android:layout_marginBottom="16dp"/>
<!-- 用ListView展示列表,简单直观 -->
<ListView
android:id="@+id/lvContacts"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:divider="#E0E0E0"
android:dividerHeight="1dp"/>
<!-- 返回按钮 -->
<Button
android:id="@+id/btnBack"
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="⬅️ 返回首页"
android:textSize="15sp"
android:background="#757575"
android:textColor="#FFFFFF"
android:layout_marginTop="12dp"/>
</LinearLayout>
再新建一个Activity,叫 ContactListActivity。在 java 目录下右键 → New → Activity → Empty Activity,确保勾选”Views”而不是”Compose”,语言选Java。
package com.example.contacthelper;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.util.List;
public class ContactListActivity extends AppCompatActivity {
private DbHelper dbHelper;
private ListView lvContacts;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contact_list);
// 初始化数据库帮助类
dbHelper = new DbHelper(this);
lvContacts = findViewById(R.id.lvContacts);
// 从数据库取出数据,用系统自带的 ArrayAdapter 显示
List<String> contacts = dbHelper.getAllContacts();
if (contacts.isEmpty()) {
// 数据库为空时给个友好提示
lvContacts.setAdapter(null);
Toast.makeText(this, "还没保存任何联系人哦~", Toast.LENGTH_SHORT).show();
} else {
// ArrayAdapter第三个参数是系统自带的简单文本布局
android.widget.ArrayAdapter<String> adapter =
new android.widget.ArrayAdapter<>(this, android.R.layout.simple_list_item_1, contacts);
lvContacts.setAdapter(adapter);
}
// 返回按钮:跳转回首页
Button btnBack = findViewById(R.id.btnBack);
btnBack.setOnClickListener(v -> {
startActivity(new Intent(this, MainActivity.class));
finish(); // 结束当前界面,压入栈底的 MainActivity 显现
});
}
}
最重要的部分:在MainActivity里把三件事串起来
现在回到 MainActivity.java,把这段完整的代码贴进去。每个关键位置我加了注释,仔细读。
package com.example.contacthelper;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private EditText etName;
private EditText etPhone;
private Button btnSave;
private Button btnView;
private DbHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 初始化数据库帮助类,此时数据库文件还不存在,第一次调用saveContact时会创建
dbHelper = new DbHelper(this);
// 找控件,findViewById是连接XML和Java的桥梁
etName = findViewById(R.id.etName);
etPhone = findViewById(R.id.etPhone);
btnSave = findViewById(R.id.btnSave);
btnView = findViewById(R.id.btnView);
// ===== 按钮1:保存数据到数据库 =====
btnSave.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String name = etName.getText().toString().trim();
String phone = etPhone.getText().toString().trim();
// 输入校验,这是好习惯,别让用户存空数据
if (name.isEmpty()) {
Toast.makeText(MainActivity.this, "姓名不能为空哦", Toast.LENGTH_SHORT).show();
etName.requestFocus(); // 把光标移回姓名框
return;
}
if (phone.isEmpty()) {
Toast.makeText(MainActivity.this, "电话号码不能为空哦", Toast.LENGTH_SHORT).show();
etPhone.requestFocus();
return;
}
// 调用数据库类的方法存数据
long result = dbHelper.saveContact(name, phone);
if (result != -1) {
// 存成功了,给用户一个反馈
Toast.makeText(MainActivity.this,
"✅ 已保存:" + name + " " + phone,
Toast.LENGTH_LONG).show();
// 清空输入框,准备下一次输入
etName.setText("");
etPhone.setText("");
} else {
Toast.makeText(MainActivity.this,
"❌ 保存失败,请重试",
Toast.LENGTH_SHORT).show();
}
}
});
// ===== 按钮2:跳转到联系人列表界面 =====
btnView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// Intent是Android里"打电话"的方式,告诉系统我要去哪个界面
Intent intent = new Intent(MainActivity.this, ContactListActivity.class);
startActivity(intent); // 发起跳转
}
});
}
}
别忘了一件事:在AndroidManifest里注册新Activity
这是新手最容易踩的坑。打开 app/manifests/AndroidManifest.xml,在 <application> 标签内部加上这一行:
<activity
android:name=".ContactListActivity"
android:exported="false"/>
不注册的话,跳转时会直接崩溃,日志里会报 Activity not found。注册的本质就是告诉系统:”我有个叫 ContactListActivity 的界面,你可以去启动它。”
跑起来,看看效果
点 Android Studio 右上角的 Run 按钮,选一个模拟器(没模拟器的话连上手机也行)。等编译完,界面就会弹出来。
先试试输入框留空点保存——会弹出”姓名不能为空”的提示。然后正常输入”小明 13900139000”,点保存,Toast 会告诉你存成功了。再去点”查看已存联系人”,跳转到新界面,列表里就能看到刚才那条记录。多点几次不同的名字,返回列表就会发现它们都按时间倒序排好了。
可能遇到的几个坑
坑一:存完数据重启应用后没了。
正常情况下不会,因为数据存在 SQLite 文件里,路径是 /data/data/你的包名/databases/contacts.db。但如果你在模拟器里清除了应用数据,数据库文件会被删掉。这是正常行为,不是bug。
坑二:Cursor 忘记关闭导致内存泄漏。
上面的代码里我每次都手动 cursor.close() 了,这是好习惯。Android 的 Cursor 不是自动回收的,用完一定要关。
坑三:输入框内容带了空格导致查询出问题。
代码里用了 .trim() 去掉首尾空格,这个细节别忽略。
坑四:多线程同时操作数据库。
本示例是单线程的简单案例,实际项目中如果有多线程需求,要注意 getWritableDatabase() 本身是线程安全的,但复杂的写操作建议用 Room 框架或者加锁。
下一步可以加什么
这个小项目把最核心的流程走通了。如果想继续深入,可以往这几个方向扩展:
- 加点 删除功能,在列表的每条记录旁边放个删除按钮,调用
db.execSQL("DELETE FROM contacts WHERE id=?") - 加点 搜索功能,给
getAllContacts()加个WHERE name LIKE ?条件 - 把
ListView换成RecyclerView,列表项用自定义布局,性能更好、更灵活 - 用 Room 数据库 替代手写 SQLite,这是Google官方推荐的现代方案
但先把眼前的这个跑顺了,后面的路自然就清楚了。
写在最后
学编程最好的方式不是看文档,而是动手把代码敲一遍、把程序跑起来、把bug一个个修掉。今天这个案例里的每一个类、每一行SQL、每一次 startActivity,都是Android开发里最基础的砖块。把它们垒好了,后面盖高楼就稳了。
如果你在跑代码的过程中遇到任何问题,别急着跳过,报错信息往往就是最直接的线索。把日志里的关键信息截图搜索,基本上都能找到答案。祝你玩得开心,下一个Android小应用就在你手里了!
