文档库 最新最全的文档下载
当前位置:文档库 › 关于android的数据存储-SQLite-ContentProvider-preferences

关于android的数据存储-SQLite-ContentProvider-preferences

一章我们将来认识Android 平台是如何对数据进行处理的。包括文件I/O 操作,如何在

程序中实现数据库相关的操作,以及如何使用Preferences 来存取数据。

9.1 文件I/O

Android 平台主要通过java.io.FileInputStream 和java.io.FileOutputStream 这两个类来实现对文件的读写,java.io.File 类则用来构造一个具体指向某个文件或者文件夹的对象。

这里需要注意的是,每个应用程序所在的包都会有一个私有的存储数据的目录(文件夹),只有属于这个包中的应用程序才有写入的权限,每个包中应用程序的私有数据目录位于Android 系统中的绝对路径/data/data/<包名>/目录中。除了私有数据目录,应用程序还拥有/sdcard 目录即Android 设备上的SD 卡的写入权限(请参考附录C )。文件系统中其他的系统目录,第三方应用程序都是不可写的。

熟悉Linux 文件系统的读者可以在命令行下用adb shell 命令进入模拟器的根文件系统,然后使用ls-l 命令查看各个目录的可读写权限,如图9-1所示。

图9-1 查看文件和目录的读写权限

9.1 文件I/O 203

8 9 下面我们用一段程序来详细讲解文件I/O 类的用法。

1 package com.studio.android.chp9.ex1; 2

3 import java.io.File;

4 import java.io.FileInputStream;

5 import java.io.FileOutputStream;

6 import java.io.IOException; 7

8 import org.apache.http.util.EncodingUtils; 9

10 import android.app.Activity; 11 import android.os.Bundle; 12 import android.util.Log; 13 import android.widget.TextView; 14

15 public class FileIO extends Activity { 16

17 final String FILE_PATH =

18 "/data/data/com.studio.android.chp9.ex1/"; 19 final String FILE_NAME = "luyou.txt"; 20 final String TAG = "I/O";

21 final String TEXT_ENCODING = "UTF-8"; 22

23 File file;

24 FileOutputStream out; 25 FileInputStream in; 26 TextView tv; 27 String display;

28 /** Called when the activity is first created. */ 29 @Override

30 public void onCreate(Bundle savedInstanceState) { 31 super.onCreate(savedInstanceState); 32 try {

33 //创建文件

34 file = new File(FILE_PATH , FILE_NAME); 35 file.createNewFile(); 36

37 //打开文件file 的OutputStream 38 out = new FileOutputStream(file);

39 String infoToWrite = "纸上得来终觉浅,绝知此事要躬行"; 40 //将字符串转换成byte 数组写入文件 41 out.write(infoToWrite.getBytes()); 42 //关闭文件file 的OutputStream 43 out.close();

204第9章 为有源头话水来——使用SQLite、ContentProvider与Preferences处理数据

44

45 //打开文件file的InputStream

46 in = new FileInputStream(file);

47 //将文件内容全部读入到byte数组

48 int length = (int)file.length();

49 byte[] temp = new byte[length];

50 in.read(temp, 0, length);

51 //将byte数组用UTF-8编码并存入display字符串中

52 display = EncodingUtils.getString(temp,TEXT_ENCODING);

53 //关闭文件file的InputStream

54 in.close();

55 } catch (IOException e) {

56 //将出错信息打印到Logcat

57 Log.e(TAG, e.toString());

58 this.finish();

59 }

60

61 //将读出的字符串用TextView显示到主界面

62 tv = new TextView(this);

63 tv.setText(display);

64 setContentView(tv);

65 }

66 }

首先在程序的开头定义一些字符串常量。

17 final String FILE_PATH =

18 "/data/data/com.studio.android.chp9.ex1/";

19 final String FILE_NAME = "luyou.txt";

20 final String TAG = "I/O";

21 final String TEXT_ENCODING = "UTF-8";

FILE_PATH指定文件存储的目录为当前包的私有数据目录,FILE_NAME是文件名,TAG是作为Log信息时的标签,TEXT_ENCODING则指定了从文件读出的数据转换成字符串时的字符编码。

33 //创建文件

34 file = new File(FILE_PATH , FILE_NAME);

35 file.createNewFile();

上面的代码创建了一个File类的对象,指定了文件存储的目录和文件名,然后使用createNewFile()方法创建文件,创建成功会返回ture,文件已经存在会返回false。若文件打开失败,如指定文件的目录不存在或没有写入该目录的权限或磁盘已满,就会抛出IOException 异常。

37 //打开文件file的OutputStream

9.1 文件I/O 205

8 9 38 out = new FileOutputStream(file);

39 String infoToWrite = "纸上得来终觉浅,绝知此事要躬行"; 40 //将字符串转换成byte 数组写入文件 41 out.write(infoToWrite.getBytes()); 42 //关闭文件file 的OutputStream 43 out.close();

