本节书摘来自异步社区《Android 3D游戏开发技术宝典——OpenGL ES 2.0》一书中的第2章,第2.3节手机自带数据库——SQLite,作者 吴亚峰,更多章节内容可以访问云栖社区“异步社区”公众号查看
2.3 手机自带数据库——SQLiteAndroid 3D游戏开发技术宝典——OpenGL ES 2.0上一节介绍了如何使用Preferences存储简单数据,而复杂的数据就需要存储到文件或数据库中了。 Android自带了一款轻量级的关系数据库——SQLite,其具有体积小,功能强大等诸多特点,成为嵌入式设备首选的数据库系统。本节将带领读者走进SQLite的世界,去学习如何应用SQLite数据库进行数据的增、删、改、查等基本操作。
2.3.1 初识SQLiteSQLite是一款满足ACID特性的具有完备功能的关系数据库系统,由于其设计目标为轻量级、开源、支持嵌入式使用,因此,目前已经在嵌入式设备领域被广泛采用。其运行需要的系统资源非常少,在嵌入式设备中可能只需要几百KB的内存就够了。
SQLite对主流编程语言的支持也非常全面,如C#、PHP、Java等,同时还支持ODBC接口。另外,SQLite的性能也是一流的,在一般应用情况下,其处理速度比MySQL、PostgreSQL这两款著名的开源数据库管理系统都快。
提示 SQLite的最新版本为3.7.6,发布时间是2011年4月12日。其官方网站为:http://www.sqlite.org或者http://www.sqlite.com.cn,读者可以在该网站上获取SQLite的源代码和相关文档。虽然SQLite占用的资源非常少,但是其功能、特性与服务器级数据库相比却丝毫不差,这也是SQLite能够受到Android系统青睐的主要原因,其部分特性如下所列。
最大可以支持2TB的数据库文件。占用资源少,一般占用250KB左右。API非常简单,易于使用。没有任何额外的依赖,是独立的。源代码完全开放,可以用于任何用途。Android系统中很多的用户数据都是存储在SQLite数据库中的,如联系人信息、通话记录、短信等,由此可见SQLite对于Android的重要性。
提示 读者要想很好地使用SQLite数据库,必须熟练掌握SQL语言。这是由于SQL已经事实上成为关系数据库操作的标准语言,市面上的关系数据库几乎无一例外都支持SQL。因此在数据库领域,有这样一句话“学好SQL,走遍天下都不怕”。
2.3.2 SQLite数据库的基本操作一般学习数据库相关课程的时候,首先介绍的就是数据库的一些基本操作,如数据的增、删、改、查等。按照惯例,本书也首先简单介绍SQLite数据库的创建、关闭及数据的增加、删除、修改、查询等基本操作,具体如下所列。
提示 想在Android下通过Java编程对SQLite数据库进行操作,就必须要用到android.database.sqlite包下的SQLiteDatabase类,该类提供了对SQLite数据库进行基本操作的所有重要方法。创建数据库。创建数据库需要用到的是openDatabase方法,此方法签名为“public static SQLiteDatabase openDatabase (String path, SQLiteDatabase.CursorFactory factory, int flags)”。其中path为数据库所在的路径;factory为游标工厂;flags为标志,可以用来控制数据库的访问模式。
关闭数据库。关闭数据库需要用到的是close方法,此方法签名为“public void close()”。在实际开发中数据库使用完毕后,一定不要忘记使用该方法关闭数据库,以防止资源的浪费。
插入数据。插入数据可以使用insert方法,此方法签名为“public long insert (String table, String nullColumnHack, ContentValues values)”。其中table为待插入的表名,nullColumnHack通常设置为null,values为待插入的数据。
更新数据。更新数据可以使用update方法,其签名为“public int update(String table, ContentValues values, String whereClause, String[] whereArgs)”。其中table为待更新的表名;values为待更新内容;whereClause为where子句的内容,用来进行记录筛选;whereArgs为where子句的参数。
删除数据。删除数据可以使用delete方法,其签名为“public int delete (String table, String whereClause, String[] whereArgs)”。其中table为要操作的表名;whereClause为where子句的内容,用来进行记录筛选;whereArgs为where子句的参数。
查询数据。查询数据可以使用query方法,其方法签名为“public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)”。其中table为要查询的表,columns为要查询的列,selection为过滤记录的子句,selectionArgs为过滤的参数值,groupBy为分组子句,having为过滤分组的子句,orderBy为记录排序子句。
提示 Android中query方法被重载了,有多个变体。这里由于篇幅所限,不再赘述,有需要的读者可以自行查阅API或其他相关资料。执行非查询SQL。对于不太熟悉SQL语言的初学者而言,插入、更新、删除数据可以用前面介绍的insert、update、delete方法。但对于熟练掌握SQL的开发人员而言,就是使用execSQL方法直接执行相应的SQL语句更方便了。
此方法签名为“public void execSQL (String sql)”或“public void execSQL (String sql, Object[] bindArgs)”。其中sql为需要执行的SQL语句,bindArgs为带参数SQL语句的参数值数组。
提示 需要注意的是,此方法仅支持执行非查询的SQL语句,如CREATE TABLE、 DELETE、 INSERT、UPDATE等,不能用于执行SELECT语句。执行查询SQL。对于熟练掌握SQL的开发人员而言,会觉得前面介绍的query方法使用过于烦琐。Android的设计人员也考虑到了这个问题,提供了支持执行查询SQL语句的rawQuery方法,其方法签名为“public Cursor rawQuery (String sql, String[] selectionArgs)”。其中sql为要执行的查询SQL语句(可以带参数),selectionArgs为查询参数的值。
提示 SQLiteDatabase类中用于数据库操作的方法还有很多,本书只是介绍了其中一些常用的,若读者有需要可以查阅API或其他相关资料进一步学习。
2.3.3 SQLite数据库的简单案例上一小节介绍了SQLite数据库的基本操作方法,本小节将详细介绍一个使用SQLite数据库的简单案例,以使读者可以更加快速地掌握SQLite数据库的使用方法,从而在开发中进行合理地使用。本案例运行效果分别如图2-10、图2-11和图2-12所示。
介绍完本案例的运行效果后,接下来将开发本案例中唯一的一个类——Sample2_4_Activity,其代码如下。
1 package com.bn.pp4; //声明包 2 import android.app.Activity; //引入相关类 3 ……//此处省略了部分类的引入代码,读者可自行查看随书光盘的源代码 4 import android.widget.Toast; //引入相关类 5 public class Sample2_4_Activity extends Activity { 6 SQLiteDatabase sld; // 声明SQLiteDatabase引用 7 public void onCreate(Bundle savedInstanceState) { // 重写的onCreate方法 8 super.onCreate(savedInstanceState); 9 setContentView(R.layout.main); // 跳转到主界面 10 Button b = (Button) this.findViewById(R.id.Button01); // 获取打开/创建数据库按钮的引用 11 b.setOnClickListener( // 为打开/创建按钮添加监听器 12 new OnClickListener() { 13 public void onClick(View v) { 14 createOrOpenDatabase(); // 调用方法打开或创建数据库 15 }}); 16 b = (Button) this.findViewById(R.id.Button02); // 获取关闭数据库按钮的引用 17 b.setOnClickListener( // 为关闭按钮添加监听器 18 new OnClickListener() { 19 public void onClick(View v) { 20 closeDatabase(); // 调用方法关闭数据库 21 }}); 22 b = (Button) this.findViewById(R.id.Button03); // 获取添加记录按钮的引用 23 b.setOnClickListener( // 为添加按钮添加监听器 24 new OnClickListener() { 25 public void onClick(View v) { 26 insert(); // 调用方法插入记录 27 }}); 28 b = (Button) this.findViewById(R.id.Button04); // 获取删除记录按钮的引用 29 b.setOnClickListener( // 为删除按钮添加监听器 30 new OnClickListener() { 31 public void onClick(View v) { 32 delete(); // 调用方法删除记录 33 }}); 34 b = (Button) this.findViewById(R.id.Button05); // 获取查询记录按钮的引用 35 b.setOnClickListener( // 为查询按钮添加监听器 36 new OnClickListener() { 37 @Override 38 public void onClick(View v) { 39 query(); // 调用方法查询记录 40 }});} 41 public void createOrOpenDatabase() { // 创建或打开数据库的方法 42 try { 43 sld = SQLiteDatabase.openDatabase( 44 "/data/data/com.bn.pp4/mydb", // 数据库所在路径 45 null, // 游标工厂,默认为null 46 SQLiteDatabase.OPEN_READWRITE | 47 SQLiteDatabase.CREATE_IF_NECESSARY //模式为读写,不存在则创建 48 ); 49 // 生成创建数据库的SQL语句 50 String sql = "create table if not exists student" + 51 "(sno char(5),stuname varchar(20)," + 52 "sage integer,sclass char(5))"; 53 sld.execSQL(sql); // 执行SQL语句 54 Toast.makeText(getBaseContext(), "成功创建数据库。", 55 Toast.LENGTH_LONG).show(); 56 } catch (Exception e) { 57 e.printStackTrace(); 58 }} 59 public void closeDatabase() { // 关闭数据库的方法 60 try { 61 sld.close(); // 关闭数据库 62 Toast.makeText(getBaseContext(), "成功关闭数据库。", 63 Toast.LENGTH_LONG).show(); 64 } catch (Exception e) { 65 e.printStackTrace(); 66 }} 67 public void insert() { // 插入记录的方法 68 try { // 生成插入记录的SQL语句 69 String sql = "insert into student values" + 70 "('001','Android',22,'283')"; 71 sld.execSQL(sql); // 执行SQL语句 72 Toast.makeText(getBaseContext(), "成功插入一条记录。", 73 Toast.LENGTH_LONG).show(); 74 } catch (Exception e) { 75 e.printStackTrace(); 76 }} 77 public void delete() { // 删除记录的方法 78 try { // 生成删除所有记录的SQL语句 79 String sql = "delete from student;"; 80 sld.execSQL(sql); // 执行SQL语句 81 Toast.makeText(getBaseContext(), "成功删除所有记录。", 82 Toast.LENGTH_LONG).show(); 83 } catch (Exception e) { 84 e.printStackTrace(); 85 }} 86 public void query(){ // 查询的方法 87 try { // 生成查询记录的SQL语句 88 String sql = "select * from student where sage>?"; 89 Cursor cur = sld.rawQuery(sql, new String[] { "20" }); // 获取Cursor对象引用 90 while (cur.moveToNext()) { // 若存在记录 91 String sno = cur.getString(0); // 获取第一列信息 92 String sname = cur.getString(1); // 获取第二列信息 93 int sage = cur.getInt(2); // 获取第三列信息 94 String sclass = cur.getString(3); // 获取第四列信息 95 Toast.makeText( 96 getBaseContext(), 97 "查询到的记录为:'" + sno + "'\t'" + sname 98 + "'\t\t'" + sage+ "'\t'" + sclass + "'", 99 Toast.LENGTH_LONG).show(); 100 } 101 cur.close(); // 关闭Cursor 102 } catch (Exception e) { 103 e.printStackTrace(); 104 }}}第10-40行为案例中的各个按钮添加监听器,监听器中调用对应的方法来实现数据库的打 开/创建、关闭、插入、删除和查询等操作。第41-58行为创建及打开数据库的方法,方法中首先获取了SQLiteDatabase对象的引用,并为其指定数据库的存储路径和读写模式,然后用“create table”语句来创建了一张名称为student的表。第67-76行为向数据库中插入一条记录的方法,插入的记录内容为“‘001’,‘Android’,22,‘283’”。第77-85行为删除数据库中所有记录的方法。第86-104行为从数据库中查找符合条件记录的方法,首先需要获取Cursor对象的引用,并为其添加查找范围(具体范围为年龄大于20)。若查到相应记录,则将记录信息用Toast显示出来。
2.3.4 使用ContentProvider组件共享数据前一小节介绍了SQLite数据库中的一些操作,但有时数据库中的信息不但创建其的应用程序要使用,还希望能够分享给其他应用程序使用。这时就需要使用ContentProvider组件了,ContentProvider组件的基本情况如下所列。
Android平台中每个应用程序都有自己的用户ID并在自己的进程中运行,每个进程拥有独立的运行环境,这样可以保证程序的完整性。但这也使得应用程序在需要进行资源共享和数据通信时,很不方便。为了解决这一问题,Android提供了专门用来在应用程序之间分享数据的ContentProvider组件。ContentProvider能将应用程序中特定的数据提供给其他应用程序使用,这些数据可以来自应用程序私有文件夹下的私有数据文件,也可以来自应用程序自己私有的SQLite数据库。当然,数据的来源还有很多其他选择,ContentProvider组件本身并没有做出限制,读者可以充分发挥想象的空间。使用ContentProvider组件共享数据的基本方式是继承ContentProvider类并重写其中的相应方法,具体情况在后面的案例中进行介绍。别的应用程序想分享数据时需要使用ContentResolver,通过ContentResolver对象将分享的需求发送给ContentProvider组件,而不能直接调用ContentProvider组件。下面使用ContentProvider组件将上一小节的案例进行升级,使得此案例具有分享数据给其他应用程序的能力,其具体开发步骤如下。
(1)在案例Sample2_4的com/bn/pp4包下新建MyContentProvider类,该类继承自ContentProvider类,并要实现其中所有的抽象方法,具体代码如下。
1 package com.bn.pp4; //声明包 2 import android.content.ContentProvider; //引入相关类 3 ……//此处省略了部分类的引入代码,读者可自行查看随书光盘的源代码 4 import android.net.Uri; //引入相关类 5 public class MyContentProvider extends ContentProvider { // 继承ContentProvider 6 private static final UriMatcher um; // 声明Uri匹配引用 7 static { 8 um = new UriMatcher(UriMatcher.NO_MATCH); // 创建UriMatcher 9 um.addURI("com.bn.pp4.provider.student", "stu", 1); // 设置匹配字符串 10 } 11 SQLiteDatabase sld; // 声明SQLiteDatabase引用 12 public String getType(Uri uri) { 13 return null; 14 } 15 // 调用数据库的query方法时会自动调用该方法 16 public Cursor query(Uri uri, String[] projection, String selection, 17 String[] selectionArgs, String sortOrder) { 18 switch (um.match(uri)) { // 若匹配成功 19 case 1: // 执行操作,获取Cursor对象引用 20 Cursor cur = sld.query("student", projection, selection, 21 selectionArgs, null, null, sortOrder); 22 return cur; // 返回Cursor对象引用 23 } 24 return null; 25 } 26 public int delete(Uri arg0, String arg1, String[] arg2) { // 空实现 27 return 0; 28 } 29 public Uri insert(Uri uri, ContentValues values) { // 空实现 30 return null; 31 } 32 public boolean onCreate() { // 创建数据库时自动调用该方法 33 sld = SQLiteDatabase.openDatabase( 34 "/data/data/com.bn.pp4/mydb", // 数据库所在路径 35 null, // 游标工厂,默认为null 36 SQLiteDatabase.OPEN_READWRITE| 37 SQLiteDatabase.CREATE_IF_NECESSARY // 读写、若不存在则创建 38 ); 39 return false; 40 } 41 public int update(Uri uri, ContentValues values, String selection, 42 String[] selectionArgs) { // 空实现 43 return 0; 44 } 45 }第6-10行为声明Uri匹配对象,并且设置匹配字符串。此匹配字符串在需要得到分享数据的应用程序中提供给ContentResolver使用,以进行配对。第12-14行重写了getType方法,本案例中对getType方法没有要求,因此其返回空值。第16-25行重写了query方法,在匹配成功后,数据需求方通过ContentResolver调用此方法查询需要的数据。第26-31行重写了delete与insert方法,本案例中对这两个方法没有要求,因此都设置为返回空值。第32-40行重写了onCreate方法,其功能为首先获取SQLiteDatabase对象引用,然后创建或打开数据库,为信息的分享做好准备。第41-44行重写了update方法,本案例中对这个方法没有要求,因此设置为返回空值。(2)仅仅是完成上面的代码还是不够的,在Android程序开发中,有一个很重要的配置文件AndroidManifest.xml。要想使用ContentProvider组件,在完成代码的开发后,还必须在该配置文件中进行相应的配置,将如下代码插入到AndroidManifest.xml文件中的application标签中。
1 <provider 2 android:name="MyContentProvider" <!--将调用的类名--> 3 android:authorities="com/bn/pp4.provider.student" <!--要匹配的Uri字符串--> 4 />2.3.5 使用ContentResolver获取分享数据升级完了Sample2_4使其具有了数据分享能力之后,就可以在别的应用程序中通过ContentResolver匹配到Sample2_4案例中的ContentProvider组件获取分享的数据了。具体的开发步骤如下所列。
(1)新建项目Sample2_4_From,将项目的包名设定为com.bn.pp4f,并新建一个继承自Activity的类ContentConsumerActivity,其代码如下。
1 package com.bn.pp4f; //包声明 2 import android.app.Activity; //相关类的引入 3 //……此处省略了部分相关类的引入代码,读者可自行查看随书光盘的源代码 4 import android.widget.EditText; //相关类的引入 5 public class ContentConsumerActivity extends Activity { 6 ContentResolver cr; // ContentResolver的引用 7 @Override //重写方法的标志 8 public void onCreate(Bundle savedInstanceState) { 9 super.onCreate(savedInstanceState); //继承父类的onCreate方法 10 setContentView(R.layout.main); //跳转到主界面 11 cr=this.getContentResolver(); //获取ContentResolver的对象 12 //初始化查询按钮 13 Button b=(Button)this.findViewById(R.id.Button01); // Button类的引用 14 b.setOnClickListener( //设置按钮监听 15 new OnClickListener(){ 16 @Override //重写方法的标志 17 public void onClick(View v) { //重写onClick方法 18 String stuname="Android"; //设置查询的字符串 19 Cursor cur=cr.query( 20 Uri.parse("content://com.bn.pp4.provider.student/stu"), 21 new String[]{"sno","stuname","sage","sclass"}, 22 "stuname=?", //查询条件 23 new String[]{stuname}, 24 "sage ASC" 25 ); 26 while(cur.moveToNext()){ 27 String sno=cur.getString(0); //获取学号 28 String sname=cur.getString(1); //获取名称 29 int sage=cur.getInt(2); //获取年龄 30 String sclass=cur.getString(3); //获取班级 31 appendMessage(sno+"\t"+sname+"\t\t"+sage+"\t"+sclass); 32 } 33 cur.close(); //关闭ContentResolver 34 }});} 35 public void appendMessage(String msg){ //向文本区中添加文本 36 EditText et=(EditText)this.findViewById(R.id.EditText02);//获取EditText的对象 37 et.append(msg+"\n"); //添加显示的字符串 38 }}第8-25行主要功能为获取ContentResolver对象的引用,并给按钮添加监听器,使得按钮按下后可以通过ContentResolver匹配到Sample2_4案例中的ContentProvider组件获取需要的数据。第26-33行功能为将获取的Sample2_4案例分享的数据显示到屏幕上的EditText控件中。第35-38行为向EditText控件中添加文本信息的方法。(2)Sample2_4_From案例开发完成后,运行该案例,其效果如图2-13、图2-14所示。
说明 图2-13为运行该案例后的界面效果图,图2-14为单击“获取”按钮后,通过ContentResolver匹配到Sample2_4案例中的ContentProvider组件获取数据后的效果图。
相关资源:敏捷开发V1.0.pptx