• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

12 游标和AsyncTask:连接数据库

武飞扬头像
梅槑
帮助1

1.引言

1.1 引入

本章将学习 让应用连接SQLite数据库

1.2 修改应用来使用数据库

有两个活动使用了Drink类。我们要在SQLite帮助器的帮助下让它们从SQLite数据库读取数据。我们要做到:
1.更新DrinkActivity中使用Drink的代码。
DrinkActivity使用Drink类来显示一个给定饮品的详细信息。我们要修改这个活动,让它从Starbuzz数据库获取这个饮品的记录
2.更新PrinkCategoryAetivity中使用Drink的代码。
DrinkCategoryActivity使用Drink类来显示所有饮品的一个列表。我们会修改这个活动,让它显示DRINK表中所有记录的一个列表。
3.让用户选择他们最喜欢的饮品。
在第11章中,我们升级了数据库,使DRINK表包含一个FAVORITE列。下面将修改这个应用,使用户可以标出哪些饮品是他们的最爱,并在TopLevelActivity中显示一个最爱饮品列表。

应用的新结构:

学新通

1.3 用游标从数据库得到数据

当前的DrinkActivity代码首先要从Drink类得到某个特定饮品的详细信息。如何修改这个代码,让它从Starbuzz数据库获取饮品详细信息?如何修改活动让它从数据库读取数据?答案就是使用游标

利用游标访问数据库数据

可以利用游标访问数据库记录集。指定你想访问哪些数据后,游标会从数据库返回记录。然后可以在游标提供的记录之间导航。

学新通

可以使用数据库查询指定想要访问什么数据 来创建游标。 

1.4 通过指定查询内容从数据库获取记录

1.4.1 指定表和列

在查询中,首先要指定希望从哪个表获取记录,另外需要哪些列的数据

学新通

 返回DRINK表中NAME和DESCRIPTION列的数据。

1.4.2 声明限制条件

指定想要哪些列之后,还可以声明数据必须满足的条件来过滤数据

例如,在我们的应用中,我们希望获取用户选择的那个饮品,为此,可以只返回_id为某个特定值的饮品。
学新通

1.4.3 使用查询得到的其他结果

 希望查询返回多行数据时,你会发现指定记录的顺序会很有用。例如,你可能想按饮品名的顺序对饮品记录排序。还可以使用查询以某种方式对数据分组,并对分组的数据应用聚集函数。例如,你可能想返回有多少种饮品,并在应用中显示。

1.4.4 利用SQLiteDatabase query()方法使用查询生成器构成SQL查询

可以使用SQLiteDatabase   query()方法建立一个查询。query()方法返回一个类型为Cursor的对象,活动可以使用这个对象来访问数据库

在底层,Android使用query()方法构造一个SQL  SELECT语句。

下面是query()的基本形式:

学新通

 可以使用这个版本的query()方法指定希望从哪个表返回数据,想要返回哪些列,希望对数据应用哪些条件,需要如何聚集数据,以及希望数据有怎样的顺序。

query()方法还有其他一些重载版本,允许为查询增加额外的细节,例如,是否希望每一行都是唯一的,以及希望返回的最大行数。我们不会全面介绍所有这些版本,不过如果你对这个内容感兴趣,可以在在线Android文档中找到重载方法的完整列表:学新通

指定表和列查询

最简单的数据库查询是返回某些列的所有记录,而不指定任何条件。

学新通应用条件来限制查询

可以使用第3个和第4个查询参数指定特定列必须有哪些值,对数据库查询应用某些查询条件。

学新通

对查询应用多个条件

 如果想对查询应用多个条件,需要确保按指定值的顺序来指定条件。如果指定条件的顺序与指定值的顺序不同,游标就会返回错误的数据。

学新通

条件指定为String值

条件值必须是string。如果要对一个列应用某个条件,但这个列并不包含文本,仍然需要把值转换为String

学新通

查询中对数据排序

如果想在应用中以某种特定的顺序显示数据,可以使用查询按某个列对数据排序。

有些情况下这会很有用,例如,可能希望按字母顺序显示饮品名。

如果希望按NAME的升序顺序从NAME和FAVORITE列获取数据,可以使用以下代码:

学新通

ASC关键字表示希望按这一列的升序排序。默认地,列都会按升序排序,所以如果你确实希望按升序排序,就可以忽略ASC。不过,如果要按降序对数据排序,就必须使用DESC

还可以根据多个列排序。例如,可以如下按FAVORITE的降序排序,然后按NAME的升序排序:

学新通

在查询中使用SQL函数

 可以在查询中使用SQL函数。利用SQL函数,可以获得另外的一些信息,如表中行数、一列的平均值或者最大值:

下面给出的是一些常用指令:

AVG() 平均值
COUNT() 行数
SUM() 总和
MAX() 最大值
MIN() 最小值

来看一个例子,如果想统计DRINK表中有多少种饮品,可以使用SQL COUNT()函数来统计_id列中值的个数:

学新通

 如果DRINK表包含一个额外的PRICE列,其中提供了每个饮品的价格,那么还可以使用SQL AVG()函数得出PRICE列的平均值,从而得到平均饮品价格:

学新通

SQL GROUD BY和HAVING子句

如果熟悉SQL的GROUP BY和HAVING子句,可以在query()方法的第5个和第6个参数中使用这两个子句。
这个不熟悉,具体的SQL语句 需要系统的学习

1.5 获取数据库的引用

1.5.1 获取数据库的引用

前面已经知道了,利用游标从数据库读取数据。

query()方法在SQLiteDatabase类中定义,这说明,要调用这个方法,需要首先得到Starbuzz数据库的一个引用。SQLiteOpenHelper类实现了两个方法来完成这个工作:getReadableDatabase()getWritableDatabase()。这两个方法都会返回一个类型为SQLiteDatabase的对象,可以利用这个对象访问数据库。这些方法如下调用:

学新通

以及

学新通

1.5.3 getReadableDatabase()与getWritableDatabase()

实际上,大多数情况下,getReadableDatabase()和getwritableDatabase()都返回同一个数据库对象的引用这个数据库对象可以用来读写数据库的数据。那么既然getReadableDatabase()方法会像getWriteableDatabase()方法一样返回同样的对象,为什么还要有这样一个方法呢?

getReadableDatabase()和getWritableDatabase()方法的关键区别在于:如果无法写数据库,调用这两个方法时就会发生不同的情况。例如,如果磁盘已满,就无法写数据库了。在这种情况下,如果使用getwritableDatabase ()方法,这个方法会失败,并抛出一个SQLiteException异常。不过,如果使用getReadableDatabase()方法,这个方法会尝试得到数据库的一个只读引用。甚至以只读方式都无法访问数据库时,它也会抛出一个SQLiteException异常。