为文件File 对象打开OutputStream 以从文件的开始写入数据,若要在文件当前内容后面添加数据可以使用FileOutputStream(File file, boolean append),append 为true 时,就从文件的末尾写入数据。若要指定文件写入的位置,也可以在使用write()方法的时候,指定文件写入的偏移量(offset )和写入到文件的字节数(count )。

void write (byte[] buffer, int offset, int count)

若文件不存在或不能以可写的方式打开,则会抛出FileNotFoundException 异常。若文件不存在也可以在打开文件的OutputStream 同时创建文件,即使用Context 对象调用

FileOutputStream openFileOutput (String name, int mode)。

这个方法打开(创建)当前应用程序私有文件目录下,即/data/data/<包名>/files/。 目录下文件名为name 的文件的OutputStream ,mode 应为Context 类中定义的一个常量,表明用什么模式打开(创建)文件,需用到多个模式时用“|”隔开,可用的值如下所示。

MODE_APPEND ,以在文件末尾写入数据这种模式打开文件。

MODE_PRIVATE ,以仅仅只有应用程序自己可读可写的模式创建文件。

MODE_WORLD_READABLE ,以其他应用程序对文件可读的模式创建文件。

MODE_WORLD_WRITEABLE ,以其他应用程序对文件可写的模式创建文件。

45 //打开文件file 的InputStream 46 in = new FileInputStream(file); 47 //将文件内容全部读入到byte 数组 48 int length = (int)file.length(); 49 byte[] temp = new byte[length]; 50 in.read(temp, 0, length);

51 //将byte 数组用UTF-8编码并存入display 字符串中

52 display = EncodingUtils.getString(temp,TEXT_ENCODING); 53 //关闭文件file 的InputStream 54 in.close();

为文件file 打开InputStream 以从文件读出数据,若文件不存在,则抛出FileNot-

FoundException 异常。这里需要注意的是,当读出的数据要转换成字符串时,要留意字符编码

问题,这里用到了EncodingUtils 类中getString()这个静态方法,来把从文件中读出到字节数组的数据,用UTF-8编码成了用户可读的字符串。与OutputStream 一样,我们可以通过一个

206第9章 为有源头话水来——使用SQLite、ContentProvider与Preferences处理数据

Context对象调用FileInputStream openFileInput (String name),来打开位于当前应用程序私有文件目录下文件名为name的文件的InputStream。但若文件不存在时会直接抛出FileNotFoundException异常。另外,当应用程序需要从项目工程目录assets/下的读出数据时,可以通过Context对象用如下的方式打开文件名为name文件的InputStream:InputStream in = this.getAssets().open(name);。

可以发现,大多数时候我们可以通过Context的对象调用openFileOutput()和openFile- Input(),来直接打开或创建位于私有文件目录的文件。另外,Context对象还可以通过调用fileList()方法来获得私有文件目录下所有文件的文件名组成的字符串数组,调用deleteFile (String name)方法来删除文件名为name的文件。

61 //将读出的字符串用TextView显示到主界面

62 tv = new TextView(this);

63 tv.setText(display);

64 setContentView(tv);

最后用一个TextView控件将从文件读出来的字符串显示到Activity的主界面,如图9-2所示。

图9-2读取文件内容并显示在界面上

9.2SQLite数据库

当应用程序需要处理的数据量比较大时,为了更加合理地存储、管理、查询数据,我们往往使用关系数据库来存储数据。Android平台为开发者提供了SQLite数据库相关的API来实现对数据库操作的支持,开发人员可以很方便地使用这些API来对SQLite数据库进行创建、修改及查询等操作。Android系统的很多用户数据,如联系人信息、通话记录、短信息等,都是存储在SQLite 数据库当中的,所以利用操作SQLite数据库的API可以同样方便地访问甚至修改这些数据。本节将从认识SQLite数据库开始来逐步介绍如何在Android程序中使用SQLite数据库。

9.2.1SQLite数据库介绍

SQLite是D. Richard Hipp用C语言编写的开源嵌入式数据库引擎。它是完全独立的,不具有外部依赖性。SQLite非常健壮,其创建者保守地估计SQLite可以处理每天多达100 000次点击率的

9.2 SQLite 数据库 207

8 9 Web 站点,有时候甚至可以处理上述数字的10倍负载。最重要地,SQLite 是一个轻量级的数据库,在一些简单语句的处理性能上比Mysql 或Postgresql 都要快。对于内存有限的Android 手持设备而言,当然是对时间空间要求越低的数据库引擎越好。

从功能上看,SQLite 支持多数SQL92标准,可以在所有主流的操作系统上运行,并且支持大多数计算机语言。其数据库单文件的特性也使得在Android 中操纵数据库更加灵活,另外SQLite 特有的内存数据库还会在某些特殊的情况下发挥奇妙的作用。更多有关SQLite 数据库的内容,请访问SQLite 官方网站:https://www.wendangku.net/doc/d82180551.html,/。

9.2.2 创建/打开SQLite 数据库

在Android 中我们通过SQLiteDatabase 这个类的对象操作SQLite 数据库。由于SQLite 数据库并不需要像C/S 数据库那样建立连接以及身份验证的特性,以及SQLite 数据库单文件数据库的特性,使得获得SQLiteDatabase 对象就像获得操作文件的对象那样简单。

