Android开发之数据保存技术(一)
2016-09-02 09:29
459 查看
本文主要讲解Android开发的数据保存技术。Android的数据保存技术主要有preference,本地文件,SQLite轻量级数据库,和Content Provider。本文只要讲SQLite和Conent Provider。preference和本地文件,将放在后面讨论。
SQLite
Android通过SQLite库提供了完善的关系数据库功能,而没有强加任何额外的限制。通过使用SQLite,可以为每一个应用程序创建独立的关系数据库。
所有的Android数据库都存储在/data/data/<package_name>/databases文件夹下。默认条件下,所有的数据库都是私有的,并且只能被创建它们的应用程序访问。要跨应用程序共享数据库,可以使用内容提供器。
SQLite是一个关系数据管理系统。它被普遍认为是:开源,兼容标准,轻量级,Single-tier。
可以用一个例子来演示SQLite。该例子将记录存储在一个数据库中,然后显示出来。
新建一个HelloSQLite的程序。
需要在某个地方放置该数据库中描述的一些常量,所以要创建一个Constants接口。
代码如下:
/*
* Android开发之数据保存技术(一)
* Constants.java
* Created on: 2011-8-15
* Author: blueeagle
* Email: liujiaxiang@gmail.com
*/
view sourceprint?
每个事件都将作为HeloSQLite表中的一行进行存储。每行都包含一个_id、time和title列。_id是主键,在扩展的BaseColums接口中声明。time和title分别作为时间和事件标记。
使用SQliteOpenHelper。接下来,创建一个名为SQLiteData的帮助器来表示数据库本身。这个类扩展自Android的SQLiteOpenHelper类,它负责管理数据库的创建和版本。需要做的就是提供一个构造方法并且覆写两个方法。
代码如下:
view sourceprint?
首次访问数据库时,SQLiteOpenHelper将注意到该数据库不存在,并调用onCreate()方法来创建它。
定义Activity主程序。
在HelloSQLite程序中做的第一次尝试是使用本地的SQLite数据库来存储事件,并将这些事件显示为TextView中的一个字符串。
布局xml文件如下:
view sourceprint?
这段代码使用一个ScrollView,以防太多的时间沾满了屏幕。
对于HelloSQLite.java的实现如下所示:相关代码有注释说明。
view sourceprint?
完成上述文件-》运行,即可看到结果显示出来。这样就完成了第一个Android数据库程序。
当然,这里面需要注意的问题:
1. 新建数据库的问题
新建数据库的时候,会遇到我们用:
db.execSQL("CREATE TABLE "+ TABLE_NAME+"("+_ID+"INTEGER PRIMARY KEY AUTOINCREMENT,"+TIME+"INTEGER,"+TITLE+"TEXT NOT NULL);");
这样一条语句。有时候SQL语句书写错误,比如少一个空格就会引起程序异常退出。这个时候建议在db.execSQL()函数中填写一个字符串变量,然后把自己写的SQL语句赋给字符串变量,在做赋值操作的时候,先打印出来看看,是不是少空格,多+号什么的小错误。
2. 版本号的问题
关于构造函数中的版本号,是有明确的说明的。
public MySQLite(Context context,String name,CursorFactory factory,int version) {
super(context,DATABASE_NAME,factory,DATABASE_VERSION);
这个版本号version当你有新版本的时候,则做更新操作,新版本的版本号将要比老版本高,如果此时修改版本号,不是向高修改,而是向低修改的话,程序就会发生异常了。
3. 数据库文件的问题
对于onCreate()方法,在程序运行的时候,只运行一次,运行的这一次就是去创建数据库。将数据库的文件存储在SD卡中。路径是data/data/包名/databases里。可以在Eclipse里通过DDMS里的File Explorer来查看。当数据库文件存在了以后,则不会再次运行。
完成了上述例子,就算完成了一个数据库的应用。但是如果列表中有数千个或者上百万个事件。程序将运行的非常慢。甚至于耗尽内存。解决办法就是数据绑定。
有了数据绑定,只需要几行代码,就可以将数据连接到视图,从而实现需求。为了演示,我们修改上面的实例。
将主程序HelloSQLite.java继承ListActivity而不是Activity。
将代码修改为如下所示:
view sourceprint?
将main.xml修改成如下形式:
view sourceprint?
增加item.xml为如下:
view sourceprint?
运行结果如图所示。
当然,这只是一个DEMO,用来展示如何使用SQLite,这个DEMO存在一些问题。其他应用程序都不能向事件数据库添加内容,甚至于无法看这些事件。那么针对这一点,引出了Android里的内容提供器,即ContentProvider。
内容提供器
在Android安全模型中,一个应用程序编写的文件无法被其他任何应用程序所读写。每个应用程序都有自己的Linux用户ID和数据目录,以及其受保护的内存空间。Android程序可以通过下面两种方式进行彼此间的通信。
第一种是IPC(Inter-Process Communication,进程间通信):一个进程使用AIDL(Android Interface Definition Language,接口定义语言)和IBinder接口声明一个任意的API。调用该API时,将在进程间安全且有效地队参数进行编组。这项先进技术用于对后台Service线程进行远程过程调用。
第二种就是ContentProvider:进程在系统中将他们本身注册为某些数据类型的提供者。请求该信息时,Android就会通过一个固定的API调用这些进程,以它们认为合适的方式查询或者修改内容。
ContentProvider管理的任何信息部分都通过一个URI来寻址,这个URI类似于以下形式:content://authority/path/id
其中content://是标准要求的前缀。authority是提供者的名称,建议使用完全限定包名称,避免出现名称冲突。Path是提供者内部的一个虚拟目录,用于标识被请求的数据类型。Id是被请求的特定记录的主键,要请求获得具有特定类型的所有记录,可以省略此参数以及后面的斜杠。
Android已经内置提供了几个提供者,包括:
content://browser;
content://contacts;
content://media;
content://settings。
下面我们就来演示如何使用内容提供器。
将HelloSQLite程序改为使用内容提供器的。对于HelloSQLite的提供者,下面都是有效的URI:
content://com.blueeagle.HelloSQLite/3 ——表示取id为3的数据
content://com.blueeagle.HelloSQLite/ ——表示取所有数据
首先需要向Contants.java添加两个常量:
public static finalString AUTHORITY = "com.blueeagle.HelloSQLite";
public static finalUri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME);
接下来可以对HelloSQLite类做一些修改:
代码如下:
view sourceprint?
下面就是实现ContentProvider
ContentProvider是一个类似于Activity的高级对象,需要向系统进行声明。因此,实现ContentProvider的第一步是将其添加到AndroidManifest.xml文件中的<activity>标签之前。
其中android:name是类名,android:authorities是在内容URI中使用的字符串。接下来创建我们自己的ContentProvider类,为继承类。ContentProvider创建后就会被调用:public boolean onCreate()ContentProvider类中有6个继承而来的方法,需要实现:
具体来说需要实现ContentProvider 类中的6个抽象方法。
Cursor query(Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder):将查询的数据以Cursor 对象的形式返回。
Uri insert(Uri uri,ContentValues values):向 Content Provider中插入新数据记录,ContentValues 为数据记录的列名和列值映射。int update(Uri uri,ContentValues values,String selection,String[] selectionArgs):更新Content Provider中已存在的数据记录。int delete(Uri uri,String selection,String[] selectionArgs):从Content Provider中删除数据记录。
String getType(Uri uri):返回Content Provider中的数据( MIME )类型。
boolean onCreate():当 Content Provider 启动时被调用。
定义一个 URI 类型的静态常量,命名为CONTENT_URI。 必须为该常量对象定义一个唯一的URI字符串,一般的做法是将 ContentProvider子类的全称类名作为URI字符串。
定义每个字段的列名,如果采用的数据库存储系统为SQLite 数据库,数据表列名可以采用数据库中表的列名。不管数据表中有没有其他的唯一标识一个记录的字段,都应该定义一个"_id"字段 来唯一标识一个记录。模式使用 "INTEGER PRIMARY KEY AUTOINCREMENT" 自动更新 一般将这些列名字符串定义为静态常量,如"_id"字段名定义为一个名为"_ID" 值为 "_id" 的静态字符串对象。
创建好的一个Content Provider必须在AndroidManifest.xml中声明。
<provider android:name=".ItemsProvider"
android:authorities="com.blueeagle" />
其中name属性为ContentProvider 子类的全称类名,authorities 属性唯一标识了一个ContentProvider。还可以通过 setReadPermission() 和 setWritePermission() 来设置其操作权限。当然也可以再上面的 xml中加入 android:readPermission 或者 android: writePermission属性来控制其权限。
注意:因为ContentProvider可能被不同的进程和线程调用,所以这些方法必须是线程安全的。
然后需要使用UriMatcher,用于匹配Uri。
用法如下:
首先把需要匹配Uri路径全部给注册上:
对于Uri:
什么是URI?将其分为A,B,C,D 4个部分:
A:标准前缀,用来说明一个Content Provider控制这些数据,无法改变的;"content://"
B:URI的标识,它定义了是哪个Content Provider提供这些数据。对于第三方应用程序,为了保证URI标识的唯一性,它必须是一个完整的、小写的 类名。这个标识在 元素的 authorities属性中说明:一般是定义该ContentProvider的包.类的名称 ;"content://hx.android.text.myprovider"
C:路径,不知道是不是路径,通俗的讲就是你要操作的数据库中表的名字,或者你也可以自己定义,记得在使用的时候保持一致就ok了;"content://hx.android.text.myprovider/tablename"
D:如果URI中包含表示需要获取的记录的ID;则就返回该id对应的数据,如果没有ID,就表示返回全部; "content://hx.android.text.myprovider/tablename/#" #表示数据id
注册完需要匹配的Uri后,就可以使用sMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数,例如匹配content://com.blueeagle路径,返回的匹配码为1。
//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//添加需要匹配的uri,如果匹配就会返回匹配码
//如果match()方法匹配content://com.blueeagle路径,返回匹配码为1
sUriMatcher.addURI(“content://com.blueeagle”,“HelloSQLite”,1);
//如果match()方法匹配content://com.blueeagle/ ***路径,返回匹配码为2
//#号为通配符
sUriMatcher.addURI(“content://com.blueeagle”,“HelloSQLite/#”,2);
switch(sUriMatcher.match(Uri.parse("content://com.blueeagle /***"))) {
case 1 break;
case 2 break;
default:
//不匹配 break;
}
自定义的contentprovider如下:
ItemsProvider.java
view sourceprint?
总结一下,创建一个新的内容提供器。
1.通过扩展抽象类ContentProvider可以创建新的内容提供器。重写onCreate方法来打开或者初始化将要使用这个新的提供器来提供底层数据源。
2.还应该提供那些用来返回指向这个提供器的完整的URI的公共静态CONTENT_URI变量。提供器之间的内容URI应该是唯一的,所以最好的做法是使URI路径以包名为基础。
定义一个内容提供器URI一般的形式为:
content://com.pakagename/datapath
例如:content://com.blueeagle/items
或者:content://com.blueeagle./items/3
内容URI可以表示为这两种形式中的任意一种形式。前面的一种URI表示对那种类型中所有的值的请求,后面附加一个/3的表示对一条记录的请求(这里请求的是记录3)。这两种形式来访问提供器都是可行的。
完成这项工作最简单的方式是使用一个UriMatcher。当通过内容解析器来访问内容提供器的时候,可以配置UriMathcer来解析URI以确定它们的形式。就像前文说的那样。
SQLite
Android通过SQLite库提供了完善的关系数据库功能,而没有强加任何额外的限制。通过使用SQLite,可以为每一个应用程序创建独立的关系数据库。
所有的Android数据库都存储在/data/data/<package_name>/databases文件夹下。默认条件下,所有的数据库都是私有的,并且只能被创建它们的应用程序访问。要跨应用程序共享数据库,可以使用内容提供器。
SQLite是一个关系数据管理系统。它被普遍认为是:开源,兼容标准,轻量级,Single-tier。
可以用一个例子来演示SQLite。该例子将记录存储在一个数据库中,然后显示出来。
新建一个HelloSQLite的程序。
需要在某个地方放置该数据库中描述的一些常量,所以要创建一个Constants接口。
代码如下:
/*
* Android开发之数据保存技术(一)
* Constants.java
* Created on: 2011-8-15
* Author: blueeagle
* Email: liujiaxiang@gmail.com
*/
view sourceprint?
01.
package com.blueeagle;
02.
03.
04.
05.
import android.provider.BaseColumns;
06.
07.
public
interface Constants extends BaseColumns {
08.
09.
/** Called when the activity is first created. */
10.
11.
public
static
finalString TABLE_NAME =
"HelloSQLite"
;
12.
13.
public
static
finalString TIME =
"time"
;
14.
15.
public
static
finalString TITLE =
"title"
;
16.
17.
}
每个事件都将作为HeloSQLite表中的一行进行存储。每行都包含一个_id、time和title列。_id是主键,在扩展的BaseColums接口中声明。time和title分别作为时间和事件标记。
使用SQliteOpenHelper。接下来,创建一个名为SQLiteData的帮助器来表示数据库本身。这个类扩展自Android的SQLiteOpenHelper类,它负责管理数据库的创建和版本。需要做的就是提供一个构造方法并且覆写两个方法。
代码如下:
view sourceprint?
01.
/*
02.
03.
* Android开发之数据保存技术(一)
04.
05.
* MySQLite.java
06.
07.
* Created on: 2011-8-16
08.
09.
* Author: blueeagle
10.
11.
* Email: liujiaxiang@gmail.com
12.
13.
*/
14.
15.
package com.blueeagle;
16.
17.
18.
19.
import android.content.Context;
20.
21.
import android.database.sqlite.SQLiteDatabase;
22.
23.
import android.database.sqlite.SQLiteDatabase.CursorFactory;
24.
25.
import android.database.sqlite.SQLiteOpenHelper;
26.
27.
import
static
com.blueeagle.Constants.TABLE_NAME;
28.
29.
import
static
com.blueeagle.Constants.TIME;
30.
31.
import
static
com.blueeagle.Constants.TITLE;
32.
33.
import
static
android.provider.BaseColumns._ID;
34.
35.
36.
37.
public
class
MySQLite extends SQLiteOpenHelper {
38.
39.
40.
41.
private
static
finalString DATABASE_NAME =
"MySQLite.db"
;
42.
43.
private
static
final
int
DATABASE_VERSION = 1;
44.
45.
46.
47.
public
MySQLite(Context context,String name,CursorFactory factory,
48.
49.
int
version) {
50.
51.
super(context,DATABASE_NAME,factory,DATABASE_VERSION);
52.
53.
// TODO Auto-generated constructor stub
54.
55.
}
56.
57.
58.
59.
@Override
60.
61.
public
void
onCreate(SQLiteDatabase db) {
62.
63.
// TODO Auto-generated method stub
64.
65.
db.execSQL(
"CREATE TABLE "
+ TABLE_NAME+
"("
+_ID+
"INTEGER PRIMARY KEY AUTOINCREMENT,"
+TIME+
"INTEGER,"
+TITLE+
"TEXT NOT NULL);"
);
66.
67.
}
68.
69.
70.
71.
@Override
72.
73.
public
void
onUpgrade(SQLiteDatabase db,
int
oldVersion,
int
newVersion) {
74.
75.
// TODO Auto-generated method stub
76.
77.
db.execSQL(
"DROP TABLE IF EXISTS"
+ TABLE_NAME);
78.
79.
onCreate(db);
80.
81.
82.
83.
}
84.
85.
}
首次访问数据库时,SQLiteOpenHelper将注意到该数据库不存在,并调用onCreate()方法来创建它。
定义Activity主程序。
在HelloSQLite程序中做的第一次尝试是使用本地的SQLite数据库来存储事件,并将这些事件显示为TextView中的一个字符串。
布局xml文件如下:
view sourceprint?
01.
main.xml
02.
03.
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
04.
05.
<
ScrollView
xmlns:android
=
"http://schemas.android.com/apk/res/android"
06.
07.
android:layout_width
=
"fill_parent"
08.
09.
android:layout_height
=
"fill_parent"
10.
11.
>
12.
13.
<
TextView
14.
15.
android:id
=
"@+id/myText"
16.
17.
android:layout_width
=
"fill_parent"
18.
19.
android:layout_height
=
"wrap_content"
20.
21.
/>
22.
23.
</
ScrollView
>
这段代码使用一个ScrollView,以防太多的时间沾满了屏幕。
对于HelloSQLite.java的实现如下所示:相关代码有注释说明。
view sourceprint?
001.
package
com.blueeagle;
002.
003.
/*
004.
005.
* Android开发之数据保存技术(一)
006.
007.
* HelloSQLite.java
008.
009.
* Created on: 2011-8-16
010.
011.
* Author: blueeagle
012.
013.
* Email: liujiaxiang@gmail.com
014.
015.
*/
016.
017.
018.
019.
import
android.app.Activity;
020.
021.
import
android.content.ContentValues;
022.
023.
import
android.database.Cursor;
024.
025.
import
android.database.sqlite.SQLiteDatabase;
026.
027.
import
android.os.Bundle;
028.
029.
import
android.widget.TextView;
030.
031.
import
static
com.blueeagle.Constants.TABLE_NAME;
032.
033.
import
static
com.blueeagle.Constants.TIME;
034.
035.
import
static
com.blueeagle.Constants.TITLE;
036.
037.
import
static
android.provider.BaseColumns._ID;
038.
039.
040.
041.
public
class
HelloSQLite
extends
Activity {
042.
043.
/** Called when the activity is first created. */
044.
045.
private
MySQLite mySqlite;
046.
047.
private
static
String[] FROM={_ID,TIME,TITLE};
048.
049.
private
static
String ORDER_BY = TIME+
" DESC"
;
050.
051.
@Override
052.
053.
public
void
onCreate(Bundle savedInstanceState) {
054.
055.
super
.onCreate(savedInstanceState);
056.
057.
setContentView(R.layout.main);
058.
059.
060.
061.
mySqlite =
new
MySQLite(
this
);
062.
063.
try
{
064.
065.
addItem(
"Hello,Android!"
);
066.
067.
Cursor cursor = getItems();
068.
069.
showItems(cursor);
070.
071.
}
072.
073.
finally
{
074.
075.
mySqlite.close();
076.
077.
}
078.
079.
}
080.
081.
//显示查询结果
082.
083.
private
void
showItems(Cursor cursor) {
084.
085.
// TODO Auto-generated method stub
086.
087.
StringBuilder builder =
new
StringBuilder(
"Saved items:\n"
);
088.
089.
while
(cursor.moveToNext()){
090.
091.
long
id = cursor.getLong(
0
);
092.
093.
long
time = cursor.getLong(
1
);
094.
095.
String title = cursor.getColumnName(
2
);
096.
097.
builder.append(id).append(
": "
);
098.
099.
builder.append(time).append(
": "
);
100.
101.
builder.append(title).append(
"\n"
);
102.
103.
}
104.
105.
TextView myTextView = (TextView)findViewById(R.id.myText);
106.
107.
myTextView.setText(builder);
108.
109.
}
110.
111.
//此函数接受一个Cursor作为输入并格式化,以便用户能够理解输出的内容
112.
113.
//查询条目方法
114.
115.
private
Cursor getItems() {
116.
117.
// TODO Auto-generated method stub
118.
119.
SQLiteDatabase db = mySqlite.getReadableDatabase();
120.
121.
Cursor cursor = db.query(TABLE_NAME,FROM,
null
,
null
,
null
,
null
,ORDER_BY);
122.
123.
startManagingCursor(cursor);
124.
125.
return
cursor;
126.
127.
}
128.
129.
//因为查询不用修改数据库,因此利用只读句柄,然后调用查询的query方法来执行SQL语句,FROM是
130.
131.
//想要使用的列构成的数组,ORDER_BY告诉SQLite按照从新到旧的顺序返回查询结果。
132.
133.
//添加条目方法
134.
135.
private
void
addItem(String string) {
136.
137.
// TODO Auto-generated method stub
138.
139.
SQLiteDatabase db = mySqlite.getWritableDatabase();
140.
141.
ContentValues values =
new
ContentValues();
142.
143.
values.put(TIME,System.currentTimeMillis());
144.
145.
values.put(TITLE,string);
146.
147.
db.insert(TABLE_NAME,
null
,values);
148.
149.
}
150.
151.
//因为要修改数据库,所以调用getWritableDatabase()方法来获取数据库的一个写句柄,当然也可以获取
152.
153.
//到读句柄。
154.
155.
}
完成上述文件-》运行,即可看到结果显示出来。这样就完成了第一个Android数据库程序。
当然,这里面需要注意的问题:
1. 新建数据库的问题
新建数据库的时候,会遇到我们用:
db.execSQL("CREATE TABLE "+ TABLE_NAME+"("+_ID+"INTEGER PRIMARY KEY AUTOINCREMENT,"+TIME+"INTEGER,"+TITLE+"TEXT NOT NULL);");
这样一条语句。有时候SQL语句书写错误,比如少一个空格就会引起程序异常退出。这个时候建议在db.execSQL()函数中填写一个字符串变量,然后把自己写的SQL语句赋给字符串变量,在做赋值操作的时候,先打印出来看看,是不是少空格,多+号什么的小错误。
2. 版本号的问题
关于构造函数中的版本号,是有明确的说明的。
public MySQLite(Context context,String name,CursorFactory factory,int version) {
super(context,DATABASE_NAME,factory,DATABASE_VERSION);
这个版本号version当你有新版本的时候,则做更新操作,新版本的版本号将要比老版本高,如果此时修改版本号,不是向高修改,而是向低修改的话,程序就会发生异常了。
3. 数据库文件的问题
对于onCreate()方法,在程序运行的时候,只运行一次,运行的这一次就是去创建数据库。将数据库的文件存储在SD卡中。路径是data/data/包名/databases里。可以在Eclipse里通过DDMS里的File Explorer来查看。当数据库文件存在了以后,则不会再次运行。
完成了上述例子,就算完成了一个数据库的应用。但是如果列表中有数千个或者上百万个事件。程序将运行的非常慢。甚至于耗尽内存。解决办法就是数据绑定。
有了数据绑定,只需要几行代码,就可以将数据连接到视图,从而实现需求。为了演示,我们修改上面的实例。
将主程序HelloSQLite.java继承ListActivity而不是Activity。
将代码修改为如下所示:
view sourceprint?
001.
package com.blueeagle;
002.
003.
/*
004.
005.
* Android开发之数据保存技术
006.
007.
* HelloSQLite.java
008.
009.
* Created on: 2011-8-16
010.
011.
* Author: blueeagle
012.
013.
* Email: liujiaxiang@gmail.com
014.
015.
*/
016.
017.
import android.app.ListActivity;
018.
019.
import android.content.ContentValues;
020.
021.
import android.database.Cursor;
022.
023.
import android.database.sqlite.SQLiteDatabase;
024.
025.
import android.os.Bundle;
026.
027.
import android.widget.SimpleCursorAdapter;
028.
029.
import
static
com.blueeagle.Constants.TABLE_NAME;
030.
031.
import
static
com.blueeagle.Constants.TIME;
032.
033.
import
static
com.blueeagle.Constants.TITLE;
034.
035.
import
static
android.provider.BaseColumns._ID;
036.
037.
038.
039.
public
class
HelloSQLiteextends ListActivity {
040.
041.
/** Called when the activity is first created. */
042.
043.
private
MySQLite mySqlite;
044.
045.
private
static
String[] FROM={_ID,TIME,TITLE};
046.
047.
private
static
String ORDER_BY = TIME+
" DESC"
;
048.
049.
//@Override
050.
051.
public
void
onCreate(Bundle savedInstanceState) {
052.
053.
super.onCreate(savedInstanceState);
054.
055.
setContentView(R.layout.main);
056.
057.
058.
059.
mySqlite =
new
MySQLite(
this
);
060.
061.
try
{
062.
063.
addItem(
"Hello,Android!"
);
064.
065.
Cursor cursor = getItems();
066.
067.
showItems(cursor);
068.
069.
}
070.
071.
finally{
072.
073.
mySqlite.close();
074.
075.
}
076.
077.
}
078.
079.
private
static
int
[] TO = {R.id.rowid,R.id.
time
,R.id.title};
080.
081.
private
void
showItems(Cursor cursor) {
082.
083.
SimpleCursorAdapter adapter =
new
SimpleCursorAdapter(
this
,R.layout.item,cursor,FROM,TO);
084.
085.
//这里有必要说明一下SimpleCursorAdapter构造函数的5个参数。1.对应于当前Activity的引用,2.一个资源,它定义一个列表条目的视图,
086.
087.
//3.数据集光标,4.一组列名称,数据来源于这些列。5.视图列表,这是数据的目的地。
088.
089.
setListAdapter(adapter);
090.
091.
092.
093.
}
094.
095.
private
Cursor getItems() {
096.
097.
SQLiteDatabase db = mySqlite.getReadableDatabase();
098.
099.
Cursor cursor = db.query(TABLE_NAME,FROM,null,null,null,null,ORDER_BY);
100.
101.
startManagingCursor(cursor);
102.
103.
return
cursor;
104.
105.
}
106.
107.
private
void
addItem(String string) {
108.
109.
// TODO Auto-generated method stub
110.
111.
SQLiteDatabase db = mySqlite.getWritableDatabase();
112.
113.
ContentValues values =
new
ContentValues();
114.
115.
values.put(TIME,System.currentTimeMillis());
116.
117.
values.put(TITLE,string);
118.
119.
db.insert(TABLE_NAME,null,values);
120.
121.
}
122.
123.
}
将main.xml修改成如下形式:
view sourceprint?
01.
<?xml version=
"1.0"
encoding=
"utf-8"
?>
02.
03.
<LinearLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
04.
05.
android:layout_width=
"fill_parent"
06.
07.
android:layout_height=
"fill_parent"
08.
09.
>
10.
11.
<ListView
12.
13.
android:id=
"@android:id/list"
14.
15.
android:layout_height=
"wrap_content"
16.
17.
android:layout_width=
"wrap_content"
>
18.
19.
</ListView>
20.
21.
<TextView
22.
23.
android:id=
"@android:id/empty"
24.
25.
android:layout_height=
"wrap_content"
26.
27.
android:layout_width=
"wrap_content"
28.
29.
android:text=
"empty!"
>
30.
31.
</TextView>
32.
33.
</LinearLayout>
增加item.xml为如下:
view sourceprint?
01.
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
02.
03.
<
RelativeLayout
xmlns:android
=
"http://schemas.android.com/apk/res/android"
04.
05.
android:orientation
=
"horizontal"
06.
07.
android:layout_width
=
"fill_parent"
08.
09.
android:layout_height
=
"fill_parent"
10.
11.
android:padding
=
"10sp"
12.
13.
>
14.
15.
<
TextView
16.
17.
android:id
=
"@+id/rowid"
18.
19.
android:layout_height
=
"wrap_content"
20.
21.
android:layout_width
=
"wrap_content"
>
22.
23.
</
TextView
>
24.
25.
<
TextView
26.
27.
android:id
=
"@+id/rowidcolon"
28.
29.
android:layout_height
=
"wrap_content"
30.
31.
android:layout_width
=
"wrap_content"
32.
33.
android:text
=
":"
34.
35.
android:layout_toRightOf
=
"@id/rowid"
>
36.
37.
</
TextView
>
38.
39.
<
TextView
40.
41.
android:id
=
"@+id/time"
42.
43.
android:layout_height
=
"wrap_content"
44.
45.
android:layout_width
=
"wrap_content"
46.
47.
android:layout_toRightOf
=
"@id/rowidcolon"
>
48.
49.
</
TextView
>
50.
51.
<
TextView
52.
53.
android:id
=
"@+id/timecolon"
54.
55.
android:layout_height
=
"wrap_content"
56.
57.
android:layout_width
=
"wrap_content"
58.
59.
android:text
=
":"
60.
61.
android:layout_toRightOf
=
"@id/time"
>
62.
63.
</
TextView
>
64.
65.
<
TextView
66.
67.
android:id
=
"@+id/title"
68.
69.
android:layout_height
=
"fill_parent"
70.
71.
android:layout_width
=
"wrap_content"
72.
73.
android:textStyle
=
"italic"
74.
75.
android:ellipsize
=
"end"
76.
77.
android:singleLine
=
"true"
78.
79.
android:layout_toRightOf
=
"@id/timecolon"
>
80.
81.
</
TextView
>
82.
83.
</
RelativeLayout
>
运行结果如图所示。
当然,这只是一个DEMO,用来展示如何使用SQLite,这个DEMO存在一些问题。其他应用程序都不能向事件数据库添加内容,甚至于无法看这些事件。那么针对这一点,引出了Android里的内容提供器,即ContentProvider。
内容提供器
在Android安全模型中,一个应用程序编写的文件无法被其他任何应用程序所读写。每个应用程序都有自己的Linux用户ID和数据目录,以及其受保护的内存空间。Android程序可以通过下面两种方式进行彼此间的通信。
第一种是IPC(Inter-Process Communication,进程间通信):一个进程使用AIDL(Android Interface Definition Language,接口定义语言)和IBinder接口声明一个任意的API。调用该API时,将在进程间安全且有效地队参数进行编组。这项先进技术用于对后台Service线程进行远程过程调用。
第二种就是ContentProvider:进程在系统中将他们本身注册为某些数据类型的提供者。请求该信息时,Android就会通过一个固定的API调用这些进程,以它们认为合适的方式查询或者修改内容。
ContentProvider管理的任何信息部分都通过一个URI来寻址,这个URI类似于以下形式:content://authority/path/id
其中content://是标准要求的前缀。authority是提供者的名称,建议使用完全限定包名称,避免出现名称冲突。Path是提供者内部的一个虚拟目录,用于标识被请求的数据类型。Id是被请求的特定记录的主键,要请求获得具有特定类型的所有记录,可以省略此参数以及后面的斜杠。
Android已经内置提供了几个提供者,包括:
content://browser;
content://contacts;
content://media;
content://settings。
下面我们就来演示如何使用内容提供器。
将HelloSQLite程序改为使用内容提供器的。对于HelloSQLite的提供者,下面都是有效的URI:
content://com.blueeagle.HelloSQLite/3 ——表示取id为3的数据
content://com.blueeagle.HelloSQLite/ ——表示取所有数据
首先需要向Contants.java添加两个常量:
public static finalString AUTHORITY = "com.blueeagle.HelloSQLite";
public static finalUri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME);
接下来可以对HelloSQLite类做一些修改:
代码如下:
view sourceprint?
001.
package
com.blueeagle;
002.
003.
/*
004.
005.
* Android开发之数据保存技术(一)
006.
007.
* HelloSQLite.java
008.
009.
* Created on: 2011-8-16
010.
011.
* Author: blueeagle
012.
013.
* Email: liujiaxiang@gmail.com
014.
015.
*/
016.
017.
018.
019.
import
android.app.ListActivity;
020.
021.
import
android.content.ContentValues;
022.
023.
import
android.database.Cursor;
024.
025.
import
android.os.Bundle;
026.
027.
import
android.widget.SimpleCursorAdapter;
028.
029.
import
static
com.blueeagle.Constants.TIME;
030.
031.
import
static
com.blueeagle.Constants.TITLE;
032.
033.
import
static
com.blueeagle.Constants.CONTENT_URI;
034.
035.
import
static
android.provider.BaseColumns._ID;
036.
037.
038.
039.
public
class
HelloSQLite
extends
ListActivity {
040.
041.
/** Called when the activity is first created. */
042.
043.
//private <a href="http://www.it165.net/database/dbmy/" target="_blank" class="keylink">MySQL</a>ite mySqlite;
044.
045.
private
static
String[] FROM = {_ID,TIME,TITLE};
046.
047.
private
static
String ORDER_BY = TIME+
" DESC"
;
048.
049.
//@Override
050.
051.
public
void
onCreate(Bundle savedInstanceState) {
052.
053.
super
.onCreate(savedInstanceState);
054.
055.
setContentView(R.layout.main);
056.
057.
058.
059.
addItem(
"Hello,Android!"
);
060.
061.
Cursor cursor = getItems();
062.
063.
showItems(cursor);
064.
065.
}
066.
067.
private
static
int
[] TO = {R.id.rowid,R.id.time,R.id.title};
068.
069.
070.
071.
//显示查询结果
072.
073.
private
void
showItems(Cursor cursor) {
074.
075.
// TODO Auto-generated method stub
076.
077.
SimpleCursorAdapter adapter =
new
SimpleCursorAdapter(
this
,R.layout.item,cursor,FROM,TO);
078.
079.
setListAdapter(adapter);
080.
081.
}
082.
083.
//使用ContentProvider时,geiItems方法也化简了
084.
085.
private
Cursor getItems() {
086.
087.
return
managedQuery(CONTENT_URI,FROM,
null
,
null
,ORDER_BY);
088.
089.
}
090.
091.
//此处使用Activity.managedQuery()方法,将内容URI,感兴趣的列表和应该使用的排序顺序传递给该方法。
092.
093.
private
void
addItem(String string) {
094.
095.
ContentValues values =
new
ContentValues();
096.
097.
values.put(TIME,System.currentTimeMillis());
098.
099.
values.put(TITLE,string);
100.
101.
getContentResolver().insert(CONTENT_URI,values);
102.
103.
}
104.
105.
//没有了对getWriteableDatabase()的调用,对insertOrThrow的调用替换成了getContentResolver().insert(CONTENT_URI,values)
106.
107.
//我们使用了一个URI而不是用数据库句柄。
108.
109.
}
下面就是实现ContentProvider
ContentProvider是一个类似于Activity的高级对象,需要向系统进行声明。因此,实现ContentProvider的第一步是将其添加到AndroidManifest.xml文件中的<activity>标签之前。
其中android:name是类名,android:authorities是在内容URI中使用的字符串。接下来创建我们自己的ContentProvider类,为继承类。ContentProvider创建后就会被调用:public boolean onCreate()ContentProvider类中有6个继承而来的方法,需要实现:
具体来说需要实现ContentProvider 类中的6个抽象方法。
Cursor query(Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder):将查询的数据以Cursor 对象的形式返回。
Uri insert(Uri uri,ContentValues values):向 Content Provider中插入新数据记录,ContentValues 为数据记录的列名和列值映射。int update(Uri uri,ContentValues values,String selection,String[] selectionArgs):更新Content Provider中已存在的数据记录。int delete(Uri uri,String selection,String[] selectionArgs):从Content Provider中删除数据记录。
String getType(Uri uri):返回Content Provider中的数据( MIME )类型。
boolean onCreate():当 Content Provider 启动时被调用。
定义一个 URI 类型的静态常量,命名为CONTENT_URI。 必须为该常量对象定义一个唯一的URI字符串,一般的做法是将 ContentProvider子类的全称类名作为URI字符串。
定义每个字段的列名,如果采用的数据库存储系统为SQLite 数据库,数据表列名可以采用数据库中表的列名。不管数据表中有没有其他的唯一标识一个记录的字段,都应该定义一个"_id"字段 来唯一标识一个记录。模式使用 "INTEGER PRIMARY KEY AUTOINCREMENT" 自动更新 一般将这些列名字符串定义为静态常量,如"_id"字段名定义为一个名为"_ID" 值为 "_id" 的静态字符串对象。
创建好的一个Content Provider必须在AndroidManifest.xml中声明。
<provider android:name=".ItemsProvider"
android:authorities="com.blueeagle" />
其中name属性为ContentProvider 子类的全称类名,authorities 属性唯一标识了一个ContentProvider。还可以通过 setReadPermission() 和 setWritePermission() 来设置其操作权限。当然也可以再上面的 xml中加入 android:readPermission 或者 android: writePermission属性来控制其权限。
注意:因为ContentProvider可能被不同的进程和线程调用,所以这些方法必须是线程安全的。
然后需要使用UriMatcher,用于匹配Uri。
用法如下:
首先把需要匹配Uri路径全部给注册上:
对于Uri:
什么是URI?将其分为A,B,C,D 4个部分:
A:标准前缀,用来说明一个Content Provider控制这些数据,无法改变的;"content://"
B:URI的标识,它定义了是哪个Content Provider提供这些数据。对于第三方应用程序,为了保证URI标识的唯一性,它必须是一个完整的、小写的 类名。这个标识在 元素的 authorities属性中说明:一般是定义该ContentProvider的包.类的名称 ;"content://hx.android.text.myprovider"
C:路径,不知道是不是路径,通俗的讲就是你要操作的数据库中表的名字,或者你也可以自己定义,记得在使用的时候保持一致就ok了;"content://hx.android.text.myprovider/tablename"
D:如果URI中包含表示需要获取的记录的ID;则就返回该id对应的数据,如果没有ID,就表示返回全部; "content://hx.android.text.myprovider/tablename/#" #表示数据id
注册完需要匹配的Uri后,就可以使用sMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数,例如匹配content://com.blueeagle路径,返回的匹配码为1。
//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//添加需要匹配的uri,如果匹配就会返回匹配码
//如果match()方法匹配content://com.blueeagle路径,返回匹配码为1
sUriMatcher.addURI(“content://com.blueeagle”,“HelloSQLite”,1);
//如果match()方法匹配content://com.blueeagle/ ***路径,返回匹配码为2
//#号为通配符
sUriMatcher.addURI(“content://com.blueeagle”,“HelloSQLite/#”,2);
switch(sUriMatcher.match(Uri.parse("content://com.blueeagle /***"))) {
case 1 break;
case 2 break;
default:
//不匹配 break;
}
自定义的contentprovider如下:
ItemsProvider.java
view sourceprint?
001.
package
com.blueeagle;
002.
003.
/*
004.
005.
* Android开发之数据保存技术(一)
006.
007.
* ItemsProvider.java
008.
009.
* Created on: 2011-8-16
010.
011.
* Author: blueeagle
012.
013.
* Email: liujiaxiang@gmail.com
014.
015.
*/
016.
017.
018.
019.
import
android.content.ContentProvider;
020.
021.
import
android.content.ContentUris;
022.
023.
import
android.content.ContentValues;
024.
025.
import
android.content.UriMatcher;
026.
027.
import
android.database.Cursor;
028.
029.
import
android.database.sqlite.SQLiteDatabase;
030.
031.
import
android.net.Uri;
032.
033.
import
android.text.TextUtils;
034.
035.
import
static
com.blueeagle.Constants.CONTENT_URI;
036.
037.
import
static
com.blueeagle.Constants.TABLE_NAME;
038.
039.
import
static
com.blueeagle.Constants.AUTHORITY;
040.
041.
import
static
android.provider.BaseColumns._ID;
042.
043.
044.
045.
public
class
ItemsProvider
extends
ContentProvider {
046.
047.
048.
049.
private
static
final
int
ITEMS =
1
;
050.
051.
private
static
final
int
ITEMS_ID =
2
;
052.
053.
/** The MIME type of a directory of items */
054.
055.
private
static
final
String CONTENT_TYPE
056.
057.
=
"vnd.android.cursor.dir/vnd.com.blueeagle"
;
058.
059.
/** The MIME type of a single item */
060.
061.
private
static
final
String CONTENT_ITEM_TYPE
062.
063.
=
"vnd.android.cursor.item/vnd.com.blueeagle"
;
064.
065.
066.
067.
private
<a href=
"http://www.it165.net/database/dbmy/"
target=
"_blank"
class
=
"keylink"
>MySQL</a>ite myDataBase ;
068.
069.
private
UriMatcher myUriMatcher;
070.
071.
072.
073.
@Override
074.
075.
public
boolean
onCreate() {
076.
077.
myUriMatcher =
new
UriMatcher(UriMatcher.NO_MATCH);
078.
079.
myUriMatcher.addURI(AUTHORITY,
"HelloSQLite"
,ITEMS);
080.
081.
myUriMatcher.addURI(AUTHORITY,
"HelloSQLite/#"
,ITEMS_ID);
082.
083.
myDataBase =
new
MySQLite(getContext());
084.
085.
return
true
;
086.
087.
}
088.
089.
@Override
090.
091.
public
int
delete(Uri uri,String selection,
092.
093.
// TODO Auto-generated method stub
094.
095.
String[] selectionArgs) {
096.
097.
SQLiteDatabase db = myDataBase.getWritableDatabase();
098.
099.
int
count;
100.
101.
switch
(myUriMatcher.match(uri)) {
102.
103.
case
ITEMS:
104.
105.
count = db.delete(TABLE_NAME,selection,selectionArgs);
106.
107.
break
;
108.
109.
case
ITEMS_ID:
110.
111.
long
id = Long.parseLong(uri.getPathSegments().get(
1
));
112.
113.
count = db.delete(TABLE_NAME,appendRowId(selection,id),
114.
115.
selectionArgs);
116.
117.
break
;
118.
119.
default
:
120.
121.
throw
new
IllegalArgumentException(
"Unknown URI "
+ uri);
122.
123.
}
124.
125.
// Notify any watchers of the change
126.
127.
getContext().getContentResolver().notifyChange(uri,
null
);
128.
129.
return
count;
130.
131.
}
132.
133.
@Override
134.
135.
public
String getType(Uri uri) {
136.
137.
// TODO Auto-generated method stub
138.
139.
switch
(myUriMatcher.match(uri)) {
140.
141.
case
ITEMS:
142.
143.
return
CONTENT_TYPE;
144.
145.
case
ITEMS_ID:
146.
147.
return
CONTENT_ITEM_TYPE;
148.
149.
default
:
150.
151.
throw
new
IllegalArgumentException(
"Unknown URI "
+ uri);
152.
153.
}
154.
155.
}
156.
157.
@Override
158.
159.
public
Uri insert(Uri uri,ContentValues values) {
160.
161.
// TODO Auto-generated method stub
162.
163.
SQLiteDatabase db = myDataBase.getWritableDatabase();
164.
165.
// Validate the requested uri
166.
167.
if
(myUriMatcher.match(uri) != ITEMS) {
168.
169.
throw
new
IllegalArgumentException(
"Unknown URI "
+ uri);
170.
171.
}
172.
173.
// Insert into database
174.
175.
long
id = db.insertOrThrow(TABLE_NAME,
null
,values);
176.
177.
// Notify any watchers of the change
178.
179.
Uri newUri = ContentUris.withAppendedId(CONTENT_URI,id);
180.
181.
getContext().getContentResolver().notifyChange(newUri,
null
);
182.
183.
return
newUri;
184.
185.
}
186.
187.
@Override
188.
189.
public
Cursor query(Uri uri,String[] projection,
190.
191.
String selection,String[] selectionArgs,String orderBy) {
192.
193.
// TODO Auto-generated method stub
194.
195.
if
(myUriMatcher.match(uri) == ITEMS_ID) {
196.
197.
long
id = Long.parseLong(uri.getPathSegments().get(
1
));
198.
199.
selection = appendRowId(selection,id);
200.
201.
}
202.
203.
// Get the database and run the query
204.
205.
SQLiteDatabase db = myDataBase.getReadableDatabase();
206.
207.
Cursor cursor = db.query(TABLE_NAME,projection,selection,
208.
209.
selectionArgs,
null
,
null
,orderBy);
210.
211.
// Tell the cursor what uri to watch,so it knows when its
212.
213.
// source data changes
214.
215.
cursor.setNotificationUri(getContext().getContentResolver(),
216.
217.
uri);
218.
219.
return
cursor;
220.
221.
}
222.
223.
private
String appendRowId(String selection,
long
id) {
224.
225.
// TODO Auto-generated method stub
226.
227.
return
_ID +
"="
+ id
228.
229.
+ (!TextUtils.isEmpty(selection)
230.
231.
?
" AND ("
+ selection +
')'
232.
233.
:
""
);
234.
235.
}
236.
237.
@Override
238.
239.
public
int
update(Uri uri,ContentValues values,String selection,
240.
241.
String[] selectionArgs) {
242.
243.
// TODO Auto-generated method stub
244.
245.
SQLiteDatabase db = myDataBase.getWritableDatabase();
246.
247.
int
count;
248.
249.
switch
(myUriMatcher.match(uri)) {
250.
251.
case
ITEMS:
252.
253.
count = db.update(TABLE_NAME,values,selection,
254.
255.
selectionArgs);
256.
257.
break
;
258.
259.
case
ITEMS_ID:
260.
261.
long
id = Long.parseLong(uri.getPathSegments().get(
1
));
262.
263.
count = db.update(TABLE_NAME,values,appendRowId(
264.
265.
selection,id),selectionArgs);
266.
267.
break
;
268.
269.
default
:
270.
271.
throw
new
IllegalArgumentException(
"Unknown URI "
+ uri);
272.
273.
}
274.
275.
// Notify any watchers of the change
276.
277.
getContext().getContentResolver().notifyChange(uri,
null
);
278.
279.
return
count;
280.
281.
}
282.
283.
}
总结一下,创建一个新的内容提供器。
1.通过扩展抽象类ContentProvider可以创建新的内容提供器。重写onCreate方法来打开或者初始化将要使用这个新的提供器来提供底层数据源。
2.还应该提供那些用来返回指向这个提供器的完整的URI的公共静态CONTENT_URI变量。提供器之间的内容URI应该是唯一的,所以最好的做法是使URI路径以包名为基础。
定义一个内容提供器URI一般的形式为:
content://com.pakagename/datapath
例如:content://com.blueeagle/items
或者:content://com.blueeagle./items/3
内容URI可以表示为这两种形式中的任意一种形式。前面的一种URI表示对那种类型中所有的值的请求,后面附加一个/3的表示对一条记录的请求(这里请求的是记录3)。这两种形式来访问提供器都是可行的。
完成这项工作最简单的方式是使用一个UriMatcher。当通过内容解析器来访问内容提供器的时候,可以配置UriMathcer来解析URI以确定它们的形式。就像前文说的那样。
相关文章推荐
- Android开发之数据保存技术
- Android开发之数据保存技术(一)
- 【Android游戏开发十三】(保存游戏数据 [下文])详解SQLite存储方式,并把SQLite的数据库文件存储在SD卡中!!!
- Android开发日志之横竖屏切换数据保存问题
- Android开发技术:Android对图片的压缩读取和保存
- 【Android游戏开发十二】(保存游戏数据 [上文])详解SharedPreference 与 FIleInputStream/FileOutputStream将数据存储到SD卡中!
- [Android开发]Android数据保存之PreferenceActivity
- Adobe Flash Builder 4.5 Android Air 程序开发系列 之四 打开与关闭应用程序是的保存数据
- (转)【Android游戏开发十二】(保存游戏数据 [上文])详解SharedPreference 与 FIleInputStream/FileOutputStream将数据存储到SD卡中!
- 【Android游戏开发十三】(保存游戏数据 [下文])详解SQLite存储方式,并把SQLite的数据库文件存储在SD卡中!!!
- Android游戏开发12:使用SharedPreference与FileInputStream/FileOutputStream保存数据
- 【Android游戏开发十二】(保存游戏数据 [上文])详解SharedPreference 与 FIleInputStream/FileOutputStream将数据存储到SD卡中!
- 【Android游戏开发十三】(保存游戏数据 [下文])详解SQLite存储方式,并把SQLite的数据库文件存储在SD卡中!!!
- (转)【Android游戏开发十三】(保存游戏数据 [下文])详解SQLite存储方式,并把SQLite的数据库文件存储在SD卡中!!!
- Android开发中保存数据的四种方法
- 【Android游戏开发十三】(保存游戏数据 [下文])详解SQLite存储方式,并把SQLite的数据库文件存储在SD卡中!!!
- Android开发中保存数据的四种方法
- 【Android游戏开发十三】(保存游戏数据 [下文])详解SQLite存储方式,并把SQLite的数据库文件存储在SD卡中!!!
- Android开发中保存数据的四种方法方法
- 【Android游戏开发十二】(保存游戏数据 [上文])详解SharedPreference 与 FIleInputS