如果只需要从数据库读取数据,最好使用getReadableDatabase()方法。如果需要写数据库,则要使用getwritableDatabase()方法。

学新通

1.6 游标相关讲解

1.6.1 获取游标的代码

 把所有这些集成在一起,下面给出获得游标的代码。后面将在活动的oncreate0方法中使用这个代码。

学新通

这个代码的执行流程:

 学新通

1.6.2 从游标读取记录,首先需要导航到这个记录

要从一个游标获取某个特定记录的值,首先需要导航到这个记录。不论游标返回多少个记录,都要首先做到这一点

学新通

1.6.3 导航游标

有4个主要方法可以用来在游标包含的记录间导航,这些方法分别是moveToFirst( )、moveToLast()、moveToPrevious()和moveToNext()。

这些方法都是负责导航游标的,如果找到记录,那么方法就会返回一个true值。

一旦导航到游标中的某个记录,就可以访问它的值了。下一页会看到如何访问记录的值。

1.6.4 获取游标值

一旦移动到游标中的某个记录,可以从这个记录获取值,在活动的视图中显示这些值。从游标中的当前记录获取值时,要使用游标的get*()方法。具体使用什么方法取决于要获取的值的类型

例如,getString()方法将某一列的值作为string返回,getInt()方法将一列的值作为int返回。每个get*()方法都需要一个指定列索引的参数。

例如,下面给出一个创建游标的查询:

学新通

 这个游标有3列:NAME、DESCRIPTION和IMAGE_RESOURCE_ID。前两列NAME和DESCRIPTION包含string类型的数据。第3列IMAGE_RESOURCE_ID包含int类型的数据。

假设你想得到当前记录的NAME列的值。NAME是游标中的第1列,包含String值。囚此要用getstring()方法得到NAME列的内容,如下所示:

学新通

学新通

 类似地,假设你想得到IMAGE_RESOURCE_ID列的内容。这是游标中的第3列,包含int值,所以要使用以下代码:

学新通

最后,需要关闭游标和数据库

 一旦从游标得到值,就要关闭游标和数据库来释放它们的资源。为此要调用游标和数据库的close()方法:

学新通

1.7 改造DrinkActivity

对DrinkActivity代码进行改进,使用数据库进行操作。

  1.  
    package com.hfad.starbuzz;
  2.  
     
  3.  
    import androidx.appcompat.app.AppCompatActivity;
  4.  
     
  5.  
    import android.content.Intent;
  6.  
    import android.database.Cursor;
  7.  
    import android.database.sqlite.SQLiteDatabase;
  8.  
    import android.database.sqlite.SQLiteException;
  9.  
    import android.database.sqlite.SQLiteOpenHelper;
  10.  
    import android.os.Bundle;
  11.  
    import android.widget.ImageView;
  12.  
    import android.widget.TextView;
  13.  
    import android.widget.Toast;
  14.  
     
  15.  
    public class DrinkActivity extends AppCompatActivity {
  16.  
     
  17.  
    public static final String EXTRA_DRINKNO="drinkNo";
  18.  
     
  19.  
    @Override
  20.  
    protected void onCreate(Bundle savedInstanceState) {
  21.  
    super.onCreate(savedInstanceState);
  22.  
    setContentView(R.layout.activity_drink);
  23.  
    //从意图中获取指定Drink
  24.  
    int drinkNo=(Integer)getIntent().getExtras().get(EXTRA_DRINKNO);
  25.  
     
  26.  
    //创建一个游标Cursor
  27.  
    try{
  28.  
    SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this);
  29.  
    SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase();
  30.  
    Cursor cursor = db.query ("DRINK",
  31.  
    new String[] {"NAME", "DESCRIPTION", "IMAGE_RESOURCE_ID"},
  32.  
    "_id = ?",
  33.  
    new String[] {Integer.toString(drinkNo)},
  34.  
    null, null,null);
  35.  
    if (cursor.moveToFirst()){//游标中只有一个记录,不过仍然先要移动到这个记录
  36.  
    //分别获取数据库中的NAME,DESCRIPTION和IMAGE_RESOURCE_ID
  37.  
    String nameText=cursor.getString(0);
  38.  
    String descriptionText=cursor.getString(1);
  39.  
    int photoId=cursor.getInt(2);
  40.  
     
  41.  
    ///Populate the drink name
  42.  
    TextView name = (TextView)findViewById(R.id.name);
  43.  
    name.setText(nameText);
  44.  
     
  45.  
    //Populate the drink description
  46.  
    TextView description = (TextView)findViewById(R.id.description);
  47.  
    description.setText(descriptionText);
  48.  
     
  49.  
    //Populate the drink image
  50.  
    ImageView photo = (ImageView)findViewById(R.id.photo);
  51.  
    photo.setImageResource(photoId);
  52.  
    photo.setContentDescription(nameText);
  53.  
    }
  54.  
    cursor.close();
  55.  
    db.close();
  56.  
    }catch (SQLiteException e){
  57.  
    Toast toast=Toast.makeText(this,"Database unavailable",Toast.LENGTH_SHORT);
  58.  
    toast.show();
  59.  
    }
  60.  
     
  61.  
    }
  62.  
    }
学新通

1.8 改造DrinkCategoryActivity

1.8.1 目前的DrinkCategoryActivity

学新通

1.8.2 替换ListView中的数组数据-CursorAdapter适配器

 对于从数据库中获取数据并到ListView中显示,适配器采用CursorAdapter适配器

学新通

 CursorAdapter与ArrayAdapter很类似,只不过不是从数组获得数据,而是从一个游标读取数据

1.8.3 CursorAdapter只读取需要的数据

下面假装我们的数据库非常庞大。例如,假设Starbuzz为满足“潮人们”的需求大大扩充了精品咖啡的品种。不再只是之前的3种咖啡,现在可以提供加强浓缩咖啡、奶和各种麦片糖酥的不同组合,这意味着现在我们要在数据库中存储30O种饮品。不过列表中一次只能看到很少的一部分。

ListView一次只能显示有限的列表项。在一个小设备上,开始时可能只会显示前11种咖啡。如果使用数组,就必须从数据库将全部300种咖啡读入数组,然后才能在屏幕上显示。

使用CursorAdapter的做法则有所不同。

1.在屏幕上显示ListView。

第一次显示列表时,它会调整大小来适应屏幕。假设列表的空间可以显示5项。

学新通

2. ListView向适配器靖求前5项。

ListView不知道数据来自哪里,不论是数组还是数据库,不过它知道适配器会提供数据。所以、它向适配器发出请求要求得到前5个饮品。

学新通

 3.CorsorAdapter要求它的游标从数据库读取5行。

CursorAdapter在构造时会得到一个游标,它只在需要时才向这个游标请求数据。

学新通