要创建或打开一个SQLite 数据库,可以直接调用SQLiteDatabase 的静态方法:SQLite-

Database openDatabase (String path,SQLiteDatabase.C ursorFactory factory,int flags),来打开文件系统中位于绝对路径path 的数据库。可以给出一个CursorFactory 对象factory 用于查询时构造Cursor 的子类对象并返回,或者传入null 使用默认的factory 构

造。参数flags 用于控制打开或创建的模式,多个模式组合用|隔开,flags 可用的值均为

SQLiteDatabase 类中定义的整型常量。

OPEN_READONLY ,以只读的方式打开数据库。 OPEN_READWRITE ,以可读写的方式打开数据库。

CREATE_IF_NECESSARY ,当数据库不存在时,创建数据库。

NO_LOCALIZED_COLLATORS ,打开数据库时,不根据本地化语言对数据进行排序。

另外,还可以调用方法SQLiteDatabase openOrC

reateDatabase (String path,SQLite- Database.C ursorFactory factory),等同于调用openDatabase(path, factory, CREATE_IF_NECESSARY)。因为创建SQLite 数据库也就是在文件系统中创建一个SQLite 数据

库的文件,所以应用程序必须对创建数据库的目录必须有可写的权限,否则会抛出SQLite-

Exception 异常。

与创建或打开文件相似,我们同样可以通过Context 对象调用SQLiteDatabase openOr-

CreateDatabase (String name,int mode, SQLiteDatabase.CursorFactory factory)直

接在私有数据库目录(/data/data/<包名>/databases/目录)中创建或打开一个名为name 的数据库。需要注意的是,这里的mode 并不是传入SQLiteDatabase 类中的几个整型常量,而是传入

208第9章 为有源头话水来——使用SQLite、ContentProvider与Preferences处理数据

之前9.1节介绍过的Context类中的几个控制权限的常量MODE_PRIVATE、MODE_WORLD_ READABLE 和MODE_WORLD_WRITEABLE。另外,还可以通过调用databaseList()方法获得由私有数据库目录内所有数据库文件的文件名组成的字符串数组,调用deleteDatabase(String name)方法删除私有数据库目录内文件名为name的数据库文件。

除了在文件系统中创建SQLite数据库,Android还支持SQLite内存数据库。在某些需要临时创建数据库,并且对操作速率相对要求较高的情况下,SQLite内存数据库就发挥作用了。要在内存中创建一个SQLite数据库只需要调用SQLiteDatabase的静态方法SQLiteDatabase create (SQLiteDatabase.CursorFactory factory)就可以了。若创建成功会返回创建的SQLite内存数据库的对象,否则会返回null。

最后,请读者记住,不管用何种方式打开了数据库,获得的SQLiteDatabase对象不再使用时(如程序退出时),都要调用close()方法来关闭打开的数据库,否则会抛出IllegalState- Exception异常。下面这个例子完整地展示了,在应用程序的私有数据库目录创建SQLite数据库并在应用程序退出时关闭并删除数据库。

1 package com.studio.android.chp9.ex2;

2

3 import android.app.Activity;

4 import android.database.sqlite.SQLiteDatabase;

5 import android.os.Bundle;

6 import android.util.Log;

7

