Android数据库应用(《Android开发入门与实战》选摘)

1.第一步

在Eclipse中,打开ex08_1_SQLite 项目,具体步骤如下。

·  新建一个项目。依次单击File→New→Android Project项。

·  在新建项目的对话框中,选择Create project from existing source项。

·  单击浏览,找到ex08_1_SQLite项目,然后单击确定。

程序的目录结构如图8-6所示。

2.第二步

单击运行项目,我们可以看到主界面如图8-7所示,这个界面的布局信息都在main.xml文件中,在一个LinearLayout当中数值排列了5个Button。

3.第三步

小知识

什么是SQLiteDatabase?

一个SQLiteDatabase的实例代表了一个SQLite的数据库,通过SQLiteDatabase实例的一些方法,我们可以执行SQL语句,对数据库进行增、删、查、改的操作。需要注意的是,数据库对于一个应用来说是私有的,并且在一个应用当中,数据库的名字也是惟一的。

          

                   ▲ 图8-6  程序目录结构图                                   ▲ 图8-7  主界面

小知识

什么是SQLiteOpenHelper ?

根据这名字,我们可以看出这个类是一个辅助类。这个类主要生成一个数据库,并对数据库的版本进行管理。当在程序当中调用这个类的方法getWritableDatabase()或者getReadableDatabase()方法的时候,如果当时没有数据,那么Android系统就会自动生成一个数据库。SQLiteOpenHelper 是一个抽象类,我们通常需要继承它,并且实现里边的3个函数,具体函数如下所示。

·  onCreate(SQLiteDatabase):在数据库第一次生成的时候会调用这个方法,一般我们在这个方法里边生成数据库表。

·  onUpgrade(SQLiteDatabase, int, int):当数据库需要升级的时候,Android系统会主动的调用这个方法。一般我们在这个方法里边删除数据表,并建立新的数据表,当然是否还需要做其他的操作,完全取决于应用的需求。

·  onOpen(SQLiteDatabase):这是当打开数据库时的回调函数,一般也不会用到。

我们在ActivityMain文件中看下边这个内部类。DatabaseHelper 类继承SQLiteOpenHelper ,具体代码如下所示:

private static class DatabaseHelper extends SQLiteOpenHelper {

DatabaseHelper(Context context) {

super(context, DATABASE_NAME, null, DATABASE_VERSION);

}

@Override

public void onCreate(SQLiteDatabase db) {

// SQL语句

String sql = "CREATE TABLE " + TABLE_NAME + " (" + TITLE

+ " text not null, " + BODY + " text not null " + ");";

Log.i("haiyang:createDB=", sql);

//执行这条SQL语句

db.execSQL(sql);

}

@Override

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}

}

 代码解释:

·  DatabaseHelper类继承了SQLiteOpenHelper 类,并且重写了onCreate和onUpgrade方法。

·  在onCreate()方法里边首先我们构造一条SQL语句,然后调用db.execSQL(sql)执行SQL语句。这条SQL语句为我们生成了一张数据库表。

·  目前我们还不需要升级数据库,所以我们在onUpgrade()函数里边没有执行任何操作。

4.第四步

文本框:   
▲ 图8-8  成功插入两条数据

我们单击插入两条记录的按钮,如果数据成功插入到数据库当中的diary表中,那么在界面的title区域就会有成功的提示,如图8-8所示。

单击这个按钮后,程序执行了监听器里的onClick方法,并最终执行了上述程序里的insertItem方法,其具体代码如下所示:

private void insertItem() {

SQLiteDatabase db = mOpenHelper.getWritableDatabase();

//首先生成SQL语句

String sql1 = "insert into " + TABLE_NAME + " (" + TITLE + ", " + BODY

+ ") values('haiyang', 'Android的发展真是迅速啊');";

String sql2 = "insert into " + TABLE_NAME + " (" + TITLE + ", " + BODY

+ ") values('icesky', 'Android的发展真是迅速啊');";

try {

Log.i("haiyang:sql1=", sql1);

Log.i("haiyang:sql2=", sql2);

db.execSQL(sql1);

db.execSQL(sql2);

setTitle("插入两条数据成功");

} catch (SQLException e) {

setTitle("插入两条数据失败");

}

}

 代码解释:

·  SQLiteDatabase db = mOpenHelper.getWritableDatabase()这条语句负责得到一个可写的SQLite数据库,如果这个数据库还没有建立,那么mOpenHelper辅助类负责建立这个数据库。如果数据库已经建立,那么直接返回一个可写的数据库。

·  sql1和sql2是我们构造的标准的插入SQL语句,如果对SQL语句不是很熟悉,可以参考相关的书籍。鉴于本书的重点是在Android方面,所以对SQL语句的构建不进行详细的介绍。

·  Log.i()会将参数内容打印到日志当中,并且打印级别是Info级别,在使用LogCat工具的时候我们会进行详细的介绍。

·  db.execSQL(sql1)语句对SQL语句进行执行。

小知识

对Android的打印级别介绍

Android支持5种打印级别,分别是Verbose、Debug、Info、Warning、Error,当然我们在程序当中一般用到的是Info级别,即将一些自己需要知道的信息打印出来,如图8-9所示。

▲ 图8-9  Android中的5种打印级别

注意

虽然执行SQL语句在Android当中并没有强制放在try catch 语句当中,但是我们最好这么做。并在catch模块中将错误信息打印在日志当中。这样一方面是方便调试,另一方面可以加强程序的健壮性。

文本框:   
▲ 图8-10  显示两条数据

5.第五步

单击查询数据库的按钮,会在界面的title区域显示当前数据表当中数据的条数,刚才我们插入了两条,那么现在单击后应该显示为两条,如图8-10所示。

单击这个按钮后,程序执行了监听器里的onClick方法,并最终执行了上述程序里的showItems方法,具体代码如下所示:

private void showItems() {

SQLiteDatabase db = mOpenHelper.getReadableDatabase();

String col[] = { TITLE, BODY };

//进行数据库查询

Cursor cur = db.query (TABLE_NAME, col, null, null, null, null, null);

Integer num = cur.getCount();

setTitle(Integer.toString(num) + " 条记录");

}

 代码解释:

·  SQLiteDatabase db = mOpenHelper.getReadableDatabase()语句首先得到一个可写的数据库。

·  Cursor cur = db.query(TABLE_NAME, col, null, null, null, null, null)语句将查询到的数据放到一个Cursor 当中。这个Cursor里边封装了这个数据表TABLE_NAME当中的所有条列。 query()方法相当的有用,在这里我们简单地讲一下。

·  第一个参数是数据库里边表的名字,比如在我们这个例子,表的名字就是TABLE_NAME,也就是"diary"。

·  第二个字段是我们想要返回数据包含的列的信息。在这个例子当中我们想要得到的列有title、body。我们把这两个列的名字放到字符串数组里边来。

·  第三个参数为selection,相当于SQL语句的where部分,如果想返回所有的数据,那么就直接置为null。

·  第四个参数为selectionArgs。在selection部分,你有可能用到“?”,那么在selectionArgs定义的字符串会代替selection中的“?”。

·  第五个参数为groupBy。定义查询出来的数据是否分组,如果为null则说明不用分组。

·  第六个参数为having ,相当于SQL语句当中的having部分。

·  第七个参数为orderBy,来描述我们期望的返回值是否需要排序,如果设置为null则说明不需要排序。

·  Integer num = cur.getCount()语句通过getCount()方法,可以得到Cursor当中数据的个数。

小知识

什么是Cursor ?

Cursor 在Android当中是一个非常有用的接口,通过Cursor 我们可以对从数据库查询出来的结果集进行随机的读写访问。

6.第六步

