2.3 手机自带数据库——SQLite

上一节介绍了如何使用Preferences存储简单数据,而复杂的数据就需要存储到文件或数据库中了。Android自带了一款轻量级的关系数据库—SQLite,其具有体积小,功能强大等特点,成为嵌入式设备首选的数据库系统。本节将带领读者走进SQLite的世界,学习如何应用SQLite数据库进行数据的增、删、改、查等基本操作。

2.3.1 初识SQLite

SQLite是一款满足ACID特性的具有完备功能的关系数据库系统,由于其设计目标为轻量级、开源、支持嵌入式使用,因此,目前已经在嵌入式设备领域被广泛采用。其运行需要的系统资源非常少,在嵌入式设备中可能只需要几百KB的内存就够了。

SQLite对主流编程语言的支持也非常全面,如C#、PHP、Java等,同时还支持ODBC接口。另外,SQLite的性能也是一流的,在一般应用情况下,其处理速度比MySQL、PostgreSQL这两款著名的开源数据库管理系统都快。

提示

SQLite的最新版本为3.8.11.1,发布时间是2015年7月29日。其官方网站为: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所示。

▲图2-10 创建数据库

▲图2-11 插入记录

▲图2-12 查询记录

介绍完本案例的运行效果后,接下来将开发本案例中唯一的一个类—Sample2_4_Activity,其代码如下。