8 public class SQLite1 extends Activity {

9

10 final String TAG = "SQLite1";

11 final String DB_NAME = "test.db";

12

13 SQLiteDatabase test;

14 String amount;

15 @Override

16 public void onCreate(Bundle savedInstanceState) {

17 super.onCreate(savedInstanceState);

18 setContentView(https://www.wendangku.net/doc/d82180551.html,yout.main);

19 amount = String.valueOf(databaseList().length);

20 Log.d(TAG,"before:"+amount);

21 test = openOrCreateDatabase(DB_NAME,MODE_PRIVATE,null);

22 amount = String.valueOf(databaseList().length);

23 Log.d(TAG,"before:"+amount);

24 }

25

26 @Override

9.2 SQLite 数据库 209

8 9 27 public void onDestroy() { 28 test.close();

29 deleteDatabase(DB_NAME);

30 amount = String.valueOf(databaseList().length); 31 Log.d(TAG,"before:"+amount); 32 super.onDestroy(); 33 } 34 }

首先,定义了两个静态字符串常量,分别用来作为Log 信息的tag 和数据库文件的文件名。然后,在onCreate()回调方法中把私有数据库目录内的文件数量打印到Logcat 上,创建数据库后再次获取数据库文件数量并打印到Logcat 上。当程序退出时,onDestroy()回调方法被执行:首先关闭打开的数据库对象,然后删除在程序一开始创建的数据库,再把此时的数据库文件数量打印到Logcat 上。整个程序从运行到关闭,打印在Logcat 上的信息过滤后如图9-3所示。

图9-3 查看关于SQLite 的日志信息

在实际的应用程序编写中,为了更好地对SQLite 数据库的创建、打开以及更改进行管理,往往会编写一个继承自SQLiteOpenHelper 的数据库辅助类来帮助我们创建和打开数据库。下面代码是一个数据库辅助类的骨架。

1 package com.studio.android.chp9.ex3; 2

3 import android.content.Context;

4 import android.database.sqlite.SQLiteDatabase;

5 import android.database.sqlite.SQLiteOpenHelper;

6 import android.database.sqlite.SQLiteDatabase.CursorFactory; 7

8 public class MyHelper extends SQLiteOpenHelper { 9 public MyHelper(Context context, String name, 10 CursorFactory factory,int version) { 11 super(context, name, factory, version); 12 } 13

14 @Override

15 public void onCreate(SQLiteDatabase db) { 16 // TODO 创建数据库后,对数据库的操作。

210第9章 为有源头话水来——使用SQLite、ContentProvider与Preferences处理数据

17 }

18

19 @Override

20 public void onUpgrade(SQLiteDatabase db,

21 int oldVersion, int newVersion) {

22 // TODO更改数据库版本的操作

23 }

24

25 }

由于SQLiteOpenHelper采用Context里面的方法来创建、打开私有数据库目录下的数据库,所以首先在构造方法中传入context,name和factory。若name传入null,则代表创建一个SQLite 内存数据库,version是数据库更改的版本号,必须为非负数。onCreate()方法会在数据库第一次被创建后执行,所以通常会在此方法中执行一些诸如创建表的数据库初始化操作。当数据库本身需要更改时,即传入的version不等于当前版本号时就会调用onUpgrade()方法,因此会在此方法中执行增加/删除表或者表中的列等操作。另外,除了必须要实现onCreate()和onUpgrade()方法以外,还可以实现onOpen()这个方法。onOpen()方法会在每次成功打开数据库后首先被执行,默认情况下此方法的实现为空。

每次在程序中需要获得某个数据库的实例对象时,我们只需要先用这个数据库的文件名构造这样的一个数据库辅助对象,然后调用这个数据库辅助对象的getWritableDatabase()或getReadableDatabase()方法,就可以获得这个数据库的SQLiteDatabase对象。下面是getWritableDatabase()和getReadableDatabase()这两个方法的详细介绍。

getWritableDatabase()。以可读写的方式创建/打开一个SQLite数据库并返回SQLite- Database对象。若之前已经以可读写的方式打开过,并且没有用close()方法关闭,就会直接把之前打开的SQLiteDatabase对象返回。否则会抛出SQLiteException异常。

getReadableDatabase()。创建/打开一个SQLite数据库,但并不一定只返回只读的SQLiteDatabase对象。正常情况下,会返回与getWritableDatabase()方法返回的相同的SQLiteDatabase对象。若出现磁盘已满或数据库只能以只读的方式打开等情况,则会返回一个只读的SQLiteDatabase对象。但若随后再次调用此方法时,问题已经被解决,只读的SQLiteDatabase对象会被关闭,而重新返回一个可读写的SQLiteDatabase数据库对象。若失败会抛出SQLiteException异常。

9.2.3利用SQLiteDatabase对象操作数据库

获得了SQLiteDatabase对象以后,我们就可以通过调用SQLiteDatabase的实例方法来对数据库进行操作了。SQLiteDatabase除了提供像execSQL()和rawQuery()这种直接对SQL语句解

9.2 SQLite 数据库 211

8 9 析的方法外,还针对INSERT 、UPDATE 、DELETE 和SELECT 等操作专门定义了相关的方法。下面是对常用的数据库操作方法的总结。

public void execSQL (String sql),public void execSQL (String sql, Object[] bindArgs),执行一条非查询SQL 语句,执行期间会获得该SQLite 数据库的写锁,执行完

毕后锁释放。不支持用;隔开的多条SQL 语句。若SQL 语句执行失败会抛出SQLException 异常。

参数:sql ,需要执行的SQL 语句字符串。

bindArgs ,SQL 语句中表达式的?占位参数列表,仅仅支持String 、byte 数组、long

和double 型数据作为参数。 返回值:无。

public Cursor rawQuery (String sql, String[] args),public Cursor rawQuery- WithFactory(SQLiteDatabase.C ursorFactory factory, String sql, String[] args, String editTable),执行一条SQL 查询语句,并把查询结果以Cursor 的子类对

象的形式返回。

参数:sql ,需要执行的SQL 语句字符串。

args ,SQL 语句中表达式的?占位参数列表,参数只能为String 类型。

factory ,CursorFactory 对象,用来构造查询完毕时返回的Cursor 的子类对象,

为null 时使用默认的CursorFactory 构造。

editable ,第一个可编辑的表名。

返回值:指向第一行数据之前的Cursor 子类对象。

public long insert (String table, String nullColumnHack, ContentValues initialValues),public long insertOrThrow (String table, String nullColumnHack, ContentValues initialValues),向指定表中插入一行数据。

参数:table ,需要插入数据的表名。

nullColumnHack ,这个参数需要传入一个列名。SQL 标准并不允许插入所有列均为

空的一行数据,所以当传入的initialValues 值为空或者为0时,用nullColumnHack 参数指定的列会被插入值为NULL 的数据,然后再将此行插入到表中。

initalValues ,用来描述要插入行数据的ContentValues 对象,即列名和列值的

映射。

返回值:新插入行的行id 。如果有错误发生返回-1。

public int update (String table, C ontentValues values, String whereC lause, String[] whereArgs),更新表中指定行的数据。

参数:table ,更新数据的表名。

212第9章 为有源头话水来——使用SQLite、ContentProvider与Preferences处理数据

values,用来描述更新后的行数据的ContentValues对象,即列名和列值的映射。

whereClause,可选的where语句(不包括WHERE关键字),用来指定需要更新的行。

若传入null则表中所有的行均会被更新。

whereArgs,where语句中表达式的?占位参数列表,参数只能为String类型。

返回值:被更新的行的数量。

public int delete (String table, String whereC lause, String[] whereArgs),删除表中指定的行。

参数:table,需要删除行的表名。

whereClause,可选的where语句(不包括WHERE关键字),用来指定需要删除的行。

若传入null则会删除所有的行。

whereArgs,where语句中表达式的?占位参数列表,参数只能为String类型。

返回值:若传入了正确的where语句则被删除的行数会被返回。若传入null,则会返回0。

若要删除所有行并且返回删除的行数,则需要在where语句的地方传入字符串1。

public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)