单击删除一条数据库的按钮后,如果成功删除,我们可以看到在屏幕的标题(title)区域有文字提示,如图8-11所示。

现在我们再单击查询数据库按钮,看数据库里边的记录是不是少了一条。单击查询数据库按钮后,出现如图8-12所示的界面。

         

           ▲ 图8-11  成功删除一条记录                                ▲ 图8-12  还剩一条记录

下面我们来看一下如何删除数据。

单击删除一条记录的按钮后,程序执行了监听器里的onClick方法,并最终执行了上述程序里的deleteItem方法,其代码如下所示:

private void deleteItem() {

    try {

    SQLiteDatabase db = mOpenHelper.getWritableDatabase();

    //进行删除操作

    db.delete(TABLE_NAME, " title = 'haiyang'", null);

    setTitle("删除title为haiyang的一条记录");

    } catch (SQLException e) {

    }

    }

 代码解释:

·  db.delete(TABLE_NAME, " title = 'haiyang'", null)语句删除了一条title='haiyang'的数据。当然如果有很多条数据title都为'haiyang',那么一并删除。我们对delete方法的参数进行以下介绍。

·  第一个参数是数据库表名,在这里是TABLE_NAME,也就是diary。

·  第二个参数,相当于SQL语句当中的where部分,也就是描述了删除的条件。

文本框:  
▲ 图8-13  删除数据库表

·  如果在第二个参数当中有“?”符号,那么第三个参数中的字符串会依次替换在第二个参数当中出现的“?”符号。

7.第七步

单击删除数据表,我们可以删除diary这张数据表,如图8-13所示。

下边我们看在代码部分,是怎么实现删除的,具体代码如下所示:

private void dropTable() {

SQLiteDatabase db = mOpenHelper.getWritableDatabase();

String sql = "drop table " + TABLE_NAME;

try {

//执行SQL语句

db.execSQL(sql);

setTitle("数据表成功删除:" + sql);

} catch (SQLException e) {

setTitle("数据表删除错误");

}

}

 代码解释:

首先我们构造了一个标准的删除数据表的SQL语句,然后执行这条语句db.execSQL(sql)。

8.第八步

现在单击其他的按钮,程序运行时有可能会出现异常,我们单击重新建立数据表按钮,如图8-14所示。

现在我们单击查询数据库,看里边是否有数据,如图8-15所示。

下边我们看一下程序是如何建立一张新表的,具体实现代码如下所示:

private void CreateTable() {

     SQLiteDatabase db = mOpenHelper.getWritableDatabase();

     String sql = "CREATE TABLE " + TABLE_NAME + " (" + TITLE

     + " text not null, " + BODY + " text not null " + ");";

          

             ▲ 图8-14  重新建立数据库表                         ▲ 图8-15  新建的表里边没有数据

     Log.i("haiyang:createDB=", sql);

     try {

     db.execSQL("DROP TABLE IF EXISTS diary");

     db.execSQL(sql);

     setTitle("数据表成功重建");

     } catch (SQLException e) {

     setTitle("数据表重建错误");

     }

     }

 代码解释:

·  sql变量表示的语句为标准的SQL语句,负责按要求建立一张新表。

·  db.execSQL("DROP TABLE IF EXISTS diary")语句表示,如果存在diary这张表,我们需要先删除,因为在同一个数据库当中不能出现两张同样名字的表。

·  db.execSQL(sql)语句执行SQL语句,新表建立。

8.4.2  完整地操作数据库——日记本实例

在上一个例子中,我们对Android系统自带的SQLite数据库进行了初步的学习,了解了一些增、删、改、查的基本工作。在这一节的例子当中,我们做了一个非常简便的日记本程序,虽然没有完善,但是已经是基本可以使用了。在例子当中,我们不但要对数据库进行增、删、改、查的操作,而且还要把数据库当中的数据显示在一个ListView当中,通过对ListView的操作,实现对数据的增、删、改、查操作。

通过这个例子我们可以学到以下操作:

·  如何对DatabaseHelper和SQLiteDatabase封装,以便让我们访问数据库更加方便和安全;

·  如何利用ContentValues类来代替原始的SQL语句进行数据库的操作;

·  如何使用SimpleCursorAdapter类和ListView配合进行ListView的显示。

日记本具体实现步骤如下所述。

1.第一步

在Eclipse中打开ex08_2_SQLite 项目,具体操作步骤如下。

·  新建一个项目。单击File→New→Android Project项。

·  在新建项目的对话框中,选择Create project from existing source项。

·  单击浏览,找到ex08_2_SQLite项目,然后单击确定。

程序的目录结构如图8-16所示。

2.第二步

我们首先运行一下建立的程序,将会出现如图8-17所示。

             

         ▲ 图8-16  程序的目录结构                       ▲ 图8-17  没有任何数据的程序主界面

程序的主Activity是ActivityMain,它是一个ListActivity,和它关联的布局文件是diary_list.xml。关于ListActivity的介绍。请参阅第7章关于ListView的介绍。

3.第三步

在继续操作前,让我们重点关注一下DiaryDbAdapter类,这个类封装了DatabaseHelper和SQLiteDatabase类,使得我们对数据库的操作更加安全和方便。

在DiaryDbAdapter的类变量里,主要定义了以下几个变量:

·  数据库、数据表、数据表中列的名字;

·  DatabaseHelper 和SQLiteDatabase的实例;

·  Context 实例。

DatabaseHelper 类的定义和上一个例子一样,只不过这个例子里边,我们在onUpgrade增加了升级的代码,具体如下所示:

private static class DatabaseHelper extends SQLiteOpenHelper {

         DatabaseHelper(Context context) {

             super(context, DATABASE_NAME, null, DATABASE_VERSION);

         }

         @Override

         public void onCreate(SQLiteDatabase db) {

//生成数据库

            db.execSQL(DATABASE_CREATE);

       

         @Override

         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

             db.execSQL("DROP TABLE IF EXISTS diary");

             onCreate(db);

         }

    }

 代码解释:

在DiaryDbAdapter类里,向外界提供了以下一些方法。

·  open(),调用这个方法后,如果数据库还没有建立,那么会建立数据库,如果数据库已经建立了,那么会返回可写的数据库实例。

·  close(),调用此方法,DatabaseHelper 会关闭对数据库的访问。

·  createDiary(String title, String body)通过一个title和body字段在数据库当中创建一条新的纪录。

·  deleteDiary(long rowId)通过记录的id,删除数据库中的那条记录。

·  getAllNotes()得到diary表中所有的记录,并且以一个Cursor的形式进行返回。

·  getDiary(long rowId)通过记录的主键id,得到特定的一条记录。

·  updateDiary(long rowId, String title, String body)更新主键id为rowId那条记录中的两个字段title和body字段的内容。

小知识

什么是ContentValues类?

ContentValues类和Hashtable比较类似,它也是负责存储一些名值对,但是它存储的名值对当中的名是一个String类型,而值都是基本类型。

我们回顾一下,在上一个例子当中,我们是通过SQL语句进行插入操作,SQL语句的好处是比较直观,但是容易出错。但是在这个例子当中我们有更好的办法,在这里我们将要插入的值都放到一个ContentValues的实例当中,然后执行插入操作,具体代码如下所示:

public long createDiary(String title, String body) {

        ContentValues initialValues = new ContentValues();

        initialValues.put(KEY_TITLE, title);

        initialValues.put(KEY_BODY, body);

        Calendar calendar =  Calendar.getInstance();

// 生成年月日字符串

        String created = calendar.get(Calendar.YEAR)+"年"+calendar.get(Calendar. MONTH)+"月"+calendar.get(Calendar.DAY_OF_MONTH)+"日"+calendar.get(Calendar.HOUR_OF _DAY)+"时"+calendar.get(Calendar.MINUTE)+"分";

        initialValues.put(KEY_CREATED, created);

        return mDb.insert(DATABASE_TABLE, null, initialValues);

}

 代码解释:

·  ContentValues initialValues = new ContentValues()语句实例化一个contentValues类。

·  initialValues.put(KEY_TITLE, title)语句将列名和对应的列值放置到initialValues里边。

·  mDb.insert(DATABASE_TABLE, null, initialValues)语句负责插入一条新的纪录,如果插入成功则会返回这条记录的id,如果插入失败会返回-1。

在更新一条记录的时候,我们也是采用ContentValues 的这套机制,具体代码如下所示:

public boolean updateDiary(long rowId, String title, String body) {

ContentValues args = new ContentValues();

args.put(KEY_TITLE, title);

args.put(KEY_BODY, body);

Calendar calendar = Calendar.getInstance();

String created = calendar.get(Calendar.YEAR) + "年"

+ calendar.get(Calendar.MONTH) + "月"

+ calendar.get(Calendar.DAY_OF_MONTH) + "日"

+ calendar.get(Calendar.HOUR_OF_DAY) + "时"

+ calendar.get(Calendar.MINUTE) + "分";

args.put(KEY_CREATED, created);

return mDb.update(DATABASE_TABLE, args, KEY_ROWID + "=" + rowId, null) > 0;

}

 代码解释:

文本框:   
▲ 图8-18  单击menu

实现更新一条记录。

4.第四步

现在返回到程序的主界面,对应的Activity是ActivityMain。

当我们单击menu按钮后会出现如图8-18所示界面。

根据我们第7章对menu的学习,对单击menu里边按钮的处理逻辑全部放在onMenuItem Selected函数里,具体代码如下所示:

public boolean onMenuItemSelected(int featureId, MenuItem item) {

switch (item.getItemId()) {

case INSERT_ID:

createDiary();

return true;

case DELETE_ID:

mDbHelper.deleteDiary(getListView().getSelectedItemId());

renderListView();

return true;

}

return super.onMenuItemSelected(featureId, item);

}

 代码解释:

·  如果单击添加一篇新日记按钮那么会执行到createDiary()语句。

·  如果单击删除一条记录,会执行mDbHelper.deleteDiary(getListView().getSelectedItemId())语句,首先删除当前被选中的某一项所对应的数据库当中的记录。

·  renderListView()语句重新对界面刷新。

在createDiary()函数里边的代码如下所示:

private void createDiary() {

Intent i = new Intent(this, ActivityDiaryEdit.class);

startActivityForResult(i, ACTIVITY_CREATE);

}

 代码解释:

·  首先构造了一个intent,这个intent负责跳转到ActivityDiaryEdit里。

·  然后启动这个intent,并且需要返回值。

5.第五步

在ActivityMain中,有多处地方都用到了renderListView()函数。在onCreate里边用这个函数显示ListView。当ListView需要发生变化后,例如,删除了一条记录或者增加了一条记录的时候,我们调用这个函数进行刷新ListView,下边来看一下此函数的实现,具体代码如下所示:

private void renderListView() {

mDiaryCursor = mDbHelper.getAllNotes();

startManagingCursor(mDiaryCursor);

String[] from = new String[] { DiaryDbAdapter.KEY_TITLE,

DiaryDbAdapter.KEY_CREATED };

int[] to = new int[] { R.id.text1, R.id.created };

SimpleCursorAdapter notes = new SimpleCursorAdapter(this,

R.layout.diary_row, mDiaryCursor, from, to);

setListAdapter(notes);

}

 代码解释:

·  mDiaryCursor = mDbHelper.getAllNotes()语句,我们首先获取数据库当中的所有数据,这些数据以Cursor的形式存在。

·  startManagingCursor(mDiaryCursor)语句,我们将生成的Cursor交给Activity来管理,这样的好处是系统能自动做很多事情,比如当程序暂停的时候,这个系统可以卸载Cursor以节省空间,当程序重新启动的时候系统重新查询生成Cursor。

·  String[] from 里边定义了ListView每一排对应的数据是从数据库中的哪个列表里选取。

·  和SimpleAdapter类似 int[] to 里边是一个View的数组。这些View只能是TextView或者ImageView。这些View是以id的形式来表示的,如Android.R.id.text1。

·  SimpleCursorAdapter notes = new SimpleCursorAdapter(this,R.layout.diary_row, mDiaryCursor, from, to)语句生成一个SimpleCursorAdapter ,我们介绍以下每一个参数的意义。

·  第一个参数是Context。

·  第二个参数为R.layout.diary_row,它关联在diary_row.xml文件当中定义的Layout,这个Layout规定ListView当中每一项的布局。

·  第三个参数就是Cursor。

·  第四个参数是数据表当中的列的数组,只有在这里边出现的列名,数据才会对应的填充在to里边对应的TextView或者ImageView当中。

·  第五个参数是在ListView里边每一项中需要被数据填充的TextView或者ImageView。

·  setListAdapter(notes)语句将SimpleCursorAdapter 和ListActivity里边的ListView绑定起来,至此在界面当中才会显示出列表来。

小知识

什么是SimpleCursorAdapter ?

在第7章,我们已经介绍过了ArrayAdapter和SimpleAdapter。和它们俩类似,SimpleCursorAdapter 也是集成Adapter。ArrayAdapter负责把一个字符串数组中的数据填充到一个ListView当中,而对应的SimpleCursorAdapter 负责把Cursor里边的内容填充到ListView当中。通过SimpleCursorAdapter 可以把数据库当中一列的数据和ListView中一排进行对应起来。和前两个Adapter类似,要求和数据进行对应的View必须是TextView或者ImageView。

6.第六步

单击添加一条数据的按钮,程序运行界面如图8-19所示。

这个界面对应的Activity是ActivityDiaryEdit,对应的布局文件是diary_edit.xml。对于这个布局文件里边用到了LinearLayout、TextView和EditText,这些我们都已经讲过,在这里不再赘述,具体看一下代码:

文本框:   
▲ 图8-19  新建一篇日记的界面

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"

Android:orientation="vertical" Android:layout_width="fill_parent"

Android:layout_height="fill_parent">

<LinearLayout Android:orientation="vertical"

Android:layout_width="fill_parent"

Android:layout_height="wrap_content">

<TextView Android:layout_width="wrap_content"

Android:layout_height="wrap_content" Android:text="@string/title"

Android:padding="2px" />

<EditText Android:id="@+id/title"

Android:layout_width="fill_parent"

Android:layout_height="wrap_content" Android:layout_weight="1" />

</LinearLayout>

<TextView Android:layout_width="wrap_content"

Android:layout_height="wrap_content" Android:text="@string/body" />

<EditText Android:id="@+id/body" Android:layout_width="fill_parent"

Android:layout_height="wrap_content" Android:layout_weight="1"

Android:scrollbars="vertical" />

<Button Android:id="@+id/confirm" Android:text="@string/confirm"

Android:layout_width="wrap_content"

Android:layout_height="wrap_content" />

</LinearLayout>

 代码解释:

在一个LinearLayout里边我们放置了一些文本框、输入框和一些Button按钮。

当程序运行输入内容后,单击确定按钮,日记就会保存到数据库当中。下边来看一下代码具体是怎么执行的。当单击确定按钮后,系统回调执行和按钮绑定的单击监听器里边的onClick方法,具体代码如下所示:

public void onClick(View view) {

String title = mTitleText.getText().toString();

String body = mBodyText.getText().toString();

if (mRowId != null) {

mDbHelper.updateDiary(mRowId, title, body);

} else

mDbHelper.createDiary(title, body);

Intent mIntent = new Intent();

setResult(RESULT_OK, mIntent);

finish();

}

 代码解释:

·  首先获得EditView里边的数据。

·  目前mRowId 为null,所以执行mDbHelper.createDiary(title, body)语句将数据保存到数据当中。

·  setResult(RESULT_OK, mIntent)语句设置返回值。

执行完上边的代码后,系统跳转到ActivityMain,并执行回调函数onActivityResult,具体代码如下所示:

protected void onActivityResult(int requestCode, int resultCode,

Intent intent) {

super.onActivityResult(requestCode, resultCode, intent);

renderListView();

}

 代码解释:

·  在回调函数中,我们重新对ListView进行刷新,将所有的数据重新显示出。

7.第七步

单击ListView里边的条列,可以对刚才保存的数据进行编辑。具体怎么实现这个功能的,我们可以看一下ActivityMain当中的onListItemClick方法代码:

protected void onListItemClick(ListView l, View v, int position, long id) {

super.onListItemClick(l, v, position, id);

Cursor c = mDiaryCursor;

c.moveToPosition(position);

Intent i = new Intent(this, ActivityDiaryEdit.class);

i.putExtra(DiaryDbAdapter.KEY_ROWID, id);

i.putExtra(DiaryDbAdapter.KEY_TITLE, c.getString(c .getColumnIndexOrThrow(DiaryDbAdapter.KEY_TITLE)));

i.putExtra(DiaryDbAdapter.KEY_BODY, c.getString(c .getColumnIndexOrThrow(DiaryDbAdapter.KEY_BODY)));

startActivityForResult(i, ACTIVITY_EDIT);

}

 代码解释:

·  c.moveToPosition(position)语句将在Cursor当中的指针移到position位置,这个position是我们单击的这个一列在整个列表中的位置。

·  Intent i = new Intent(this, ActivityDiaryEdit.class)语句构造一个跳转到ActivityDiaryEdit的intent。

·  putExtra()方法负责将要传递的数据放到intent当中。

·  c.getString(c.getColumnIndexOrThrow(DiaryDbAdapter.KEY_TITLE))得到这一条数据中列名为title的值。

·  c.getString(c.getColumnIndexOrThrow(DiaryDbAdapter.KEY_BODY))得到这一条数据中列名为body的值。

·  startActivityForResult(i, ACTIVITY_EDIT)语句启动intent,发生Activity的跳转。

我们来看一下ActivityDiaryEdit中的onCreate()里边的代码:

Bundle extras = getIntent().getExtras();

if (extras != null) {

String title = extras.getString(DiaryDbAdapter.KEY_TITLE);

String body = extras.getString(DiaryDbAdapter.KEY_BODY);

mRowId = extras.getLong(DiaryDbAdapter.KEY_ROWID);

if (title != null) {

mTitleText.setText(title);

}

if (body != null) {

mBodyText.setText(body);

}

}

 代码解释:

文本框:  
▲ 图8-20  上下移动焦点界面

·  对于ActivityDiaryEdit这个Activity有两个intent可以跳转进来,一个是新建一篇日记的时候,这个时候intent里边的extras部分没有任何数据。第二种情况是在单击列表的某一个条列的时候,这个时候的intent如上所示会携带extras数据。所以在ActivityDiaryEdit中我们通过判断extras是否为null,就可以判断是哪种intent启动的。

·  当extras不为null,我们将extras里边的数据显示出来。

8.第八步

在程序的主界面当中,上下移动焦点(可以通过键盘的上下键或者模拟器中的上下按键),可以对拥有焦点的那一项进行删除,如图8-20、图8-21、图8-22所示。

具体执行onMenuItemSelected中的代码:

mDbHelper.deleteDiary(getListView().getSelectedItemId());

renderListView();

 代码解释:

·  getListView()方法获取当前的ListView引用。

·  getSelectedItemId()方法得到当前这一列所对应的数据项的rowId,也就是这条数据在数据库中的主键id。

             

              ▲ 图8-21  进行删除界面                                  ▲ 图8-22  删除后界面

·  mDbHelper.deleteDiary()方法删去数据库中的这一列数据。

·  renderListView()负责对列表重新刷新一遍。

至此,对数据库的学习就先告一段落,接下来将学习contentProvider,它是Android应用当中非常重要的一部分。而且程序间的大部分数据交换都是通过contentProvider机制进行。

8.5  我的数据你来用——ContentProvider介绍

8.5.1  初识ContentProvider 

在第6章当中,介绍了组成Android程序的主要4部分,它们分别是。

·  Activity。

·  Broadcast Intent Receiver。

·  Service。

·  Content Provider。

关于Actvity和相关View的部分,已经在前边章节进行了比较详细的介绍,在这一节中,将学习Android应用里另外一个非常重要的部分ContentProvider。

1.什么是ContentProvider

Android这个系统和其他的操作系统还不太一样,读者需要记住的是,数据在Android当中是私有的,当然这些数据包括文件数据和数据库数据以及一些其他类型的数据。那这个时候有读者就会提出问题,难道两个程序之间就没有办法对于数据进行交换?Android这么优秀的系统不会让这种情况发生的。解决这个问题主要靠ContentProvider。一个Content Provider类实现了一组标准的方法接口,从而能够让其他的应用保存或读取此Content Provider的各种数据类型。也就是说,一个程序可以通过实现一个Content Provider的抽象接口将自己的数据暴露出去。外界根本看不到,也不用看到这个应用暴露的数据在应用当中是如何存储的,或者是用数据库存储还是用文件存储,还是通过网上获得,这些一切都不重要,重要的是外界可以通过这一套标准及统一的接口和程序里的数据打交道,可以读取程序的数据,也可以删除程序的数据,当然,中间也会涉及一些权限的问题。下边列举一些较常见的接口,这些接口如下所示。

·  query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder):通过Uri进行查询,返回一个Cursor。

·  insert(Uri url, ContentValues values):将一组数据插入到Uri 指定的地方。

·  update(Uri uri, ContentValues values, String where, String[] selectionArgs):更新Uri指定位置的数据。

·  delete(Uri url, String where, String[] selectionArgs):删除指定Uri并且符合一定条件的数据。

2.什么是ContentResolver

外界的程序通过ContentResolver接口可以访问ContentProvider提供的数据,在Activity当中通过getContentResolver()可以得到当前应用的ContentResolver实例。ContentResolver提供的接口和ContentProvider中需要实现的接口对应,主要有以下几个。

·  query(Uri uri, String[] projection, String selection, String[] selectionArgs,String sortOrder):通过Uri进行查询,返回一个Cursor。

·  insert(Uri url, ContentValues values):将一组数据插入到Uri 指定的地方。

·  update(Uri uri, ContentValues values, String where, String[] selectionArgs):更新Uri指定位置的数据。

·  delete(Uri url, String where, String[] selectionArgs):删除指定Uri并且符合一定条件的数据。

3.ContentProvider和ContentResolver中用到的Uri

在ContentProvider和ContentResolver当中用到了Uri的形式通常有两种,一种是指定全部数据,另一种是指定某个ID的数据。我们看下面的例子。

·  content://contacts/people/  这个Uri指定的就是全部的联系人数据。

·  content://contacts/people/1 这个Uri指定的是ID为1的联系人的数据。 

在上边两个类中用到的Uri一般由3部分组成。

·  第一部分是:"content://" 。

·  第二部分是要获得数据的一个字符串片段。

·  最后就是ID(如果没有指定ID,那么表示返回全部)。

由于URI通常比较长,而且有时候容易出错,且难以理解。所以,在Android当中定义了一些辅助类,并且定义了一些常量来代替这些长字符串的使用,例如下边的代码:

·  Contacts.People.CONTENT_URI (联系人的URI)。