4. 游标从数据库读取前5行。

尽管数据库表包含300行,但是游标只需要读取前5行。这样就高效得多,而且这说明屏幕可以更快地显示数据。

学新通

5.用户滚动列表。

用户滚动列表时,CursorAdapter要求游标从数据库读取更多行。如果用户只是稍稍向下滚动列表,只新增一项,游标就从数据库再多读1行。

学新通

 所以CursorAdapter会比ArrayAdapter高效得多。它只读取真正需要的数据。这说明,它的速度更快,而且占用的内存更少,而速度和内存都是需要特别注意的重要方面。

1.9 SimpleCursorAdapter将数据映射到视图

我们要创建一个简单游标适配器在应用中使用。SimpleCursorAdapter是CursorAdapter的一个实现,如果需要在一个列表视图中显示游标数据,大多数情况下都可以使用SimpleCursorAdapter。它从游标得到列数据,并把列映射到TextView或Imageview。

学新通 

在这里,我们希望在DrinkCategoryActivity列表视图中显示一个饮品名列表,所以使用一个简单游标适配器将各个饮品的名字分别映射到列表视图中的一个文本视图。

1.9.1 创建游标

创建游标来使用游标适配器时,首先要考虑这个游标需要包含哪些列。游标应当包含列表视图中要显示的所有列,以及_id列。这里必须包含_id列,否则游标适配器将无法工作。为什么会这样呢?

在第11章中我们提到过,Android有一个约定,要求表中的主键列命名为_id。这对于Android非常重要,所以游标适配器认为肯定有这一列,并用这一列唯一地标识游标中的各行。对一个列表视图使用游标适配器时,列表视图会用这一列来标识用户单击了哪一行。

使用游标适配器显示饮品名时,游标必须包含_idNAME列,如下所示:
学新通

1.9.2 创建SimpleCursorAdapter

 要创建一个简单游标适配器,需要告诉它你希望如何显示数据,要使用哪一个游标,以及哪些列要映射到哪些视图。下面会创建一个简单游标适配器来显示一个饮品名列表:

学新通

 就像使用数组适配器时一样,我们要用android.R.layout .simple_list_item_1告诉Android我们希望在列表视图中将游标中的各行显示为一个文本视图。这个文本视图的ID为android.R.id.text1。

SimpleCursorAdapter构造函数的一般形式如下:

学新通

 构造函数的参数讲解:

  • context是当前上下文
  • layout指定希望如何显示数据。这里不再指定需要从哪个数组获取数据
  • 使用cursor参数指定哪个游标包含数据。
  • fromColumns指定希望使用游标中的哪些列
  • toviews指定希望在哪些视图中显示这些列。
  • flags参数通常设置为0,这是默认值。另一种候选做法是将它设置为FLAG__ REGISTER_CONTENT_ OBSERVER,这会注册一个内容观察者,内容发生改变时就会得到通知。这里不介绍这种做法,因为这可能会导致内存泄漏。

context和layout参数与创建数组适配器时使用的参数完全相同。

1.9.3 关闭游标和数据库

这一章前面介绍游标时,我们说过,使用完之后要关闭游标和数据库来释放它们的资源。在我们的DrinkActivity代码中,我们使用了一个游标从数据库获取饮品的详细信息,一旦在视图中使用了这些值,就立即关闭游标和数据库。
使用游标适配器时,做法稍有些不同游标适配器需要游标保持打开状态,因为它有可能要从游标获取更多数据。如果用户向下滚动列表视图的项列表,需要查看更多的数据,就会发生这种情况。
学新通

 这意味着,一旦使用setAdapter()方法将游标与列表视图连接,就不能立即关闭游标和数据库。实际上,可以使用活动的onDestroy()方法来关闭游标和数据库。由于活动将被撤销,所以不再需要游标或数据库连接,因此这个时候可以将它们关闭:

学新通

1.10 修改后的DrinkCategoryActivity

将数组适配器修改为游标适配器后:

  1.  
    package com.hfad.starbuzz;
  2.  
     
  3.  
    import androidx.appcompat.app.AppCompatActivity;
  4.  
     
  5.  
    import android.app.ListActivity;
  6.  
    import android.content.Intent;
  7.  
    import android.database.Cursor;
  8.  
    import android.database.sqlite.SQLiteDatabase;
  9.  
    import android.database.sqlite.SQLiteException;
  10.  
    import android.database.sqlite.SQLiteOpenHelper;
  11.  
    import android.os.Bundle;
  12.  
    import android.view.View;
  13.  
    import android.widget.ArrayAdapter;
  14.  
    import android.widget.CursorAdapter;
  15.  
    import android.widget.ListView;
  16.  
    import android.widget.SimpleCursorAdapter;
  17.  
    import android.widget.Toast;
  18.  
     
  19.  
    public class DrinkCategoryActivity extends ListActivity {
  20.  
     
  21.  
    //增加这两个私有变量,以便在onDestory()方法中关闭数据库和游标。
  22.  
    private SQLiteDatabase db;
  23.  
    private Cursor cursor;
  24.  
     
  25.  
    @Override
  26.  
    protected void onCreate(Bundle savedInstanceState) {
  27.  
    super.onCreate(savedInstanceState);
  28.  
    ListView listDrinks=getListView();
  29.  
    try{
  30.  
    SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this);
  31.  
    db=starbuzzDatabaseHelper.getReadableDatabase();
  32.  
    //创建游标
  33.  
    cursor=db.query("DRINK",
  34.  
    new String[]{"_id","NAME"},
  35.  
    null,null,null,null,null);
  36.  
    //创建游标适配器
  37.  
    CursorAdapter listAdapter=new SimpleCursorAdapter(this,
  38.  
    android.R.layout.simple_list_item_1,
  39.  
    cursor,
  40.  
    new String[]{"NAME"},
  41.  
    new int[]{android.R.id.text1},
  42.  
    0);
  43.  
    //使用适配器
  44.  
    listDrinks.setAdapter(listAdapter);
  45.  
    }catch (SQLiteException e){
  46.  
    Toast toast=Toast.makeText(this,"Database Unavaiable",Toast.LENGTH_SHORT);
  47.  
    toast.show();
  48.  
    }
  49.  
    }
  50.  
     
  51.  
    @Override
  52.  
    protected void onListItemClick(ListView l, View v, int position, long id) {
  53.  
    Intent intent=new Intent(DrinkCategoryActivity.this,DrinkActivity.class);
  54.  
    intent.putExtra(DrinkActivity.EXTRA_DRINKNO,(int)id);
  55.  
    startActivity(intent);
  56.  
     
  57.  
    }
  58.  
     
  59.  
    @Override
  60.  
    protected void onDestroy() {
  61.  
    super.onDestroy();
  62.  
    cursor.close();
  63.  
    db.close();
  64.  
    }
  65.  
    }