public Cursor query (boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit)

public Cursor query (String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) public C ursor queryWithFactory (SQLiteDatabase.C ursorFactory cursorFactory, boolean distinct, String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy, String limit) 根据检索条件检索指定表并把满足条件的行以Cursor的子类对象返回。

参数:table,检索的表名。

columns,由需要返回列的列名所组成的字符串数组,传入null会返回所有的列。

selection,指定需要返回的行的where语句(不包括WHERE关键字)。传入null则

返回所有行。

selectionArgs,where语句中表达式的?占位参数列表,参数只能为String类型。

groupBy,对结果集进行分组的group by语句(不包括GROUP BY关键字)。传入null

将不对结果集进行分组。

having,对分组结果集设置条件的having语句(不包括HAVING关键字)。必须配合

groupBy参数使用,传入null将不对分组结果集设置条件。

9.2 SQLite 数据库 213

8 9 orderBy ,对结果集进行排序的order by 语句(不包括ORDER BY 关键字)。传入null

将对结果集使用默认的排序。

limit ,对返回的行数进行限制的limit 语句(不包括LIMIT 关键字)。传入null 将不

限制返回的行数。

distinct ,如果希望结果集没有重复的行传入true ,否则传入false 。

cursorFactory ,使用这个CursorFactory 来构造返回的Cursor 子类对象,传入null 使用默认的CursorFactory 。

返回值:指向第一行数据之前的Cursor 子类对象。

了解了这些函数,下面让我们来把数据库的操作填充到之前的MyHelper 骨架中,并且在程序中利用MyHelper 创建并打开数据库名为code.db 的数据库,最后向数据库中的countrycode 表插入数据,如表9-1所示。

表9-1 countrycode 表中包含的数据

_id (integer )

country (varchar )

code (integer )

1 中国 86

2 意大利 39 3

洪都拉斯

504

首先新建项目SQLite2,把之前MyHelper 的骨架复制到MyHelper.java ,在MyHelper 类中为表名和列名定义字符串静态常量。

public static final String TB_NAME = "countrycode"; public static final String ID = "_id";

public static final String COUNTRY = "country"; public static final String CODE = "code";

TB_NAME 指定了表名,而ID 、COUNTRY 和CODE 则分别指定了各个列的列名。

然后在onCreate()和onUpgrade()方法中加入创建表和删除表的操作。

@Override

public void onCreate(SQLiteDatabase db) {

// 创建表countrycode

db.execSQL("CREATE TABLE IF NOT EXISTS " + TB_NAME + " ("

+ ID + " INTEGER PRIMARY KEY," + COUNTRY + " VARCHAR," + CODE + " INTEGER)"); }

214第9章 为有源头话水来——使用SQLite、ContentProvider与Preferences处理数据调用了execSQL()方法来创建如表9-1所示的表。

@Override

public void onUpgrade(SQLiteDatabase db,

int oldVersion, int newVersion) {

//删除以前的旧表,创建一张新的空表

db.execSQL("DROP TABLE IF EXISTS "+TB_NAME);

onCreate(db);

}

每次需要更新数据库版本时,将以前的表删除,重新创建空表。这是很极端的一种做法,会删除以前数据库内的所有数据。其实在对数据库进行改变时,若需要保留之前数据库中的数据,可以使用ALERT TABLE语句来直接修改表。

接下来就是在Activity中程序的运行部分了,编辑SQLite2.java,代码如下所示。

1 package com.studio.android.chp9.ex3;

2

3 import android.app.Activity;

4 import android.content.ContentValues;

5 import android.database.sqlite.SQLiteDatabase;

6 import android.os.Bundle;