8.5.2  使用ContentProvider读取系统数据

在这个例子里边,首先在系统的联系人应用当中插入一些联系人信息,然后把这些联系人的名字和电话再显示出来,通过这个例子可以学到。

·  如何在联系人应用当中添加联系人。

·  如何使用系统提供的ContentProvider。

·  如何使用ContentResolver当中的query()方法。

具体实现步骤如下所示。

1.第一步

在Eclipse中打开ex09_1_ContentProvider项目,具体操作如下。

(1)新建一个项目,依次单击File→New→Android Project项。

(2)在新建项目的对话框中,选择Create project from existing source项。

(3)单击浏览按钮,找到ex09_1_ContentProvider项目,然后单击确定按钮。 

程序的目录结构如图8-23所示。

2.第二步

首先运行这个项目,将会看到如图8-24所示的界面。

             

            ▲ 图8-23  程序的目录结构                        ▲ 图8-24  未添加任何数据的主界面

图8-24所示的列表中没有任何数据,接下来的操作是为应用添加几条联系人数据。

3.第三步

按照下列图示添加几条数据到联系人列表中,具体步骤如下。

(1)单击模拟器的Home键,在转出来的界面上,单击桌面上的Contacts应用,如图8-25所示。

(2)进入应用后,单击MENU项,在出现的界面上单击New contact 按钮,如图8-26所示。

      

             ▲ 图8-25  单击Contacts应用                        ▲ 图8-26  单击New contact选项

(3)添加联系人姓名和电话号码信息,如图8-27所示。

(4)单击MENU项,在返回的界面上单击Save项保存,如图8-28所示。

                  

        ▲ 图8-27  添加联系人姓名和电话号码                  ▲ 图8-28  保存联系人姓名和电话号码信息

(5)按照上边的操作步骤,添加了两条数据后显示如图8-29所示。

4.第四步

再次运行程序,模拟器显示如图8-30所示。

           

                ▲ 图8-29  添加后结果                                  ▲ 图8-30  模拟器显示

我们看一下程序ActivityMain中的onCreate()方法,具体代码如下所示:

protected void onCreate(Bundle savedInstanceState) {

       super.onCreate(savedInstanceState);

       Cursor c = getContentResolver().query(Phones.CONTENT_URI, null, null, null, null);

       startManagingCursor(c); 

       ListAdapter adapter = new SimpleCursorAdapter(this,

               Android.R.layout.simple_list_item_2, c, 

                       new String[] { Phones.NAME, Phones.NUMBER }, 

                       new int[] { Android.R.id.text1, Android.R.id.text2 });

       setListAdapter(adapter);

}

 代码解释:

·  getContentResolver()方法得到应用的ContentResolver实例。

·  query(Phones.CONTENT_URI, null, null, null, null)。它是ContentResolver里的方法,负责查询所有联系人,并返回一个Cursor。这个方法参数比较多,每个参数的具体含义如下。

·  第一个参数为Uri,在这个例子里边这个Uri是联系人的Uri。

·  第二个参数是一个字符串的数组,数组里边的每一个字符串都是数据表中某一列的名字,它指定返回数据表中那些列的值。

·  第三个参数相当于SQL语句的where部分,描述哪些值是我们需要的。

·  第四个参数是一个字符串数组,它里边的值依次代替在第三个参数中出现的“?”符号。

·  第五个参数指定了排序的方式。

·  startManagingCursor(c)语句让系统来管理生成的Cursor。

·  ListAdapter adapter = new SimpleCursorAdapter(this,Android.R.layout.simple_list_item_2, c, new String[] { Phones.NAME, Phones.NUMBER }, new int[] { Android.R.id.text1, Android.R.id.text2 })语句生成一个SimpleCursorAdapter。(关于SimpleCursorAdapter我们在第7章已经详细介绍过了。)

·  setListAdapter(adapter)。将ListView和SimpleCursorAdapter进行绑定。

8.5.3  使用ContentProvider操作数据日记本实例

在上一个例子当中学习了ContentProvider,并且使用了系统中的一个联系人的ContentProvider,在本节当中,我们仍然实现类似第8章所讲的日记本的例子,只不过这次我们用ContentProvider实现,而不是直接用数据库实现。这样外界的程序就可以访问得到日记本这个应用数据。

通过这个例子可以学到。

·  如何实现一个ContentProvider。

·  理解UriMatcher的含义。

·  onPrepareOptionsMenu方法介绍。

具体实现步骤如下所示。

1.第一步

在Eclipse中打开ex09_2_contentProvider项目,具体操作步骤如下。

(1)新建一个项目,依次单击 File → New → Android Project项。

(2)在新建项目的对话框中,选择Create project from existing source项。

(3)单击浏览项,找到ex09_2_contentProvider项目,然后单击确定。 

程序的目录结构如图8-31所示。

2.第二步

首先运行这个项目,将会看到如图8-32所示界面。

           

           ▲ 图8-31  程序的目录结构                     ▲ 图8-32  未添加任何数据的主界面

图8-32所示的这个界面我们在第8章的例子里已经见过。本例的主Activity仍然是一个ListActivity,而且关联的布局文件也是diary_list.xml,关于ListActivity的使用方法和详细说明可以参见第7章第6节。diary_list.xml的代码如下所示:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"

Android:layout_width="wrap_content"

Android:layout_height="wrap_content">

<ListView Android:id="@+id/Android:list"

Android:layout_width="wrap_content"

Android:layout_height="wrap_content" />

<TextView Android:id="@+id/Android:empty"

Android:layout_width="wrap_content"

Android:layout_height="wrap_content" Android:text="您还没有开始写日记呢!单击下边的Menu按钮开始写日记吧:)" />

</LinearLayout>

 代码解释:

其中ListView的id我们必须定义成Android:id=“@+id/Android:list”,这样系统才可以在List Activity里引用的到。

3.第三步

和第8章的前一个日记本例子一样,日记本的数据还是存储在SQLite数据库中,但是不一样的是,在这个例子里,执行增、删、改、查操作时不是直接访问数据库,而是通过日记本程序的ContentProivder来实现。

我们先来看一下Diary这个类,这个类里边有一个内部静态类DiaryColumns,和它的名字意思一样,这个类里主要定义了日记本数据库的列表字段的名字,具体代码如下所示:

public static final class DiaryColumns implements BaseColumns {

        private DiaryColumns() {}

        public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/diaries");

        public static final String CONTENT_TYPE = "vnd.Android.cursor.dir/vnd.google. diary";

        public static final String CONTENT_ITEM_TYPE = "vnd.Android.cursor.item/vnd. google.diary";

        public static final String DEFAULT_SORT_ORDER = "created DESC";

        public static final String TITLE = "title";

        public static final String BODY = "body";

        public static final String CREATED = "created";

    }

 代码解释:

·  BaseColumns 是一个接口,里边有两个变量,一个是_ID=“_id”,一个是_COUNT="_ count" 。在Android当中,每一个数据库表至少有一个字段,而且这个字段是_id。所以当我们构造列名的辅助类时,直接实现BaseColumns ,这样我们便默认地拥有了_id字段。

·  在我们的日记本的数据表里,一共有4个字段,分别是:“id”、"title"、"body"、"created"。

小知识

在Android的设计“哲学”里是鼓励开发者使用内部类的,这样不但使用方便,而且执行效率也高。

4.第四步

现在我们来看DiaryContentProvider类,这个类继承自ContentProvider,在这个类里边实现了ContentProvider的一些接口方法,并且成为日记本的一个ContentProvider。我们现在通过学习这个类,来详细了解实现一个自定义的ContentProvider的具体步骤。

(1)继承ContentProvider.DiaryContentProvider类是继承ContentProvider类的。