学新通

 运行应用:

学新通

 不过,现在数据是从数据库读取的。实际上,现在可以删除Drink.java代码,因为不再需要饮料数组。我们需要的所有数据现在都来自数据库。

下面需要对应用做出修改,让应用更新数据库中的数据

2.应用更新数据库中的数据

2.1 将重要信息放在顶层活动中

顶层活动的设计需要仔细考虑,因为这是用户看到的第一个界面。理想情况下,它应当包含对新用户和原有用户都有用的内容。要达到这个目的,一种办法是考虑你的用户希望应用里有什么,然后为用户提供相应的途径让他们能够在前端屏幕上完成他们想要的功能。例如,如果你在设计一个应用要播放音乐,可能希望把用户最近播放的播放列表放在顶层活动中,这样用户就能很容易地找到这些播放列表。

我们要修改Starbuzz的顶层活动,在顶层活动中增加用户最喜爱的饮品,并允许用户单击这些链接直接查看他们选择的饮品。

学新通

2.2 在DrinkActivity中增加我的最爱

2.2.1 修改DrinkActivity中的布局

 在第11章中,我们为Starbuzz数据库中的DRINK表增加了一个FAVORITE列。我们将用这个列让用户指定某个饮品是不是他的最爱,这样我们就能知道要在TopLevelActivity中显示哪些饮品。要让用户在DrinkActivity中编辑饮品,因为这个活动会显示饮品的详细信息。

为此,需要为activity_drink..xml增加一个新视图,用来编辑和显示FAVORITE列的值。布局中使用的视图类型取决于要用它表示什么类型的数据。我们需要一个视图允许用户选择true/false值,所以这里要使用一个复选框。

首先要在activity_drink.xml增加复选框。

  1.  
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.  
    xmlns:tools="http://schemas.android.com/tools"
  3.  
    android:layout_width="match_parent"
  4.  
    android:layout_height="match_parent"
  5.  
    android:orientation="vertical"
  6.  
    tools:context="com.hfad.starbuzz.DrinkActivity" >
  7.  
     
  8.  
    ...
  9.  
     
  10.  
    <CheckBox
  11.  
    android:id="@ id/favorite"
  12.  
    android:layout_width="wrap_content"
  13.  
    android:layout_height="wrap_content"
  14.  
    android:text="@string/favorite"
  15.  
    android:onClick="onFavoriteClicked"/>
  16.  
     
  17.  
    </LinearLayout>
学新通

2.2.2 为游标增加一个新列

接下来要修改DrinkActivity代码,让favorite复选框显示数据库中FAVORITE列的值。

就像这个活动中的其他视图一样,可以用同样的方法获取FAVORITE列的值,只需要把FAVORITE列增加到游标中。这样一来,我们可以从游标获取FAVORITE列的值,再把复选框的值设置为这个值,从而修改数据库信息

下面是oncreate()方法中与此相关的部分:

学新通

这就可以 确保复选框显示FAVORITE列的值。

接下来要单击这个复选框时更新数据库。

2.2.3 响应单击事件来更新数据库

为activity_drink.xml增加复选框时,我们将android:onclick属性设置为onFavoriteclicked()。这说明,只要点击这个复选框,就会调用这个活动中的onFavoriteclicked ()方法。要让这个方法用复选框的当前值更新数据库。如果用户选中或取消选中这个复选框,就会调用onFavoriteClicked()方法,用户所做的修改将保存到数据库中

在第11章中,你已经看到如何使用SQLiteDatabase方法修改SQLite数据库中保存的数据。另外也了解了如何使用insert()方法插入数据,如何使用delete()方法删除数据,以及如何使用update()方法更新现有的记录。

可以在活动中使用这些方法修改数据。例如,可以使用insert()方法在DRINK表中增加新的饮品记录,或者使用delete()方法删除记录。在这里,我们希望用复选框的当前值更新DRINK表的FAVORITE列,这可以使用update()方法来完成。

复习一下,update()方法有以下形式:

学新通

这里table是想要更新的表的名字,values是一个Contentvalues对象,其中包含想要更新的列以及想设置的值构成的名/值对。whereclause和whereArgs参数指定希望更新哪些记录。

2.2.4 DrinkActivity代码

对DrinkeActivity代码进行修改:

  1.  
    package com.hfad.starbuzz;
  2.  
     
  3.  
    import androidx.appcompat.app.AppCompatActivity;
  4.  
     
  5.  
    import android.content.ContentValues;
  6.  
    import android.content.Intent;
  7.  
    import android.database.Cursor;
  8.  
    import android.database.sqlite.SQLiteDatabase;
  9.  
    import android.database.sqlite.SQLiteException;
  10.  
    import android.database.sqlite.SQLiteOpenHelper;
  11.  
    import android.os.Bundle;
  12.  
    import android.view.View;
  13.  
    import android.widget.CheckBox;
  14.  
    import android.widget.ImageView;
  15.  
    import android.widget.TextView;
  16.  
    import android.widget.Toast;
  17.  
     
  18.  
    public class DrinkActivity extends AppCompatActivity {
  19.  
     
  20.  
    public static final String EXTRA_DRINKNO="drinkNo";
  21.  
     
  22.  
    @Override
  23.  
    protected void onCreate(Bundle savedInstanceState) {
  24.  
    super.onCreate(savedInstanceState);
  25.  
    setContentView(R.layout.activity_drink);
  26.  
    //从意图中获取指定Drink
  27.  
    int drinkNo=(Integer)getIntent().getExtras().get(EXTRA_DRINKNO);
  28.  
     
  29.  
    //创建一个游标Cursor
  30.  
    try{
  31.  
    SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this);
  32.  
    SQLiteDatabase db = starbuzzDatabaseHelper.getWritableDatabase();
  33.  
    Cursor cursor=db.query("DRINK",
  34.  
    new String[]{"NAME","DESCRIPTION","IMAGE_RESOURCE_ID","FAVORITE"},//为游标增加FAVORITE列
  35.  
    "_id=?",
  36.  
    new String[]{Integer.toString(drinkNo)},
  37.  
    null,null,null);
  38.  
    if (cursor.moveToFirst()){//游标中只有一个记录,不过仍然先要移动到这个记录
  39.  
    //分别获取数据库中的NAME,DESCRIPTION和IMAGE_RESOURCE_ID
  40.  
    String nameText=cursor.getString(0);
  41.  
    String descriptionText=cursor.getString(1);
  42.  
    int photoId=cursor.getInt(2);
  43.  
    boolean isFavorite=(cursor.getInt(3)==1);//得到FAVORITE列的值,它存储在数据库中,1表示true,0表示false
  44.  
    ///Populate the drink name
  45.  
    TextView name = (TextView)findViewById(R.id.name);
  46.  
    name.setText(nameText);
  47.  
     
  48.  
    //Populate the drink description
  49.  
    TextView description = (TextView)findViewById(R.id.description);
  50.  
    description.setText(descriptionText);
  51.  
     
  52.  
    //Populate the drink image
  53.  
    ImageView photo = (ImageView)findViewById(R.id.photo);
  54.  
    photo.setImageResource(photoId);
  55.  
    photo.setContentDescription(nameText);
  56.  
     
  57.  
    //Populate the favorite checkbox
  58.  
    CheckBox favorite = (CheckBox) findViewById(R.id.favorite);
  59.  
    favorite.setChecked(isFavorite);
  60.  
    }
  61.  
    cursor.close();
  62.  
    db.close();
  63.  
    }catch (SQLiteException e){
  64.  
    Toast toast=Toast.makeText(this,"Database unavailable",Toast.LENGTH_SHORT);
  65.  
    toast.show();
  66.  
    }
  67.  
     
  68.  
    }
  69.  
     
  70.  
    public void onFavoriteClicked(View view){
  71.  
    int drinkNo=(Integer)getIntent().getExtras().get("drinkNo");
  72.  
    CheckBox favorite=(CheckBox)findViewById(R.id.favorite);
  73.  
    ContentValues drinkValues=new ContentValues();
  74.  
    drinkValues.put("FAVORITE",favorite.isChecked());//将favorite复选框的值
  75.  
    SQLiteOpenHelper stazbuzzDatabaseHelper=new StarbuzzDatabaseHelper(DrinkActivity.this);
  76.  
    try {
  77.  
    SQLiteDatabase db=stazbuzzDatabaseHelper.getWritableDatabase();
  78.  
    db.update("DRINK",drinkValues,
  79.  
    "_id=?",new String[]{Integer.toString(drinkNo)});
  80.  
    db.close();
  81.  
    }catch (SQLiteException e){
  82.  
    Toast toast=Toast.makeText(this,"Database unavailable",Toast.LENGTH_SHORT);
  83.  
    toast.show();
  84.  
    }
  85.  
    }
  86.  
    }