7

8 public class SQLite2 extends Activity {

9

10 public static final String DB_NAME = "code.db";

11 public static final int VERSION = 1;

12

13 MyHelper helper;

14 SQLiteDatabase db;

15

16 /** Activity第一次创建时调用 */

17 @Override

18 public void onCreate(Bundle savedInstanceState) {

19 super.onCreate(savedInstanceState);

20 setContentView(https://www.wendangku.net/doc/d82180551.html,yout.main);

21

22 //初始化数据库辅助对象

23 helper = new MyHelper(this, DB_NAME, null, VERSION);

24

25 //获得可读写的SQLiteDatabase对象

26 db = helper.getWritableDatabase();

27

28 //用insert方法像数据库中插入"中国 86"

9.2 SQLite 数据库 215

8 9 29 ContentValues values = new ContentValues(); 30 values.put(MyHelper.COUNTRY, "中国"); 31 values.put(MyHelper.CODE, 86);

32 db.insert(MyHelper.TB_NAME, MyHelper.ID, values); 33

34 //使用insert 方法

35 //插入完全为空的ContentValues,

36 //再使用update 方法修改行数据为"意大利 39"

37 db.insert(MyHelper.TB_NAME, MyHelper.ID,null); 38 values.clear();

39 values.put(MyHelper.COUNTRY, "意大利"); 40 values.put(MyHelper.CODE, 39);

41 db.update(MyHelper.TB_NAME, values, MyHelper.ID + " = 2",null); 42

43 //使用execSQL 方法插入数据"洪都拉斯 504" 44 db.execSQL("INSERT INTO "

45 + MyHelper.TB_NAME + "(" 46 + MyHelper.COUNTRY + ","

47 + MyHelper.CODE + ") VALUES " 48 + "('洪都拉斯',504)"); 49 } 50

51 @Override

52 public void onDestroy() { 53 //程序退出时删除所有行

54 db.delete(MyHelper.TB_NAME,null,null); 55 super.onDestroy(); 56 } 57 }

首先在为数据库名和版本分别定义两个静态常量。

10 public final String DB_NAME = "code.db"; 11 public final int VERSION = 1;

DB_NAME 指定了要创建/打开的数据库的名字,VERSION 则指定了数据库的版本。

这里为了演示方法的使用,在Activity 的onCreate()方法中分别用了三种不同的方式来向数据库中插入、更新数据。在实际的应用程序编写过程中请读者根据情况,自行选择操作数据库的方式。首先是构造一个ContentValues 的对象,然后用insert 方法将数据插入到数据库。

28 //用insert 方法向数据库中插入"中国 86"

29 ContentValues values = new ContentValues(); 30 values.put(MyHelper.COUNTRY, "中国"); 31 values.put(MyHelper.CODE, 86);

32 db.insert(MyHelper.TB_NAME, MyHelper.ID, values);

216第9章 为有源头话水来——使用SQLite、ContentProvider与Preferences处理数据

细心的读者可能会注意到,这里我们并没有指定_id列的值。这是因为SQLite数据库中将所有声明为“INTEGER PRIMARY KEY”的列自动识别成自增列。在插入一行数据的时候,若不指定自增列的数据或给自增列传入NULL值时,会自动给自增列赋一个所有行中此列里的最大值加1的数。若添加的是第一行则从数字1开始。而这里的_id就是一个自增列,所以在插入这行数据后,此行数据_id列的值为1。

而第二种方式是,先用insert方法插入一个为null的ContentValues,然后再用update方法修改刚才所插入的行。

34 //使用insert方法

35 //插入完全为空的ContentValues,

36 //再使用update方法修改行数据为"意大利 39"

37 db.insert(MyHelper.TB_NAME, MyHelper.ID,null);

38 values.clear();

39 values.put(MyHelper.COUNTRY, "意大利");

40 values.put(MyHelper.CODE, 39);

41 db.update(MyHelper.TB_NAME, values, MyHelper.ID + " = 2",null);

由于传入的ContentValues对象为null,所以将会把由第二个参数设置的列赋值为NULL。这里需要分清NULL列和空列:空列是没有任何值的列,而NULL列的意思是值为NULL的列。这里我们第二个参数设置的是_id列,又由于_id列是自增列,传入NULL会自增赋值,所以最终_id 列的值为2。然后再使用update方法,更新_id为2的行,把国家信息和区号信息填入。

43 //使用execSQL方法插入数据"洪都拉斯 504"

44 db.execSQL("INSERT INTO "

45 + MyHelper.TB_NAME + "("

46 + MyHelper.COUNTRY + ","

47 + MyHelper.CODE + ") VALUES "

48 + "('洪都拉斯',504)");

最后使用的是直接执行SQL语句字符串的方式插入行。

程序退出时清空表中数据,避免下次启动程序时重复添加数据。

51 @Override

52 public void onDestroy() {

53 //程序退出时删除所有行

54 db.delete(MyHelper.TB_NAME,null,null);

55 super.onDestroy();

56 }

9.2.4Cursor的使用