(2)定义一个 public static final的Uri类型的变量,并且命名为CONTENT_URI。Diary Content Provider所能处理的Uri都是基于CONTENT_URI来构建的,需要注意的是,这个CONTENT_URI中的内容以content://开头,并且全部小写,且全局惟一。

下边是在这个例子中的一个普通URI,通过分析这个URI,可以了解content URI的构成,代码如下所示:

content://com.ex09_2_contentprovider.diarycontentprovider/diaries/1

 代码解释:

·  第一部分是content://,这部分是一直存在的,也是不用做什么修改的。

·  第二部分是授权(AUTHORITY)部分,在这个例子里边就是com.ex09_2_ content provider.diarycontentprovider,授权部分是惟一的,在程序中一般是我们实现的那个Content Provider的全称,并且全都小写。这部分是和在AndroidManifest.xml文件当中的<provider Android:name ="DiaryContentProvider"  Android:authorities="com.ex09_2_contentprovider. diaryconten tprovider" />部分对应的。

·  第三部分是请求数据的类型,例如,在这个例子当中定义的类型是diaries。当然这一部分可以是0个片段或者多个片段构成,如果Content Provider只是暴露出了一种类型的数据,那么这部分可以为空,但是如果暴露出了多种,尤其是包含子类的时候,就不能为空,例如,日记本程序里边可以暴露出来两种数据,一种是用户自己的“diaries/my”,另一种是其他人的“diaries/others”。

·  第四部分就是“1”,当然这部分是允许为空的。如果为空,表示请求全部数据;如果不为空,表示请求特定ID的数据。

(3)构建用户的数据存储系统。在这个例子当中,是将数据存储到数据库系统当中。通常是将数据存储在数据库系统中,但是也可以将数据存储在其他的地方,如文件系统等。

(4)实现ContentProvider这个抽象类的抽象方法,具体如下所示:

·  public boolean onCreate(),当ContentProvider生成的时候调用此方法。

·  public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder) ,此方法返回一个Cursor 对象作为查询结果集。

·  public Uri insert(Uri uri, ContentValues initialValues),此方法负责往数据集当中插入一列,并返回这一列的Uri。

·  public int delete(Uri uri, String where, String[] whereArgs),此方法负责删除指定Uri的数据。

·  public int update(Uri uri, ContentValues values, String where,String[] whereArgs),此方法负责更新指定Uri的数据。

·  public String getType(Uri uri),返回所给Uri的MIME类型。

(5)在AndroidManifest.xml文件中增加<provider>标签,例如,在这个例子中,实现的代码为:<provider Android:name="DiaryContentProvider" Android:authorities="com.ex09_2_contentprovider. diarycontentprovider" />, 其中Android:name为我们实现的这个content provider的类名;Android: authorities为content URI的第二部分,即授权部分,代码为:"com.ex09_2_contentprovider. diarycontentprovider"。

5.第五步

继续来看DiaryContentProvider类的代码。此实例的数据是存储在数据库当中,和前面章节学过的数据例子一样,此实例中我们定义了DatabaseHelper 辅助类。这个类在第8章中有详细的讲解,这里就不再进行详细说明,具体代码如下所示:

private static class DatabaseHelper extends SQLiteOpenHelper {

DatabaseHelper(Context context) {

super(context, DATABASE_NAME, null, DATABASE_VERSION);

}

@Override

public void onCreate(SQLiteDatabase db) {

db.execSQL("CREATE TABLE " + DIARY_TABLE_NAME + " ("

+ DiaryColumns._ID + " INTEGER PRIMARY KEY,"

+ DiaryColumns.TITLE + " TEXT," + DiaryColumns.BODY

+ " TEXT," + DiaryColumns.CREATED + " TEXT" + ");");

}

@Override

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

db.execSQL("DROP TABLE IF EXISTS notes");

onCreate(db);

}

}

 代码解释:

DatabaseHelper里操作数据库的辅助类,通过这个类我们可以生成数据库,并且维护这个数据库。

在DiaryContentProvider中,我们定义了一些变量和常量,其中这些常量主要是描述数据库的信息。

private static final String DATABASE_NAME = "database";

private static final int DATABASE_VERSION = 1;

private static final String DIARY_TABLE_NAME = "diary";

private static HashMap<String, String> sDiariesProjectionMap;

private static final int DIARIES = 1;

private static final int DIARY_ID = 2;

private static final UriMatcher sUriMatcher;

小知识

什么是UriMatcher?

UriMatcher是匹配Uri的一个辅助类,例如,在我们的DiaryContentProvider中的static模块中,有下边的代码:

sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

sUriMatcher.addURI(Diary.AUTHORITY, "diaries", DIARIES);

sUriMatcher.addURI(Diary.AUTHORITY, "diaries/#", DIARY_ID);

 代码解释:

·  sUriMatcher.addURI(Diary.AUTHORITY, "diaries", sUriMatcher.match(uri))表示,如果我们的Uri 是content://com.ex09_2_contentprovider.diarycontentprovider/diaries/,那么sUriMatcher.match(uri) 的返回值就是DIARIES。

·  sUriMatcher.addURI(Diary.AUTHORITY, "diaries/#", DIARY_ID)表示,如果我们的Uri是content://com.ex09_2_contentprovider.diarycontentprovider/diaries/id(其中后边的id是一个数字),那么sUriMatcher.match(uri)的返回值就是DIARY_ID。

通过UriMatcher类我们可以很方便地判断一个Uri的类型,特别是判断这个Uri是对单个数据的请求,还是对全部数据的请求。

6.第六步

现在我们来看一下在第四步列出来的抽象方法具体是怎么实现的。我们就从增、删、改、查的顺序说起。

(1)首先来看插入的方法:insert()。

具体实现代码如下所示:

@Override

public Uri insert(Uri uri, ContentValues initialValues) {

if (sUriMatcher.match(uri) != DIARIES) {

throw new IllegalArgumentException("Unknown URI " + uri);

}

ContentValues values;

if (initialValues != null) {

values = new ContentValues(initialValues);

} else {

values = new ContentValues();

}

if (values.containsKey(Diary.DiaryColumns.CREATED) == false) {

values.put(Diary.DiaryColumns.CREATED, getFormateCreatedDate());

}

if (values.containsKey(Diary.DiaryColumns.TITLE) == false) {

Resources r = Resources.getSystem();

values.put(Diary.DiaryColumns.TITLE, r

.getString(Android.R.string.untitled));

}

if (values.containsKey(Diary.DiaryColumns.BODY) == false) {

values.put(Diary.DiaryColumns.BODY, "");

}

SQLiteDatabase db = mOpenHelper.getWritableDatabase();

long rowId = db.insert(DIARY_TABLE_NAME, DiaryColumns.BODY, values);

if (rowId > 0) {

Uri diaryUri= ContentUris.withAppendedId(

Diary.DiaryColumns.CONTENT_URI, rowId);

return diaryUri;

}

throw new SQLException("Failed to insert row into " + uri);

}

 代码解释:

·  首先我们通过语句sUriMatcher.match(uri) != DIARIES对传进来的Uri进行了判断,如果这个Uri不是DIARIES类型的,那么这个Uri就是一个非法的Uri。

·  SQLiteDatabase db = mOpenHelper.getWritableDatabase()语句负责得到一个SQLiteDatabase 的实例。

·  db.insert(DIARY_TABLE_NAME, DiaryColumns.BODY, values)语句负责插入一条记录到数据库中。

·  需要注意的是,insert()返回的是一个Uri,而不是一个记录的id,所以,我们还应该把记录的id构造成一个Uri:Uri diaryUri= ContentUris.withAppendedId(Diary.DiaryColumns.CONTENT_URI, rowId).withAppendedId()方法在下边的小知识当中详细介绍。