学新通

2.3 在TOPLevelActivity中显示用户的最爱

2.3.1 需求实现

最后还需要在TopLevelActivity中显示用户最喜爱的饮品。

需要在布局中增加一个新的ListView。
这会显示用户最喜爱的饮品列表。

需要填充ListView。
我们要用数据库中用户最喜爱的饮品填充这个列表。

需要让ListView响应单击事件。
如果用户点击某个最喜爱的饮品,我们要在DrinkActivity中显示这个饮品的详细信息。

学新通

2.3.2 在activity_top_level.xml中显示用户最喜爱的饮品

要为activity_top_level.xml中增加一个列表视图,将用这个列表视图显示用户最喜爱的饮品列表。另外还要增加一个文本视图显示这个列表的标题。

  1.  
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.  
    xmlns:tools="http://schemas.android.com/tools"
  3.  
    android:layout_width="match_parent"
  4.  
    android:layout_height="match_parent"
  5.  
    android:orientation="vertical"
  6.  
    tools:context=".TopLevelActivity" >
  7.  
     
  8.  
    <ImageView android:layout_width="200dp"
  9.  
    android:layout_height="100dp"
  10.  
    android:src="@drawable/starbuzz_logo"
  11.  
    android:contentDescription="@string/starbuzz_logo" />
  12.  
     
  13.  
    <ListView android:id="@ id/list_options"
  14.  
    android:layout_width="match_parent"
  15.  
    android:layout_height="wrap_content"
  16.  
    android:entries="@array/options" />
  17.  
     
  18.  
    <TextView
  19.  
    android:layout_width="wrap_content"
  20.  
    android:layout_height="wrap_content"
  21.  
    android:layout_margin="50dp"
  22.  
    android:textAppearance="?android:attr/textAppearanceLarge"
  23.  
    android:text="@string/favorites"/>
  24.  
     
  25.  
    <ListView
  26.  
    android:id="@ id/list_favorites"
  27.  
    android:layout_width="match_parent"
  28.  
    android:layout_height="wrap_content"/>
  29.  
     
  30.  
    </LinearLayout>
学新通

2.3.3 TopLevelActivity.java所需的修改

接下来需要在刚增加的列表视图中显示用户最喜爱的饮品,并让列表视图响应单击事件。为此,我们需要完成以下工作:

1.需要创建一个游标来填充ListView。

游标将返回FAVORITE列设置为1的所有饮品,也就是用户标志为最爱的所有饮品。类似于DrinkCategoryActivity代码中的做法,可以使用一个cursorAdapter将游标连接到这个Listview。

学新通

2.需要创建一个onltemCliekListener,让ListView可以响应单击事件。

如果用户单击了某个最喜爱的饮品,可以创建一个意图启动DrinkActivity,传入刚单击的饮品的ID。这会为用户显示他们刚选择的饮品的详细信息。

学新通