查询数据库均会把查询的结果包装在一个Cursor的子类对象中返回。Cursor就像是位于结

9.2 SQLite 数据库 217

8 9 果集之上的一个游标,可以对结果集进行向前、向后或随机的访问。而Cursor 本身是一个接口类,提供了对结果集访问的一些抽象方法,根据功能的不同在其子类有着不同的实现。要控制查询时返回的Cursor 类型,可以自定义一个继承自CursorFactory 类通过实现其newCursor()方法来返回需要的Cursor 子类对象,但在CursorFactory 传入null 的默认情况下,查询操作会返回一个指向第一行数据之前的SQLiteCursor 的对象。

表9-2是对Cursor 中常用的一些方法的介绍。

表9-2 有关Cursor 的常用方法

方 法

描 述

moveToPosition(int pos) 移动Cursor 到结果集的指定的位置。若移到了结果集之外返回false ,否则返回true

move(int offset)

根据相对当前Cursor 所指向的位置,向前或向后移动offset 个位置(正数向前,负数向后)。若Cursor 指向了结果集之外,将会指到第一行数据之前(-1),并且会返回false ,否则返回true

moveToFirst () moveToLast() 将Cursor 移动到第一行(最后一行)数据。若结果集为空返回false ,否则返回true

moveToNext() moveToLast() 将Cursor 向前或向后移动一行。若Cursor 移到了结果集之外返回false ,否则返回true

isBeforeFirst() isAfterLast() Cursor 是否已经指向第一行数据之前(最后一行数据之后),是返回true ,否则返回false

isFirst() isLast()

Cursor 是否指向第一行(最后一行)数据,是返回true ,否返回false

getColumnCount () getCount()

返回行(列)数

getColumnIndexOrThrow (String columnName) 根据列名返回列索引号(从0开始),若指定列名不存在会抛出Illegal- ArgumentException 异常

getBlob (int index) getInt (int index) getLong (int index) getShort (int index) getFloat (int index) getDouble (int index) getString (int index)

按类型返回Cursor 所指的当前行的index 列索引号所指列的数据

利用这些方法我们可以很容易地完成提取或者遍历结果集的操作。下面的代码演示了如何遍历上例中创建的code.db 。

Cursor c; ...

c = db.query(MyHelper.TB_NAME,null,null,null,null,null, MyHelper.CODE+" DESC");

final int countryIndex = c.getColumnIndexOrThrow(MyHelper.COUNTRY); final int codeIndex = c.getColumnIndexOrThrow(MyHelper.CODE); for (c.moveToFirst();!(c.isAfterLast());c.moveToNext()) {

218第9章 为有源头话水来——使用SQLite、ContentProvider与Preferences处理数据

String country = c.getString(countryIndex);

int code = c.getInt(codeIndex);

Toast.makeText(this, country+code, Toast.LENGTH_LONG).show();

}

首先对code.db进行查询,并按区号的降序返回了表中所有行在Cursor的结果集中。然后直接利用一个for循环对结果集遍历,并把每次访问结果集的数据用Toast显示出来。

在实际的应用编写过程中,更多是通过适配器(见第4章)来将Cursor与适配器控件联系起来。Android为Cursor提供了一个抽象类CursorAdapter,可以方便实现Cursor与适配器的连接。只需要创建一个继承自CursorAdapter的类,实现其bindView()和newView()两个抽象方法,或根据需要重新实现其他方法就可以用此类来构造一个可适配Cursor的适配器。下面是关于bindView()和newView()两个抽象方法需要实现的内容。

public void bindView(View view, Context context, Cursor cursor),重用一个已有的view,使其显示当前cursor所指向的数据。

public View newView(Context context, Cursor cursor, ViewGroup parent),为cursor所指向的数据新建一个View对象,并显示其数据。

另外还需要注意的是,传入到CursorAdapter中的Cursor结果集必须包含有列名为_id的列,否则CursorAdapter将不会起作用。

前面(见表4-4)已经介绍到了几种Android已经实现好了的适配器,其中SimpleCursor- Adapter就是为Cursor对象专门实现的一种方便使用的适配器类,下面是对其构造方法传入参数的介绍。

public SimpleC ursorAdapter (C ontext contex, int layout, C ursor c, String[] from, int[] to)

context,当前程序的上下文对象。

layout,用来描述如何显示在适配器控件上的布局文件的R类引用。

from,由需要显示出来的列名组成的字符串数组。

to,由layout所指定的布局文件中子控件的id所组成的整形数组,与from相对应。

下面我们为上面的SQLite2程序设计一个由TextView和Spinner组成的用户界面,然后在程序中注释掉遍历Cursor的for循环代码。实现用Spinner选择不同的国家时,TextView就显示相对应国家的区号。首先是布局文件main.xml。

1

2

9.2 SQLite 数据库 219

8 9 3 xmlns:android="https://www.wendangku.net/doc/d82180551.html,/apk/res/android" 4 android:orientation="vertical" 5 android:layout_width="fill_parent" 6 android:layout_height="fill_parent" 7 >

8