代码位置:见随书中源代码/第2章/Sample2_4/src/com/bn/pp4目录下的Sample2_4_Activity.java。

      1       package  com.bn.pp4;
      2     ……//此处省略了部分类的引入代码,读者可自行查看随书的源代码
      3       public  class  Sample2_4_Activity  extends  Activity  {
      4           SQLiteDatabase sld; // 声明SQLiteDatabase引用
      5               @Override
      6           public void onCreate(Bundle savedInstanceState) {    // onCreate方法
      7                       super.onCreate(savedInstanceState);
      8                 setContentView(R.layout.main);                    //跳转到主界面
      9                       Button  b  =  (Button)  this.findViewById(R.id.Button01);
                                                              //获取打开/创建数据库按钮的引用
      10                b.setOnClickListener(                    //为打开/创建按钮添加监听器
      11                     new  OnClickListener()  {
      12                              @Override
      13                              public  void  onClick(View  v)  {
      14                            createOrOpenDatabase();     //调用方法打开或创建数据库
      15                     }});
      16                b = (Button) this.findViewById(R.id.Button02); //获取关闭数据库按钮的引用
      17                b.setOnClickListener(                    //为关闭按钮添加监听器
      18                     new  OnClickListener()  {
      19                              @Override
      20                              public  void  onClick(View  v)  {
      21                            closeDatabase();              //调用方法关闭数据库
      22                     }});
      23                b = (Button) this.findViewById(R.id.Button03); //获取添加记录按钮的引用
      24                b.setOnClickListener(                    //为添加按钮添加监听器
      25                     new  OnClickListener()  {
      26                              @Override
      27                              public  void  onClick(View  v)  {
      28                            insert();                      //调用方法插入记录
      29                     }});
      30                b = (Button) this.findViewById(R.id.Button04); //获取删除记录按钮的引用
      31                b.setOnClickListener(                    //为删除按钮添加监听器
      32                     new  OnClickListener()  {
      33                              @Override
      34                              public  void  onClick(View  v)  {
      35                            delete();                      //调用方法删除记录
      36                     }});
      37                b = (Button) this.findViewById(R.id.Button05); //获取查询记录按钮的引用
      38                b.setOnClickListener(                    //为查询按钮添加监听器
      39                     new  OnClickListener()  {
      40                              @Override
      41                              public  void  onClick(View  v)  {
      42                            query();                       //调用方法查询记录
      43             }}); }
      44          public void createOrOpenDatabase() {         //创建或打开数据库的方法
      45                     try  {
      46                              sld  =  SQLiteDatabase.openDatabase(
      47                                  "/data/data/com.bn.pp4/mydb",        //数据库所在路径
      48                                  null,                    //游标工厂,默认为null
      49                                              SQLiteDatabase.OPEN_READWRITE  |
      50                                  SQLiteDatabase.CREATE_IF_NECESSARY //模式为读写,若不存在则创建
      51                      );                                    //生成创建数据库的SQL语句
      52                              String  sql  =  "create  table  if  not  exists  student"  +
      53                                              "(sno  char(5), stuname  varchar(20), "  +
      54                                              "sage  integer, sclass  char(5))";
      55                      sld.execSQL(sql);                  //执行SQL语句
      56                      Toast.makeText(getBaseContext(), "成功创建数据库。",
      57                                              Toast.LENGTH_LONG).show();
      58                     }  catch  (Exception  e)  {
      59                              e.printStackTrace();
      60             }}
      61          public void closeDatabase() {                 //关闭数据库的方法
      62                     try  {
      63                      sld.close();                        //关闭数据库
      64                      Toast.makeText(getBaseContext(), "成功关闭数据库。",
      65                                              Toast.LENGTH_LONG).show();
      66                     }  catch  (Exception  e)  {
      67                              e.printStackTrace();
      68             }}
      69          public void insert() {                         //插入记录的方法
      70                try  {                                      //生成插入记录的SQL语句
      71                              String  sql  =  "insert  into  student  values"  +
      72                                              "('001', 'Android',22, '283')";
      73                      sld.execSQL(sql);                  //执行SQL语句
      74                      Toast.makeText(getBaseContext(), "成功插入一条记录。",
      75                                               Toast.LENGTH_LONG).show();
      76                     }  catch  (Exception  e)  {
      77                              e.printStackTrace();
      78             }}
      79          public void delete() {                         //删除记录的方法
      80                try  {                                      //生成删除所有记录的SQL语句
      81                              String  sql  =  "delete  from  student; ";
      82                      sld.execSQL(sql);                  //执行SQL语句
      83                      Toast.makeText(getBaseContext(), "成功删除所有记录。",
      84                                              Toast.LENGTH_LONG).show();
      85                     }  catch  (Exception  e)  {
      86                              e.printStackTrace();
      87             }}
      88          public void query(){                           //查询的方法
      89                try {                                      //生成查询记录的SQL语句
      90                              String  sql  =  "select  *  from  student  where  sage>? ";
      91                              Cursor  cur  =  sld.rawQuery(sql,  new  String[]  {  "20"  });
                                                              //获取Cursor对象引用
      92                      while (cur.moveToNext()) {        //若存在记录
      93                            String sno = cur.getString(0);       //获取第一列信息
      94                            String sname = cur.getString(1);     //获取第二列信息
      95                            int sage = cur.getInt(2);             //获取第三列信息
      96                            String sclass = cur.getString(3);    //获取第四列信息
      97                                      Toast.makeText(
      98                                                      getBaseContext(),
      99                                        "查询到的记录为:'" + sno + "'\t'" + sname
      100                                                    +  "'\t\t'"  +  sage+  "'\t'"  +  sclass  +  "'",
      101                                                    Toast.LENGTH_LONG).show();
      102                            }
      103                     cur.close();                                  //关闭Cursor
      104                    }  catch  (Exception  e)  {
      105                            e.printStackTrace();
      106    }}}

❑ 第9-43行为案例中的各个按钮添加监听器,监听器中调用对应的方法来实现数据库的打开/创建、关闭、插入、删除和查询等操作。

❑ 第43-60行为创建及打开数据库的方法,方法中首先获取了SQLiteDatabase对象的引用,并为其指定数据库的存储路径和读写模式,然后用“create table”语句创建了一张名称为student的表。

❑ 第69-78行为向数据库中插入一条记录的方法,插入的记录内容为“'001', 'Android',22, '283'”。

❑ 第79-87行为删除数据库中所有记录的方法。

❑ 第88-106行为从数据库中查找符合条件记录的方法,首先需要获取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类,并要实现其中所有的抽象方法,具体代码如下。