2.3.4 新的顶层活动代码

  1.  
    package com.hfad.starbuzz;
  2.  
     
  3.  
    import androidx.appcompat.app.AppCompatActivity;
  4.  
     
  5.  
    import android.content.Intent;
  6.  
    import android.database.Cursor;
  7.  
    import android.database.sqlite.SQLiteDatabase;
  8.  
    import android.database.sqlite.SQLiteException;
  9.  
    import android.database.sqlite.SQLiteOpenHelper;
  10.  
    import android.os.Bundle;
  11.  
    import android.view.View;
  12.  
    import android.widget.AdapterView;
  13.  
    import android.widget.CursorAdapter;
  14.  
    import android.widget.ListView;
  15.  
    import android.widget.SimpleCursorAdapter;
  16.  
    import android.widget.Toast;
  17.  
     
  18.  
    public class TopLevelActivity extends AppCompatActivity {
  19.  
     
  20.  
    private SQLiteDatabase db;
  21.  
    private Cursor favoritesCursor;
  22.  
     
  23.  
    @Override
  24.  
    protected void onCreate(Bundle savedInstanceState) {
  25.  
    super.onCreate(savedInstanceState);
  26.  
    setContentView(R.layout.activity_top_level);
  27.  
    //下面的代码 是用于填充options列表视图,并让这个列表视图响应单击事件。
  28.  
    //创建事件监听器
  29.  
    AdapterView.OnItemClickListener itemClickListener=new AdapterView.OnItemClickListener() {
  30.  
    @Override
  31.  
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  32.  
    switch (position){
  33.  
    case 0:{
  34.  
    Intent intent=new Intent(TopLevelActivity.this,DrinkCategoryActivity.class);
  35.  
    startActivity(intent);
  36.  
    }
  37.  
    }
  38.  
    }
  39.  
    };
  40.  
    //为ListView设置 事件监听器
  41.  
    ListView listView=(ListView)findViewById(R.id.list_options);
  42.  
    listView.setOnItemClickListener(itemClickListener);
  43.  
     
  44.  
    //从游标中发布list_favorites ListView
  45.  
    ListView listFavorites=(ListView)findViewById(R.id.list_favorites);
  46.  
    try{
  47.  
    SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this);
  48.  
    db = starbuzzDatabaseHelper.getWritableDatabase();
  49.  
    favoritesCursor=db.query("DRINK",
  50.  
    new String[]{"_id","NAME"},
  51.  
    "FAVORITE=1",
  52.  
    null,null,null,null);
  53.  
    CursorAdapter favoriteAdapter=new SimpleCursorAdapter(TopLevelActivity.this,
  54.  
    android.R.layout.simple_list_item_1,
  55.  
    favoritesCursor,
  56.  
    new String[]{"NAME"},
  57.  
    new int[]{android.R.id.text1},0);
  58.  
    listFavorites.setAdapter(favoriteAdapter);
  59.  
    }catch (SQLiteException e){
  60.  
    Toast toast=Toast.makeText(this,"Database unavailable",Toast.LENGTH_SHORT);
  61.  
    toast.show();
  62.  
    }
  63.  
     
  64.  
    //如果用户单击了favorites ListView 中的某一项,会创建一个意图启动DrinkActivity并传入这个饮品的ID
  65.  
    listFavorites.setOnItemClickListener(new AdapterView.OnItemClickListener() {
  66.  
    @Override
  67.  
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
  68.  
    Intent intent=new Intent(TopLevelActivity.this,DrinkActivity.class);
  69.  
    intent.putExtra(DrinkActivity.EXTRA_DRINKNO,(int)id);
  70.  
    startActivity(intent);
  71.  
    }
  72.  
    });
  73.  
     
  74.  
    }
  75.  
     
  76.  
    @Override
  77.  
    protected void onDestroy() {
  78.  
    super.onDestroy();
  79.  
    favoritesCursor.close();
  80.  
    db.close();
  81.  
    }
  82.  
    }
学新通

运行代码,发现问题

学新通

 而在旋转之后,这个屏幕就恢复了。

2.4 游标不会自动刷新与解决办法

2.4.1 游标不会自动刷新的原因

如果用户导航到DrinkActivity来选择一个新的最爱饮品,这个新的最爱饮品不会自动显示在TopLevelActivity的favorites列表视图中。这是因为,游标只在创建时获取数据。在这里,游标在活动的onCreate()方法中创建,所以只会在创建活动时获取数据。用户导航到其他活动时,TopLevelActivity会停止,而不是撤销和重新创建

学新通

 游标不会自动跟踪数据库中的底层数据是否改变。如果底层数据在游标创建之后改变,游标不会更新。它仍包含原来的记录,而不包含任何改变

学新通

2.4.2 解决办法-用changeCursor()修改游标

要解决这个问题,可以在用户返回TopLevelActivity时将favorites列表视图使用的底层游标改为一个新版本。如果在活动的onRestart()方法中完成这个工作,用户返回到TopLevelActivity时,ListView中的数据就会刷新。用户选择的所有新的最爱饮品都会显示,而不再标志为最爱的饮品则会从列表中去除。

为此,可以使用cursorAdapter changeCursor()方法。changeCursor()方法将游标适配器当前使用的游标替换为一个新游标,并关闭原来的游标。下面给出这个方法:

学新通

changeCursor方法有一个参数,即新游标。下面是实际代码的一个例子:

学新通

2.4.3 修改后的TopLevelActivity代码

  1.  
    public class TopLevelActivity extends AppCompatActivity {
  2.  
     
  3.  
    //其他不变
  4.  
     
  5.  
    @Override
  6.  
    protected void onRestart() {
  7.  
    super.onRestart();
  8.  
    try{
  9.  
    SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this);
  10.  
    db = starbuzzDatabaseHelper.getWritableDatabase();
  11.  
    //与前面一样,用同样的方法创建一个新游标
  12.  
    Cursor newCursor=db.query("DRINK",
  13.  
    new String[]{"_id","NAME"},
  14.  
    "FAVORITE=1",
  15.  
    null,null,null,null);
  16.  
    ListView listFavorites = (ListView) findViewById(R.id.list_favorites);
  17.  
    CursorAdapter adapter=(CursorAdapter)listFavorites.getAdapter();//得到列表视图的适配器
  18.  
    adapter.changeCursor(newCursor);//将游标适配器当前使用的游标替换为一个新的游标
  19.  
    favoritesCursor=newCursor;
  20.  
    }catch (SQLiteException e){
  21.  
    Toast toast=Toast.makeText(this,"Database unavailable",Toast.LENGTH_SHORT);
  22.  
    toast.show();
  23.  
    }
  24.  
    }
  25.  
     
  26.  
    @Override
  27.  
    protected void onDestroy() {
  28.  
    super.onDestroy();
  29.  
    favoritesCursor.close();
  30.  
    db.close();
  31.  
    }
  32.  
    }
学新通

运行之后,问题解决了。

3.线程合作:AsyncTask

3.1 需求引入

数据库会让应用变得很慢很慢。

想想看应用打开一个数据库时要做什么。它首先要在闪存中搜索查找数据库文件。如果没有找到数据库文件,就需要创建一个空的数据库。然后它要运行所有SQL命令,在数据库中创建数据库表和需要的所有初始数据。最后,还要执行一些查询从数据库得到数据。

这是很耗费时间的,对于Starbuzz应用中使用的这样一个很小的数据库,这不需要太长时间。但是随着数据库变得越来越大,这个时间会不断增加。也许你还没有意识到,不过你的应用已经逐渐失去效力,甚至变得比感恩节慢如牛的YouTube还要慢。

对于创建和读取数据库的速度,我们没有多少可以做的,不过确实可以采取一些措施防止界面反应变慢。

线程合作会让生活更美好

3.2 线程类型介绍

访问一个很慢的数据库时,有一个重要的问题:这会让你的应用看起来好像没有响应。要理解这是为什么,需要先考虑Android中线程如何工作

从Lollipop版本开始,要考虑3种不同类型的线程:

1.主事件线程

这是Android的工作主力。它会监听意图,接收来自屏幕的触摸消息,另外还会调用活动的所有方法。

2.呈现线程

通常你不需要与这个线程交互,不过它会读取关于屏幕更新的一个请求列表,然后调用底层图形硬件重绘屏幕,让应用看起来很漂亮

3.你创建的所有其他线程。

如果不细心,应用的几乎所有工作都可能会在主事件线程中完成。为什么?