9 android:id="@+id/display"

10 android:layout_width="fill_parent" 11 android:layout_height="wrap_content" 12 android:textSize="20px" /> 13

14 android:id="@+id/spinner"

15 android:layout_width="fill_parent" 16 android:layout_height="wrap_content"/> 17

在设置了主界面布局以后,就是实现功能部分了,为Spinner 和TextView 分别定义一个实例变量以便在程序中引用。

TextView display; Spinner s; ...

在注释掉遍历Cursor 的代码后,添加如下填充Spinner 控件的代码。

s = (Spinner)findViewById(R.id.spinner); display = (TextView)findViewById(R.id.display);

SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, https://www.wendangku.net/doc/d82180551.html,yout.simple_spinner_item, c,

new String[] { MyHelper.COUNTRY}, new int[] {android.R.id.text1}); adapter.setDropDownViewResource(

https://www.wendangku.net/doc/d82180551.html,yout.simple_spinner_dropdown_item);

s.setAdapter(adapter);

s.setOnItemSelectedListener(new OnItemSelectedListener(){ @Override

public void onItemSelected(AdapterView adapter,View v, int pos, long id) { c.moveToPosition(pos);

display.setText(c.getString(codeIndex)); }

@Override

220第9章 为有源头话水来——使用SQLite、ContentProvider与Preferences处理数据

public void onNothingSelected(AdapterView arg0) {}

});

首先通过id分别获得TextView和Spinner对象的引用,然后使用之前查询返回的Cursor和Android自带的Spinner子控件的布局构造一个SimpleCursorAdapter,再调用setDrop- DownViewResource()方法来设置点开Spinner后显示子控件的布局,最后设置Spinner的适配器为adapter并对其子控件被选取的事件设置一个监听器。需要注意的是Spinner不支持设置点击事件的监听器(OnClickListener),强行设置的话会抛出异常。最后程序运行的结果如图9-4所示。

图9-4最终显示数据库信息的界面

9.3ContentProvider

当数据需要在应用程序间共享时,我们就可以利用ContentProvider为数据定义一个URI。之后其他应用程序对数据进行查询或者修改时,只需要从当前上下文对象获得一个ContentResolver (内容解析器)传入相应的URI就可以了。本节中将以前面创建的code.db数据库为例,向读者介绍如何定义一个ContentProvider,以及如何在其他程序中使用ContentResolver访问URI所指定的数据。

9.3.1定义ContentProvider

要为当前应用程序的私有数据定义URI,就需要专门定义一个继承自ContentProvider的类,然后根据不同的操作调用的方法去实现这些方法的功能。下面我们用SQLite2这个例子,来为它的数据库code.db定义一个URI。

首先,在SQLite2的包中创建一个新类ContryCode.java,来装入所有与数据库操作有关的

9.3 ContentProvider 221

8 9 静态字段,以便于打包成JAR 文件供其他应用程序调用。

1 package com.studio.android.chp9.ex3; 2

3 import https://www.wendangku.net/doc/d82180551.html,.Uri; 4

5 public class CountryCode { 6

7 public static final String DB_NAME = "code.db"; 8 public static final String TB_NAME = "countrycode"; 9 public static final int VERSION = 1; 10

11 public static final String ID = "_id";

12 public static final String COUNTRY = "country"; 13 public static final String CODE = "code"; 14

15 public static final String AUTHORITY = 16 "com.studio.andriod.provider.countrycode"; 17 public static final int ITEM = 1; 18 public static final int ITEM_ID = 2; 19

20 public static final String CONTENT_TYPE =

21 "vnd.android.cursor.dir/vnd.studio.android.countrycode"; 22 public static final String CONTENT_ITEM_TYPE =

23 "vnd.android.cursor.item/vnd.studio.android.countrycode"; 24

25 public static final Uri CONTENT_URI =

26 Uri.parse("content://" + AUTHORITY + "/item"); 27 }

其中DB_NAME 、TB_NAME 和VERSION 分别定义了数据库和表的名称以及数据库的版本号。ID 、

COUNTRY 和CODE 分别定义的是表中的各个列的列名。AUTHORITY 定义了标识ContentProvider 的字

符串,ITEM 和ITEM_ID 分别用于UriMatcher (资源标识符匹配器)中对路径item 和item/id 的匹配号码。CONTENT_TYPE 和CONTENT_ITEM_TYPE 定义了数据的MIME 类型。需要注意的是,单一数据的MIME 类型字符串应该以vnd.android.cursor.item/开头,数据集的MIME 类型字符串则应该以vnd.android.cursor.dir/开头。CONTENT_URI 定义的是查询当前表数据的

content://样式URI 。

接下来,同样是在SQLite2的包中创建一个继承自ContentProvider 的类MyProvider.java ,来实现对数据操作的各个方法。这里将用到MyHelper 来辅助获得SQLiteDatabase 对象,虽然也可以直接使用Context.OpenOrCreate()方法获得,但不及使用数据库打开辅助类方便。

public class MyProvider extends ContentProvider {

相关文档
相关文档 最新文档