小知识

什么是ContentUris?

ContentUris是content URI的一个辅助类。它有两个方法很有用,具体如下所示。

·  public static Uri withAppendedId(Uri contentUri, long id),这个方法负责把id和contentUri连接成一个新的Uri。比如在我们这个例子当中是这么使用的:ContentUris.withAppendedId (Diary. DiaryColumns.CONTENT_URI, rowId)。如果rowId为100的话,那么现在的这个Uri的内容就是:

content://com.ex09_2_contentprovider.diarycontentprovider/diaries/100。

·  public static long parseId(Uri contentUri),这个方法负责把content URI 后边的id解析出来,比如现在这个content URI 是content://com.ex09_2_contentprovider.diarycontentprovider/diaries/100,那么这个函数的返回值就是100。

(2)现在来看删除方法:delete()。

具体实现代码如下所示:

@Override

public int delete(Uri uri, String where, String[] whereArgs) {

SQLiteDatabase db = mOpenHelper.getWritableDatabase();

String rowId = uri.getPathSegments().get(1);

return db .delete(DIARY_TABLE_NAME, DiaryColumns._ID + "=" + rowId, null);

}

 代码解释:

·  rowId = uri.getPathSegments().get(1)负责得到rowId的值。

getPathSegments()方法得到一个String的List,在我们例子当中uri.getPathSegments().get(1)为rowId,如果是uri.getPathSegments().get(0)那值就为"diaries"。

·  db.delete(DIARY_TABLE_NAME, DiaryColumns._ID + "=" + rowId, null)是标准的SQLite删除操作。第一个参数数据表的名字,第二个参数相当于SQL语句当中的where部分。

(3)更新一条数据的方法:update()。

具体实现代码如下所示:

@Override

public int update(Uri uri, ContentValues values, String where,

String[] whereArgs) {

SQLiteDatabase db = mOpenHelper.getWritableDatabase();

String rowId = uri.getPathSegments().get(1);

return db.update(DIARY_TABLE_NAME, values, DiaryColumns._ID + "="

+ rowId, null);

}

 代码解释:

·  首先得到SQLiteDatabase 的实例,然后得到rowId,最后再调用db.update(DIARY_ TABLE_NAME, values, DiaryColumns._ID + "="+ rowId, null)语句执行更新工作。

(4)查寻一条数据的方法:query()。

具体实现代码如下所示:

@Override

public Cursor query(Uri uri, String[] projection, String selection,

String[] selectionArgs, String sortOrder) {

SQLiteQueryBuilder qb = new SQLiteQueryBuilder();

switch (sUriMatcher.match(uri)) {

case DIARIES:

qb.setTables(DIARY_TABLE_NAME);

break;

case DIARY_ID:

qb.setTables(DIARY_TABLE_NAME);

qb.appendWhere(DiaryColumns._ID + "="

+ uri.getPathSegments().get(1));

break;

default:

throw new IllegalArgumentException("Unknown URI " + uri);

}

String orderBy;

if (TextUtils.isEmpty(sortOrder)) {

orderBy = Diary.DiaryColumns.DEFAULT_SORT_ORDER;

} else {

orderBy = sortOrder;

}

SQLiteDatabase db = mOpenHelper.getReadableDatabase();

Cursor c = qb.query(db, projection, selection, selectionArgs, null,

null, orderBy);

return c;

}

 代码解释:

·  SQLiteQueryBuilder 是一个构造SQL查询语句的辅助类。

·  sUriMatcher.match(uri),根据返回值可以判断这次查询请求时,它是请求全部数据还是某个id的数据。

·  如果返回值是DIARIES,那么只需要执行qb.setTables(DIARY_TABLE_NAME)语句就可以了。

·  如果返回值是DIARY_ID,那么还需要将where部分的参数设置进去,代码为qb.appendWhere(DiaryColumns._ID + "="+ uri.getPathSegments().get(1))。

·  SQLiteDatabase db = mOpenHelper.getReadableDatabase(),得到一个可读的SQLiteDatabase 实例。

·  Cursor c = qb.query(db, projection, selection, selectionArgs, null,null, orderBy)语句,这个查询类似于一个标准的SQL查询,但是这个查询是SQLiteQueryBuilder 来发起的,而不是SQLiteDatabase 直接发起的,所以在参数方面略有不同。这个函数为 query(SQLiteDatabase db, String[] projectionIn, String selection, String[] selectionArgs, String groupBy, String having, String sortOrder, String limit)下边将各个参数介绍一下。

·  第一个参数为要查询的数据库实例。

·  第二个参数是一个字符串数组,里边的每一项代表了需要返回的列名。

·  第三个参数相当于SQL语句中的where部分。

·  第四个参数是一个字符串数组,里边的每一项依次替代在第三个参数中出现的问号(?)。

·  第五个参数相当于SQL语句当中的groupby部分。

·  第六个参数相当于SQL语句当中的having部分。

·  第七个参数描述是怎么进行排序。

·  第八个参数相当于SQL当中的limit部分,控制返回的数据的个数。

在DiaryContentProvider类里边还有两个方法,一个是getType(Uri uri),另一个是getFormateCreatedDate()。后者根据时间得到一个我们特定格式的字符串,比较简单。而前者是必须要重写的方法。下边看一下我们如何重写了getType方法,具体代码如下所示:

public String getType(Uri uri) {

switch (sUriMatcher.match(uri)) {

case DIARIES:

return DiaryColumns.CONTENT_TYPE;

case DIARY_ID:

return DiaryColumns.CONTENT_ITEM_TYPE;

default:

throw new IllegalArgumentException("Unknown URI " + uri);

}

}

 代码解释:

·  此方法返回一个所给Uri的指定数据的MIME类型。它的返回值如果以vnd.Android. cursor.item开头,那么就代表这个Uri指定的是单条数据。如果是以vnd.Android.cursor.dir开头的话,那么说明这个Uri指定的是全部数据。

7.第七步

在程序主界面单击MENU键,程序运行界面如图8-33所示。

在ActivityMain中执行的代码如下所示:

@Override

public boolean onCreateOptionsMenu(Menu menu) {

super.onCreateOptionsMenu(menu);

menu.add(0, MENU_ITEM_INSERT, 0, R.string.menu_insert);

return true;

}

 代码解释:

·  我们给菜单(menu)添加了一项,如图8-33所示。

单击添加日记按钮后进入如图8-34所示界面。

              

               ▲ 图8-33  单击MENU键                             ▲ 图8-34  新建日记的页面

图8-34所示的这个页面的布局和第8章前一个日记本程序里的布局完全一样,关于布局,读者可以参看第8章日记本程序例子的讲解。

下面来看一下在ActivityDiaryEditor中,是怎么处理两种不同情况进入日记本程序界面的。一种是通过新建日记进入此界面,另一种是经过编辑日记进入此日记运行界面。下面是在ActivityDiaryEditor程序中使用的onCreate()函数,其代码如下所示:

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setTheme(Android.R.style.Theme_Black);

final Intent intent = getIntent();

final String action = intent.getAction();

setContentView(R.layout.diary_edit);

mTitleText = (EditText) findViewById(R.id.title);

mBodyText = (EditText) findViewById(R.id.body);

confirmButton = (Button) findViewById(R.id.confirm);

if (EDIT_DIARY_ACTION.equals(action)) {// 编辑日记

mState = STATE_EDIT;

mUri = intent.getData();

mCursor = managedQuery(mUri, PROJECTION, null, null, null);

mCursor.moveToFirst();

String title = mCursor.getString(1);

mTitleText.setTextKeepState(title);

String body = mCursor.getString(2);

mBodyText.setTextKeepState(body);

setResult(RESULT_OK, (new Intent()).setAction(mUri.toString()));

setTitle("编辑日记");

} else if (INSERT_DIARY_ACTION.equals(action)) {// 新建日记

mState = STATE_INSERT;

setTitle("新建日记");

} else {

Log.e(TAG, "no such action error");

finish();

return;

}

