今天来学习另一种比较专业的存储方式——数据库,在 Android 中引入了一个轻量级的数据库框架:SQLite。如果你对数据库非常熟悉,那它可以全面支持数据的 SQL 语言,同时也提供了 Java 接口方便不太熟悉数据库的 Android 工程师使用。
1. SQLite 是什么
SQLite 是一个开源的轻量级关系数据库管理系统,简称RDMS(Relational Database Management System)。Android 通过 SQLIte 来进行数据库增删改查等相关操作,如果想进一步了解 SQLite,可以参考网上教程的数据库相关课程,这里主要围绕 Android 中的使用方法来展开。
Android 已经预置了 SQLite DateBase,所以我们不需要做任何的依赖配置,跟前面几种存储方式一样,存储在 SQLite 的数据也是 App 的私有数据,也就是只有自己的 App 能够访问,这样能够保证数据安全。
2. 数据库辅助类
为了帮助我们快速使用数据库,Android 系统封装了一个辅助类——SQLite Helper,可以通过辅助类完成数据库的更新、升级等操作,SQLite Helper 使用示例如下:
public class DbHandler extends SQLiteOpenHelper { private static final int DB_VERSION = 1; private static final String DB_NAME = "usersdb"; private static final String TABLE_Users = "userdetails"; private static final String KEY_ID = "id"; private static final String KEY_NAME = "name"; private static final String KEY_LOC = "location"; private static final String KEY_DESG = "designation"; public DbHandler(Context context){ super(context,DB_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { // 创建表格 String CREATE_TABLE = "CREATE TABLE " + TABLE_Users + "(" + KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + KEY_NAME + " TEXT," + KEY_LOC + " TEXT," + KEY_DESG + " TEXT"+ ")"; db.execSQL(CREATE_TABLE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 删除表格 db.execSQL("DROP TABLE IF EXISTS " + TABLE_Users); // 重建 onCreate(db); } }
在代码中我们创建了一个名叫“ usersdb”的数据库,在“ usersdb”中创建了一张名为“ userdetails”的表格,里面包含姓名、地址位置、描述等用户信息。细心的读者可能会注意到,代码中存在两个生命周期回调方法:
onCreate(SQLiteDatabase db):
当数据库被创建的时候回调,此方法在整个 App 运行期间只会被回调一次,在数据库创建好之后就不会再回调,通常会把数据库中的表格创建代码写在onCreate()中。
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion):
当数据库需要升级时调用,通常发生在 App 覆盖升级的时候。当我们需要修改数据库,比如删除字段、增加表格、修改字段等等操作的时候,需要将对应数据库的版本号 +1,此时在 App 升级之后发现版本号有变化,便会回调此方法,我们可以在onUpgrade()方法中做数据的适配及搬迁。
3. 使用 SQL 操作数据库
前面提到过,我们完全可以通过传统的 SQL 语言来操作数据库。
3.1 SQL 语言执行方法
Android 为我们提供了以下方法来执行 SQL:
- **execSQL(SQL,Object[]):**执行带占位符的SQL语句,用于修改数据库内容
- **rawQuery(SQL,Object[]):**执行带占位符的SQL查询语句,返回查询结构的游标指针——Cursor
3.2 数据库查询指针
Cursor 相当于一个数据库指针,指向查询的结果,我们可以通过移动 Cursor 来获取想要的数据,Cursor支持以下方法:
- move(offset):
- 向上或者向下移动,参数是移动的行数,正数表示向下,负数向上
- moveToFirst():
- 移动到第一行,移动成功返回 true,否则为 false
- moveToLast():
- 移动到最后一行,成功返回true,否则为 flase
- moveToNext():
- 移动到下一行,成功返回true,否则为 false
- moveToPrevious():
- 移动到前一条数据
- getCount():
- 获得总得数据条数
- isFirst():
- 判断当前是否是第一条记录
- isLast():
- 判断是否是最后一条记录
- moveToPosition(int):
- 直接移动到指定行
4. 使用 Android 接口操作数据库
如果你对数据库并不是很熟悉或者不想用 SQL 来操作数据库,同样可以采用纯 Java 接口来操作。在 Android 中数据库的内容用 ContentValues 表示,所以我们的增删改查其实都是对某个 ContentValues 进行操作。
4.1 插入数据
我们通过insert()
方法进行插入:
// 采用写模式获取数据库对象 SQLiteDatabase db = this.getWritableDatabase(); // 创建一个数据表,key 就是表格每一列的列名 ContentValues cValues = new ContentValues(); cValues.put(KEY_NAME, name); cValues.put(KEY_LOC, location); cValues.put(KEY_DESG, designation); // 添加一行数据,返回这一行的主 key long newRowId = db.insert(TABLE_Users,null, cValues);
4.2 查询数据
查询数据是通过query()
方法实现的:
// 以写模式获取数据库对象 SQLiteDatabase db = this.getWritableDatabase(); // 从TABLE_Users中查询指定userid的数据的名称、位置、描述信息 Cursor cursor = db.query(TABLE_Users, new String[]{KEY_NAME, KEY_LOC, KEY_DESG}, KEY_ID+ "=?",new String[]{String.valueOf(userid)},null, null, null, null);
4.3 更新数据
更新数据使用update()
方法:
// 以写的模式获取数据库对象 SQLiteDatabase db = this.getWritableDatabase(); ContentValues cVals = new ContentValues(); cVals.put(KEY_LOC, location); cVals.put(KEY_DESG, designation); int count = db.update(TABLE_Users, cVals, KEY_ID+" = ?",new String[]{String.valueOf(id)});
4.4 删除数据
删除数据使用delete()
方法
SQLiteDatabase db = this.getWritableDatabase(); db.delete(TABLE_Users, KEY_ID+" = ?",new String[]{String.valueOf(userid)});
以上就是 SQLite 的 CURD(insert、update、delete、query)操作对应的几个系统 API。
5. SQLite 使用示例
数据库通常用于存储批量的 Key-Value,比如通讯录里、短信、用户信息等。本节就借用上一节的例子,在 SharePreferenced 一节中我们实现了一个登陆框,这一节我们稍作修改,做一个用户信息登记表。首页类似登录框一样提供一个用户信息等级的页面,然后登记完我们通过 ListView 展示所有用户信息。为了方便大家理解,我们来统计一下王者荣耀英雄的名称、主打位置以及特征描述。
5.1 英雄登记页面
等级页面主要两部分组成:
- **信息输入框:**包含名称、主打位置、特征
- 确认登记 Button
<?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"> <TextView android:id="@+id/fstTxt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:layout_marginTop="150dp" android:text="名称" /> <EditText android:id="@+id/txtName" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:ems="10" /> <TextView android:id="@+id/secTxt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:text="主打位置" /> <EditText android:id="@+id/txtLocation" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:ems="10" /> <TextView android:id="@+id/thirdTxt" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:text="特征描述" /> <EditText android:id="@+id/txtDesignation" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="100dp" android:ems="10" /> <Button android:id="@+id/btnSave" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="100dp" android:text="确认登记" /> </LinearLayout>
5.2 数据库辅助类
辅助类就是提供操作英雄登记表数据的增删改查,使用第 4 小节学习的 API 即可,创建“DbHelper.Java”类,编写如下代码:
package com.emercy.myapplication; 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.HashMap; public class DbHelper extends SQLiteOpenHelper { private static final int DB_VERSION = 1; private static final String DB_NAME = "herodb"; private static final String TABLE_Heres = "herodetails"; private static final String KEY_ID = "id"; static final String KEY_NAME = "name"; static final String KEY_POS = "position"; static final String KEY_DESG = "designation"; public DbHelper(Context context) { super(context, DB_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { String CREATE_TABLE = "CREATE TABLE " + TABLE_Heres + "(" + KEY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + KEY_NAME + " TEXT," + KEY_POS + " TEXT," + KEY_DESG + " TEXT" + ")"; db.execSQL(CREATE_TABLE); initHero(db, "安琪拉","中路", "草丛"); initHero(db, "马超","对抗路", "帅气"); initHero(db, "伽罗","发育路", "禁掉"); initHero(db, "鲁班","发育路", "手长"); initHero(db, "兰陵王","打野", "绕后"); initHero(db, "猴子","打野", "爆发"); } private void initHero(SQLiteDatabase db, String name, String location, String designation) { ContentValues cValues = new ContentValues(); cValues.put(KEY_NAME, name); cValues.put(KEY_POS, location); cValues.put(KEY_DESG, designation); long newRowId = db.insert(TABLE_Heres, null, cValues); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 重建数据表 db.execSQL("DROP TABLE IF EXISTS " + TABLE_Heres); onCreate(db); } // 增加英雄角色详情 void insertUserDetails(String name, String location, String designation) { // 获取可写数据库对象 SQLiteDatabase db = this.getWritableDatabase(); // 以key为列创建数据 ContentValues cValues = new ContentValues(); cValues.put(KEY_NAME, name); cValues.put(KEY_POS, location); cValues.put(KEY_DESG, designation); long newRowId = db.insert(TABLE_Heres, null, cValues); db.close(); } // 查询数据详情 public ArrayList<HashMap<String, String>> GetUsers() { SQLiteDatabase db = this.getWritableDatabase(); ArrayList<HashMap<String, String>> userList = new ArrayList<>(); StringBuilder queryBuilder = new StringBuilder(); String query = "SELECT " + KEY_NAME + ", " + KEY_POS + ", " + KEY_DESG + " FROM " + TABLE_Heres; Cursor cursor = db.rawQuery(query, null); while (cursor.moveToNext()) { HashMap<String, String> user = new HashMap<>(); user.put(KEY_NAME, cursor.getString(cursor.getColumnIndex(KEY_NAME))); user.put(KEY_POS, cursor.getString(cursor.getColumnIndex(KEY_POS))); user.put(KEY_DESG, cursor.getString(cursor.getColumnIndex(KEY_DESG))); userList.add(user); } return userList; } // 获取某个英雄的详情数据 public ArrayList<HashMap<String, String>> GetUserByUserId(int userid) { SQLiteDatabase db = this.getWritableDatabase(); ArrayList<HashMap<String, String>> userList = new ArrayList<>(); String query = "SELECT name, location, designation FROM " + TABLE_Heres; Cursor cursor = db.query(TABLE_Heres, new String[]{KEY_NAME, KEY_POS, KEY_DESG}, KEY_ID + "=?", new String[]{String.valueOf(userid)}, null, null, null, null); if (cursor.moveToNext()) { HashMap<String, String> user = new HashMap<>(); user.put("name", cursor.getString(cursor.getColumnIndex(KEY_NAME))); user.put("designation", cursor.getString(cursor.getColumnIndex(KEY_DESG))); user.put("location", cursor.getString(cursor.getColumnIndex(KEY_POS))); userList.add(user); } return userList; } // 删除数据 public void DeleteUser(int userid) { SQLiteDatabase db = this.getWritableDatabase(); db.delete(TABLE_Heres, KEY_ID + " = ?", new String[]{String.valueOf(userid)}); db.close(); } // 更新英雄数据 public int UpdateUserDetails(String location, String designation, int id) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues cVals = new ContentValues(); cVals.put(KEY_POS, location); cVals.put(KEY_DESG, designation); int count = db.update(TABLE_Heres, cVals, KEY_ID + " = ?", new String[]{String.valueOf(id)}); return count; } }
我们在onCreate
中创建了英雄表格,并预置了几条英雄数据(英雄描述纯手打,有误请谅解 - -!),接着对外暴露了表格的 CURD 接口。
5.3 英雄登记逻辑
读取输入框的英雄属性,接着通过DbHelper
类提供的 API 进行插入操作:
package com.emercy.myapplication; import android.app.Activity; import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; public class MainActivity extends Activity { EditText name, loc, desig; Button saveBtn; Intent intent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); name = (EditText) findViewById(R.id.txtName); loc = (EditText) findViewById(R.id.txtLocation); desig = (EditText) findViewById(R.id.txtDesignation); saveBtn = (Button) findViewById(R.id.btnSave); saveBtn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String username = name.getText().toString(); String location = loc.getText().toString(); String designation = desig.getText().toString(); DbHelper DbHelper = new DbHelper(MainActivity.this); DbHelper.insertUserDetails(username, location, designation); intent = new Intent(MainActivity.this, SecondActivity.class); startActivity(intent); Toast.makeText(getApplicationContext(), "英雄信息登记成功", Toast.LENGTH_SHORT).show(); } }); } }
5.4 英雄列表 listView
创建 list.xml 布局文件,用来展示英雄的名称、位置和描述:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="5dp"> <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="17sp" android:textStyle="bold" /> <TextView android:id="@+id/designation" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/name" android:layout_marginTop="7dp" android:textColor="#343434" android:textSize="14dp" /> <TextView android:id="@+id/location" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/designation" android:layout_alignBottom="@+id/designation" android:layout_alignParentEnd="true" android:textColor="#343434" android:textSize="14sp" /> </RelativeLayout>
5.5 英雄列表展示
最后就是一个英雄列表的 Activity,里面放置一个 listView用于展示所有的英雄数据:
package com.emercy.myapplication; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.ListAdapter; import android.widget.ListView; import android.widget.SimpleAdapter; import java.util.ArrayList; import java.util.HashMap; import static com.emercy.myapplication.DbHelper.KEY_DESG; import static com.emercy.myapplication.DbHelper.KEY_NAME; import static com.emercy.myapplication.DbHelper.KEY_POS; public class SecondActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.second); DbHelper db = new DbHelper(this); ArrayList<HashMap<String, String>> userList = db.GetUsers(); ListView lv = (ListView) findViewById(R.id.user_list); ListAdapter adapter = new SimpleAdapter(SecondActivity.this, userList, R.layout.list, new String[]{KEY_NAME, KEY_DESG, KEY_POS}, new int[]{R.id.name, R.id.designation, R.id.location}); lv.setAdapter(adapter); Button back = (Button) findViewById(R.id.btnBack); back.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); } }
通过DbHelper提供的检索接口,拿到每一行的数据绑定到 ListView 上即可。编译运行,完成登陆之后便会跳转到英雄列表,此时列表中展示的是预置的数据以及我们刚刚登记的数据。
最后记得在 AndroidManifest.xml 中添加 SecondActivity 的注册。
<activity android:name=".SecondActivity" />
6. 小结
本节学习了一个比较专业的存储方式,SQLite 是一种轻量级的开源数据库框架,已经被预置到 Android 系统中,我们可以非常方便的使用。既可以通过标准的 SQL 语句进行操作,也可以使用Android 提供的 Java 接口做 CURD。通过最后一个例子可以学习到 SQLite 的所有常用接口,在今后需要用到数据库的时候,可以回头来看看本节的例子。