因为这是运行事件方法的主事件线程。如果把数据库代码放在onCreate()方法中(就像Starbuzz应用中一样),主事件线程就会忙于与数据库交互,而不会迅速应对来自屏幕或其他应用的事件。如果你的数据库代码要花很长时间运行,用户就会觉得应用没有响应,自己被忽视了。

所以这里的技巧将数据库代码从主事件线程移出来,在后台的一个定制线程中运行

建立界面:主事件线程

与数据库交互:后台线程

用数据库数据更新视图:主事件线程

3.2 明确代码运行的线程

应用中使用数据库时,最好在后台线程中运行数据库代码,而在主事件线程中用数据库数据更新视图。我们会分析DrinkActivity代码中的onFavoritesclicked()方法(复选框中的选项被点击了,则会被触发的函数,这会引起数据库中数据内容的更新),让你了解如何解决这一类问题。

学新通

 这个方法每个部分代码对应的作用:

1.需要在数据库代码之前运行的代码。

前几行代码要得到favorite复选框的值,把它放在drinkvalues Contentvalues对象中。这个代码必须在数据库代码之前运行。

2.需要在后台线程中运行的数据库代码。

这会更新DRINK表。

3.需要在数据库代码之后运行的代码。

如果数据库不可用,我们希望向用户显示一个消息。这必须在主事件线程中运行。

3.3 完成异步任务 AsyncTask

3.3.1 AsyncTask与常用方法

AsyncTask类允许你在后台完成操作。这些操作运行结束时,就可以在主事件线程中更新视图。如果任务是重复性的,甚至可以利用这个类发布任务运行的进度

学新通 

要创建AsyncTask,需要扩展AsyncTask类,并实现它的doInBackground()方法。这个方法中的代码会在后台线程中运行,所以非常适合把数据库代码放在这里。AsyncTask类还有一个onPreExecute()方法,这个方法在doInBackground()之前运行,另外有一个onPostExecute()方法在doInBackground()之后运行。如果需要发布任务进度还有一个onProgressUpdate()方法。

下面来看这个类:

学新通

 AsyncTask由3个泛型参数定义:Params,Progress和Results。

  1. Params是一个对象类型,用来向doInBackground()方法传递任务参数。
  2. Progress也是一个对象类型,用来指示任务进度。
  3. Result是任务结果类型。如果不打算使用某个参数,可以将它设置为void。

注:上面这三个参数只是表示的是泛型的一个含义,在实际编写代码的时候,需要具体表示,如:Integer,Boolean等。

3.3.2 onPreExecute()方法

onPreExecute()方法会在后台任务开始之前调用,用来建立任务。onPreExecute()方法在主事件线程调用,所以它可以访问用户界面中的视图。onPreExecute()方法没有参数,返回类型为void。

我们将使用onPreExecute()方法得到favorite复选框的值,把它放在drinkvalues Contentvalues对象中。这是因为,需要访问这个复选框才能得到它的值,而且这个工作必须在运行数据库代码之前完成。我们会使用方法外的另一个属性表示drinkvalues Contentvalues对象,这样一来,这个类的其他方法也可以访问这个对象。

学新通

3.3.3 doInBackground()方法

doInBackground()方法会在onPreExecute()之后立即在后台运行

需要定义任务接收什么类型的参数,另外返回类型是什么。

我们要使用doInBackground()方法执行数据库代码,让它在后台线程中运行。这里要传入需要更新的饮品的ID,另外使用一个Boolean返回值,以便知道代码是否成功运行:

学新通

3.3.4 onProgressUpdate()方法

onProgressUpdate()方法会在主事件线程调用,所以可以访问用户界面中的视图。可以使用这个方法更新屏幕上的视图向用户显示进度。要定义这个方法接收什么类型的参数。

学新通

如果由doInBackground()方法调用publishProgress(),就会运行onProgressUpdate()方法,如下所示:

学新通

在这个应用中,我们没有发布任务的进度,因此不需要实现这个方法。通过改变UpdateDrinkTask的签名,指出没有使用表示任务进度的对象。

学新通

3.3.5 onPostExeceute()方法

后台任务完成后会调用onPostExecute()方法。它在主事件线程中调用,所以可以访问用户界面中的视图。可以使用这个方法为用户呈现任务的结果。要把doInBackground()方法的结果传入onPostExecute()方法,所以参数必须与doInBackground()的返回类型一致。

我们要使用onPostExecute()方法来检查doInBackground()方法中的数据库代码是否成功运行。如果没有,就要向用户显示一个消息。这个工作在onPostExecute()方法中完成,因为这个方法可以更新用户界面;doInBackground()方法在后台线程中运行,所以不能更新视图。
学新通

3.4 AsyncTask类

前面最开始介绍AsyncTask类时,我们说过它由3个泛型参数来定义: Params、Progress和Results。指定这些参数时要查看doInBackground()、onProgressUpdate()和onPostExecute()方法所用参数的类型。Params是doInBackground()的参数类型,Progress是onProgressUpdate()的参数类型,Result是onPostExecute()方法的类型:

学新通

在这个例子中,doInBackground()接受Integer参数,onPostExecute()有一个Boolean参数。这里没有使用onProgressUpdate ()方法。这说明,在这个例子中,Params是Integer, Progress是void,Result是Boolean:

学新通
 

3.5 执行AsyncTask

 要调用AsyncTask的execute()方法来运行任务。如果doInBackground()方法有参数,要把这些参数增加到execute()方法。

例如,我们想把用户选择的饮品传递到AsyncTask的doInBackground()方法,所以可以使用以下代码来调用:

学新通

用execute()方法传递的参数类型必须与AsyncTask doInBackground()方法希望的参数类型一致。我们的doInBackground()方法需要Integer参数,所以要传入整数:

 学新通

 要在DrinkActivity的onFavoritesclicked ()方法中执行Update-DrinkTask。下面给出这个方法:

学新通

 创建AsyncTask时,要把它作为一个内部类增加到使用这个任务的活动中。我们要把UpdateDrinkTask类作为一个内部类增加到DrinkActivity.java,在DrinkActivity的onFavoriteclicked()方法中执行这个任务,这样一来,用户单击favorite复选框时这个任务就会在后台更新数据库