代码位置:见随书中源代码/第2章/Sample2_4/src/com/bn/pp4目录下的MyContentProvider.java。

      1       package  com.bn.pp4;
      2     ……//此处省略了部分类的引入代码,读者可自行查看随书的源代码
      3     public class MyContentProvider extends ContentProvider {  //继承ContentProvider
      4           private static final UriMatcher um;               //声明Uri匹配引用
      5               static  {
      6                 um = new UriMatcher(UriMatcher.NO_MATCH);  //创建UriMatcher
      7                 um.addURI("com.bn.pp4.provider.student", "stu", 1); //设置匹配字符串
      8               }
      9           SQLiteDatabase sld;                                 //声明SQLiteDatabase引用
      10             @Override
      11             public  String  getType(Uri  uri)  {
      12                     return  null;
      13             }
      14          @Override  //调用数据库的query方法时会自动调用该方法
      15             public  Cursor  query(Uri  uri,  String[]  projection,  String  selection,
      16                              String[]  selectionArgs,  String  sortOrder)  {
      17                switch (um.match(uri)) {                //若匹配成功
      18                case 1:                                    //执行操作,获取Cursor对象引用
      19                              Cursor  cur  =  sld.query("student",  projection,  selection,
      20                                              selectionArgs,  null,  null,  sortOrder);
      21                      return cur;                         //返回Cursor对象引用
      22                     }
      23                     return  null;
      24             }
      25             @Override
      26          public int delete(Uri arg0, String arg1, String[] arg2) {    //空实现
      27                     return  0;
      28             }
      29             @Override
      30          public Uri insert(Uri uri, ContentValues values) {             //空实现
      31                     return  null;
      32             }
      33             @Override
      34          public boolean onCreate() {                        //创建数据库时自动调用该方法
      35                     sld  =  SQLiteDatabase.openDatabase(
      36                            "/data/data/com.bn.pp4/mydb",    //数据库所在路径
      37                            null,                               //游标工厂,默认为null
      38                                      SQLiteDatabase.OPEN_READWRITE|
      39                            SQLiteDatabase.CREATE_IF_NECESSARY //读写、若不存在则创建
      40                     );
      41                     return  false;
      42             }
      43             @Override
      44             public  int  update(Uri  uri,  ContentValues  values,  String  selection,
      45                      String[] selectionArgs) {              //空实现
      46                     return  0;
      47     }}

❑ 第4-8行为声明Uri匹配对象,并且设置匹配字符串。此匹配字符串在需要得到分享数据的应用程序中提供给ContentResolver使用,以进行配对。

❑ 第11-13行重写了getType方法,本案例中对getType方法没有要求,因此其返回空值。

❑ 第15-24行重写了query方法,在匹配成功后,数据需求方通过ContentResolver调用此方法查询需要的数据。

❑ 第26-32行重写了delete与insert方法,本案例中对这两个方法没有要求,因此都设置为返回空值。

❑ 第34-42行重写了onCreate方法,其功能为首先获取SQLiteDatabase对象引用,然后创建或打开数据库,为信息的分享做好准备。

❑ 第44-47行重写了update方法,本案例中对这个方法没有要求,因此设置为返回空值。

(2)仅仅是完成上面的代码还是不够的,在Android程序开发中,有一个很重要的配置文件AndroidManifest.xml。要想使用ContentProvider组件,在完成代码的开发后,还必须在该配置文件中进行相应的配置,将如下代码插入到AndroidManifest.xml文件中的application标签中。

代码位置:见随书中源代码/第2章/Sample2_4目录下的AndroidManifest.xml。

      1       <provider
      2          android:name="MyContentProvider"              <! --将调用的类名-->
      3          android:authorities="com/bn/pp4.provider.student"  <! --要匹配的Uri字符串-->
      4               android:exported="true"/>

2.3.5 使用ContentResolver获取分享数据

升级完了Sample2_4使其具有了数据分享能力之后,就可以在别的应用程序中通过ContentResolver匹配到Sample2_4案例中的ContentProvider组件获取分享的数据了。具体的开发步骤如下所列。

(1)创建项目Sample2_4_From,将项目的包名设定为com.bn.pp4f,并创建一个继承自Activity的类ContentConsumerActivity,其代码如下。

代码位置:见随书中源代码/第2章/ Sample2_4_From/src/com/bn/pp4f目录下的Content ConsumerActivity.java。

      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 运行界面1

▲图2-14 运行界面2

说明

图2-13为运行该案例后的界面效果图,图2-14为单击“获取”按钮后,通过ContentResolver匹配到Sample2_4案例中的ContentProvider组件获取数据后的效果图。