confirmButton.setOnClickListener(new View.OnClickListener() {

public void onClick(View view) {

if (mState == STATE_INSERT) {

insertDiary();

} else {

updateDiary();

}

Intent mIntent = new Intent();

setResult(RESULT_OK, mIntent);

finish();

}

});

}

 代码解释:

·  通过getIntent()得到启动当前Activity的那个Intent。

·  通过 intent.getAction()得到这个intent的动作(Action)。在此操作中,当前的Activiy是通过单击先前的新建日记的按钮进来的,所以,看一下ActivityMain当中的onOptionsItemSelected()函数,了解一下动作(Action)是怎么设置的,此代码如下所示:

Intent intent0 = new Intent(this, ActivityDiaryEditor.class);

intent0.setAction(ActivityDiaryEditor.INSERT_DIARY_ACTION);

intent0.setData(getIntent().getData());

startActivity(intent0);

 代码解释:

·  从上述代码可以看到,通过语句intent0.setAction(ActivityDiaryEditor.INSERT_ DIARY _ACTION)设置的action是ActivityDiaryEditor.INSERT_DIARY_ACTION)实现的。

·  在ActivityDiaryEditor中的onCreate()函数中,当动作(Action)为INSERT_DIARY_ACTION时候,我们执行mState = STATE_INSERT,也就是标记当前的状态为插入状态。

·  如果当前的动作(Action)是EDIT_DIARY_ACTION,那么当前就是编辑状态:mState = STATE_EDIT。

当运行程序填充数据后单击确定按钮,执行confirmButton的单击监听器当中的onClick()函数。这个函数根据不同的状态执行插入或者更新数据的操作。

按照现在的程序执行流程,单击确定按钮后,程序将执行插入操作,实现插入操作代码如下所示:

private void insertDiary() {

String title = mTitleText.getText().toString();

String body = mBodyText.getText().toString();

ContentValues values = new ContentValues();

values.put(Diary.DiaryColumns.CREATED, DiaryContentProvider

.getFormateCreatedDate());

values.put(Diary.DiaryColumns.TITLE, title);

values.put(Diary.DiaryColumns.BODY, body);

getContentResolver().insert(Diary.DiaryColumns.CONTENT_URI, values);

}

 代码解释:

·  首先通过getText()方法将编辑框中的数据都读出来。

·  将要插入的数据都放到一个ContentValues 的实例当中。

·  调用getContentResolver()得到当前应用的一个ContentResolver的实例。

·  insert(Diary.DiaryColumns.CONTENT_URI, values)语句为ContentResolver的插入方法,这个方法有两个参数,第一个参数是数据的Uri,第二个参数是包含要插入数据的ContentValues 的实例。执行这条语句的最终会调用DiaryContentProvider中的insert()方法。

当单击确定按钮后,程序运行出现如图8-35所示的界面。

8.第八步

按方向键向下键将焦点移动到这条数据上,然后单击MENU键会出现如图8-36所示界面。

图8-36所示的这个界面和刚才讲到的单击MENU键出现的界面一样,下面看一下实现的代码,以便了解此界面是怎么生成的,具体实现代码如下所示:

             

          ▲ 图8-35  已经插入了一条数据                             ▲ 图8-36  单击MENU键界面

public boolean onPrepareOptionsMenu(Menu menu) {

super.onPrepareOptionsMenu(menu);

final boolean haveItems = getListAdapter().getCount() > 0;

if (haveItems) {

if (getListView().getSelectedItemId() > 0) {

menu.removeGroup(1);

Uri uri = ContentUris.withAppendedId(getIntent().getData(),

getSelectedItemId());

Intent intent = new Intent(null, uri);

menu.add(1, MENU_ITEM_EDIT, 1, "编辑内容").setIntent(intent);

menu.add(1, MENU_ITEM_DELETE, 1, "删除当前日记");

}

}else{

menu.removeGroup(1);

}

return true;

}

 代码解释:

·  当前和这个ListView相关联的ListAdapter里边元素的个数大于零的时候haveItems为真,否则为假。

·  menu.removeGroup(1),如果menu里边有分组为1组的子项的话,那么就先全部删除。

·  getIntent().getData()得到的Uri是DiaryColumns.CONTENT_URI。

·  通过ContentUris.withAppendedId(getIntent().getData(),getSelectedItemId())生成一个和ListView中获得焦点这一项的数据的Uri。

·  menu.add(1, MENU_ITEM_EDIT, 1, "编辑内容").setIntent(intent),在menu当中增加了一项“编辑内容”,并且这一项和一个intent关联,这个intent主要用于携带数据,它里边携带的就是一个Uri的数据,在下边的onOptionsItemSelected()函数当中会用到。

·  menu.add(1, MENU_ITEM_DELETE, 1, "删除当前日记"),增加另外一项。

·  如果haveItems为假,也就是当前和这个ListView相关联的ListAdapter里边元素的个数为零,即数据显示在ListView上的时候,单击MENU按钮的话,如果menu当中还有第1分组的子项的话,就给删除了,实现语句为:menu.removeGroup(1)。

小知识

单击MENU按钮的时候,在Activity中回调的函数可能有两个。

第一个是onOptionsItemSelected(),这个函数只在第一次在当前应用当中单击MENU键的时候回调,以后再不回调。

第二个是onPrepareOptionsMenu(),这个函数在每次单击MENU键后显示menu的时候被系统回调,每次menu显示前都会回调此函数。我们一般根据条件改变menu显示的逻辑都放在这个函数里边。

当单击MENU键时出现3个按钮,单击其中的某一个按钮会触发Android系统回调onOptionsItemSelected()函数,此函数实现代码如下所示:

@Override

public boolean onOptionsItemSelected(MenuItem item) {

switch (item.getItemId()) {

// 插入一条数据

case MENU_ITEM_INSERT:

Intent intent0 = new Intent(this, ActivityDiaryEditor.class);

intent0.setAction(ActivityDiaryEditor.INSERT_DIARY_ACTION);

intent0.setData(getIntent().getData());

startActivity(intent0);

return true;

// 编辑当前数据内容

case MENU_ITEM_EDIT:

Intent intent = new Intent(this, ActivityDiaryEditor.class);

intent.setData(item.getIntent().getData());

intent.setAction(ActivityDiaryEditor.EDIT_DIARY_ACTION);

startActivity(intent);

return true;

// 删除当前数据

case MENU_ITEM_DELETE:

Uri uri = ContentUris.withAppendedId(getIntent().getData(),

getListView().getSelectedItemId());

getContentResolver().delete(uri, null, null);

renderListView();

}

return super.onOptionsItemSelected(item);

}

 代码解释:

·  当用户单击添加新日记按钮后,程序新建一个跳转Activity的intent0,并且设置了Action和data,这两个部分在程序跳转到ActivityDiaryEditor后会用到。

·  当用户单击编辑按钮后,程序也新建了一个跳转Activity的intent,并且也设置了Action和data。同样,这两部分在程序跳转到ActivityDiaryEditor后会用到。需要注意的是,通过item.getIntent().getData()得到了所需要的Uri。

·  当用户单击删除按钮后,程序通过ContentUris.withAppendedId(getIntent().getData(), getListView().getSelectedItemId())先得到需要删除数据的Uri,然后得到当前的ContentResolvor,然后调用它的delete方法进行删除。

·  删除数据后,调用renderListView()函数对ListView进行及时刷新。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值