下面来看DrinkActivity代码:

  1.  
    package com.hfad.starbuzz;
  2.  
     
  3.  
    import androidx.appcompat.app.AppCompatActivity;
  4.  
     
  5.  
    import android.content.ContentValues;
  6.  
     
  7.  
    import android.database.Cursor;
  8.  
    import android.database.sqlite.SQLiteDatabase;
  9.  
    import android.database.sqlite.SQLiteException;
  10.  
    import android.database.sqlite.SQLiteOpenHelper;
  11.  
    import android.os.AsyncTask;
  12.  
    import android.os.Bundle;
  13.  
    import android.view.View;
  14.  
    import android.widget.CheckBox;
  15.  
    import android.widget.ImageView;
  16.  
    import android.widget.TextView;
  17.  
    import android.widget.Toast;
  18.  
     
  19.  
    public class DrinkActivity extends AppCompatActivity {
  20.  
     
  21.  
    //将AnsyncTask作为内部类增加到活动中
  22.  
    private class UpdateDrinkTask extends AsyncTask<Integer,Void,Boolean>{
  23.  
     
  24.  
    ContentValues drinkValues;
  25.  
     
  26.  
    /** 在主事件线程中运行,能访问用户界面中的视图
  27.  
    * 数据库代码运行之前,将复选框的值放在drinkValues ContentValues对象中
  28.  
    */
  29.  
    @Override
  30.  
    protected void onPreExecute() {
  31.  
    CheckBox favorite=(CheckBox)findViewById(R.id.favorite);
  32.  
    drinkValues=new ContentValues();
  33.  
    drinkValues.put("FAVORITE",favorite.isChecked());
  34.  
    }
  35.  
     
  36.  
    /**
  37.  
    * 在后台线程中运行数据库的代码
  38.  
    *Boolean返回值,以便知道代码是否成功运行
  39.  
    * drinks是个Integer类型的数组
  40.  
    */
  41.  
    @Override
  42.  
    protected Boolean doInBackground(Integer... drinks) {
  43.  
    int drinkNo=drinks[0];
  44.  
    SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(DrinkActivity.this);
  45.  
    try {
  46.  
    SQLiteDatabase db=starbuzzDatabaseHelper.getWritableDatabase();
  47.  
    db.update("DRINK",drinkValues,
  48.  
    "_id=?",new String[]{Integer.toString(drinkNo)});
  49.  
    db.close();
  50.  
    return true;
  51.  
    }catch (SQLiteException e){
  52.  
    return false;
  53.  
    }
  54.  
    }
  55.  
     
  56.  
    /**
  57.  
    *后台任务完成后会调用onPostExecute()方法。
  58.  
    * 它在主事件线程中调用,可以访问用户界面中的视图。可以使用这个方法为用户呈现任务的结果
  59.  
    * @param success 此处是Boolean 因此onInBackground()返回的是Boolean
  60.  
    */
  61.  
    @Override
  62.  
    protected void onPostExecute(Boolean success) {
  63.  
    if (!success){
  64.  
    Toast toast=Toast.makeText(DrinkActivity.this,"Database unavailable",Toast.LENGTH_SHORT);
  65.  
    toast.show();
  66.  
    }
  67.  
    }
  68.  
    }
  69.  
     
  70.  
     
  71.  
    public static final String EXTRA_DRINKNO="drinkNo";
  72.  
     
  73.  
    @Override
  74.  
    protected void onCreate(Bundle savedInstanceState) {
  75.  
    super.onCreate(savedInstanceState);
  76.  
    setContentView(R.layout.activity_drink);
  77.  
    //从意图中获取指定Drink
  78.  
    int drinkNo=(Integer)getIntent().getExtras().get(EXTRA_DRINKNO);
  79.  
     
  80.  
    //创建一个游标Cursor
  81.  
    try{
  82.  
    SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this);
  83.  
    SQLiteDatabase db = starbuzzDatabaseHelper.getWritableDatabase();
  84.  
    Cursor cursor=db.query("DRINK",
  85.  
    new String[]{"NAME","DESCRIPTION","IMAGE_RESOURCE_ID","FAVORITE"},//为游标增加FAVORITE列
  86.  
    "_id=?",
  87.  
    new String[]{Integer.toString(drinkNo)},
  88.  
    null,null,null);
  89.  
    if (cursor.moveToFirst()){//游标中只有一个记录,不过仍然先要移动到这个记录
  90.  
    //分别获取数据库中的NAME,DESCRIPTION和IMAGE_RESOURCE_ID
  91.  
    String nameText=cursor.getString(0);
  92.  
    String descriptionText=cursor.getString(1);
  93.  
    int photoId=cursor.getInt(2);
  94.  
    boolean isFavorite=(cursor.getInt(3)==1);//得到FAVORITE列的值,它存储在数据库中,1表示true,0表示false
  95.  
    ///Populate the drink name
  96.  
    TextView name = (TextView)findViewById(R.id.name);
  97.  
    name.setText(nameText);
  98.  
     
  99.  
    //Populate the drink description
  100.  
    TextView description = (TextView)findViewById(R.id.description);
  101.  
    description.setText(descriptionText);
  102.  
     
  103.  
    //Populate the drink image
  104.  
    ImageView photo = (ImageView)findViewById(R.id.photo);
  105.  
    photo.setImageResource(photoId);
  106.  
    photo.setContentDescription(nameText);
  107.  
     
  108.  
    //Populate the favorite checkbox
  109.  
    CheckBox favorite = (CheckBox) findViewById(R.id.favorite);
  110.  
    favorite.setChecked(isFavorite);
  111.  
    }
  112.  
    cursor.close();
  113.  
    db.close();
  114.  
    }catch (SQLiteException e){
  115.  
    Toast toast=Toast.makeText(this,"Database unavailable",Toast.LENGTH_SHORT);
  116.  
    toast.show();
  117.  
    }
  118.  
     
  119.  
    }
  120.  
     
  121.  
    public void onFavoriteClicked(View view){
  122.  
    int drinkNo=(Integer)getIntent().getExtras().get("drinkNo");
  123.  
    new UpdateDrinkTask().execute(drinkNo);
  124.  
    }
  125.  
    }
学新通

3.6 AsyncTask步骤小结

学新通

4.总结

  1. 可以利用游标读写数据库。
  2. 可以调用SQLiteDatabasequery0)方法创建游标。在底层,这会建立一个SQL SELECT语句。
  3. getWritableDatabase()方法返回一个SQLiteDatabase对象,允许读写数据库。
  4. getReadableDatabase ()返回一个SQLiteDatabase对象,允许以只读方式访问数据库。也可能可以读写数据库,不过不能保证这一点。
  5. 可以使用moveTo*()方法导航游标。
  6. 可以使用get* ()方法从游标获取值。
  7. 使用完游标和数据库之后要关闭游标和数据库连接。
  8. CursorAdapter是一个使用游标的适配器。可以利用SimpleCursorAdapter用游标返回的值填充ListView。要适当地设计应用,把有用的内容放在顶层活动中。
  9. cursorAdapter  ChangeCursor()方法把游标适配器当前使用的游标替换为你提供的一个新游标。然后关闭原来的游标。
  10. 可以使用AsyncTask在后台线程中运行数据库代码。
     

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgakfga
系列文章
更多 icon
同类精品
更多 icon
继续加载