使用嵌入式关系型SQLite数据库存储数据
除了可以使用文件或SharedPreferences存储数据,还可以选择使用SQLite数据库存储数据。
在Android平台上,集成了一个嵌入式关系型数据库—SQLite,SQLite3支持 NULL、INTEGER、REAL(浮点数字)、TEXT(字符串文本)和BLOB(二进制对象)数据类型,虽然它支持的类型只有五种,但实际上sqlite3也接受varchar(n)、char(n)、decimal(p,s) 等数据类型,只不过在运算或保存时会转成对应的五种数据类型。 SQLite最大的特点是你可以把各种类型的数据保存到任何字段中,而不用关心字段声明的数据类型是什么。例如:可以在Integer类型的字段中存放字符串,或者在布尔型字段中存放浮点数,或者在字符型字段中存放日期型值。 但有一种情况例外:定义为INTEGER PRIMARY KEY的字段只能存储64位整数, 当向这种字段保存除整数以外的数据时,将会产生错误。 另外, SQLite 在解析CREATE TABLE 语句时,会忽略 CREATE TABLE 语句中跟在字段名后面的数据类型信息,如下面语句会忽略 name字段的类型信息:
CREATE TABLE person (personid integer primary key autoincrement, name varchar(20))
SQLite可以解析大部分标准SQL语句,如:
查询语句:select * from 表名 where 条件子句 group by 分组字句 having ... order by 排序子句
如:select * from person
select * from person order by id desc
select name from person group by name having count(*)>1
分页SQL与mysql类似,下面SQL语句获取5条记录,跳过前面3条记录
select * from Account limit 5 offset 3 或者 select * from Account limit 3,5
插入语句:insert into 表名(字段列表) values(值列表)。如: insert into person(name, age) values(‘传智’,3)
更新语句:update 表名 set 字段名=值 where 条件子句。如:update person set name=‘传智‘ where id=10
删除语句:delete from 表名 where 条件子句。如:delete from person where id=10
使用SQLiteOpenHelper对数据库进行版本管理
我们在编写数据库应用软件时,需要考虑这样的问题:因为我们开发的软件可能会安装在很多用户的手机上,如果应用使用到了SQLite数据库,我们必须在用户初次使用软件时创建出应用使用到的数据库表结构及添加一些初始化记录,另外在软件升级的时候,也需要对数据表结构进行更新。那么,我们如何才能实现在用户初次使用或升级软件时自动在用户的手机上创建出应用需要的数据库表呢?总不能让我们在每个需要安装此软件的手机上通过手工方式创建数据库表吧?因为这种需求是每个数据库应用都要面临的,所以在Android系统,为我们提供了一个名为SQLiteOpenHelper的抽象类,必须继承它才能使用,它是通过对数据库版本进行管理来实现前面提出的需求。
为了实现对数据库版本进行管理,SQLiteOpenHelper类提供了两个重要的方法,分别是onCreate(SQLiteDatabase db)和onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion),前者用于初次使用软件时生成数据库表,后者用于升级软件时更新数据库表结构。当调用SQLiteOpenHelper的getWritableDatabase()或者getReadableDatabase()方法获取用于操作数据库的SQLiteDatabase实例的时候,如果数据库不存在,Android系统会自动生成一个数据库,接着调用onCreate()方法,onCreate()方法在初次生成数据库时才会被调用,在onCreate()方法里可以生成数据库表结构及添加一些应用使用到的初始化数据。onUpgrade()方法在数据库的版本发生变化时会被调用,一般在软件升级时才需改变版本号,而数据库的版本是由程序员控制的,假设数据库现在的版本是1,由于业务的变更,修改了数据库表结构,这时候就需要升级软件,升级软件时希望更新用户手机里的数据库表结构,为了实现这一目的,可以把原来的数据库版本设置为2(有同学问设置为3行不行?当然可以,如果你愿意,设置为100也行),并且在onUpgrade()方法里面实现表结构的更新。当软件的版本升级次数比较多,这时在onUpgrade()方法里面可以根据原版号和目标版本号进行判断,然后作出相应的表结构及数据更新。
getWritableDatabase()和getReadableDatabase()方法都可以获取一个用于操作数据库的SQLiteDatabase实例。但getWritableDatabase() 方法以读写方式打开数据库,一旦数据库的磁盘空间满了,数据库就只能读而不能写,倘若使用的是getWritableDatabase() 方法就会出错。getReadableDatabase()方法先以读写方式打开数据库,如果数据库的磁盘空间满了,就会打开失败,当打开失败后会继续尝试以只读方式打开数据库。
//类没有实例化,是不能用作父类构造器的参数,必须声明为静态
private static final String name = "itcast"; //数据库名称
private static final int version = 1; //数据库版本
public DatabaseHelper( Context context) {
//第三个参数CursorFactory指定在执行查询时获得一个游标实例的工厂类,设置为null,代表使用系统默认的工厂类
super( context , name , null , version);
}
@Override public void onCreate( SQLiteDatabase db) {
db . execSQL( "CREATE TABLE IF NOT EXISTS person (personid integer primary key autoincrement, name varchar(20), age INTEGER)");
}
@Override public void onUpgrade( SQLiteDatabase db , int oldVersion , int newVersion) {
db . execSQL( "DROP TABLE IF EXISTS person");
onCreate( db);
}
}
上面onUpgrade()方法在数据库版本每次发生变化时都会把用户手机上的数据库表删除,然后再重新创建。一般在实际项目中是不能这样做的,正确的做法是在更新数据库表结构时,还要考虑用户存放于数据库中的数据不会丢失。
使用SQLiteDatabase操作SQLite数据库
Android提供了一个名为SQLiteDatabase的类,该类封装了一些操作数据库的API,使用该类可以完成对数据进行添加(Create)、查询(Retrieve)、更新(Update)和删除(Delete)操作(这些操作简称为CRUD)。对SQLiteDatabase的学习,我们应该重点掌握execSQL()和rawQuery()方法。 execSQL()方法可以执行insert、delete、update和CREATE TABLE之类有更改行为的SQL语句; rawQuery()方法用于执行select语句。
execSQL()方法的使用例子:
SQLiteDatabase db = ....;
db.execSQL("insert into person(name, age) values('传智播客', 4)");
db.close();
执行上面SQL语句会往person表中添加进一条记录,在实际应用中, 语句中的“传智播客”这些参数值会由用户输入界面提供,如果把用户输入的内容原样组拼到上面的insert语句, 当用户输入的内容含有单引号时,组拼出来的SQL语句就会存在语法错误。要解决这个问题需要对单引号进行转义,也就是把单引号转换成两个单引号。有些时候用户往往还会输入像“ & ”这些特殊SQL符号,为保证组拼好的SQL语句语法正确,必须对SQL语句中的这些特殊SQL符号都进行转义,显然,对每条SQL语句都做这样的处理工作是比较烦琐的。 SQLiteDatabase类提供了一个重载后的execSQL(String sql, Object[] bindArgs)方法,使用这个方法可以解决前面提到的问题,因为这个方法支持使用占位符参数(?)。使用例子如下:
SQLiteDatabase db = ....;
db.execSQL("insert into person(name, age) values(?,?)", new Object[]{"传智播客", 4});
db.close();
execSQL(String sql, Object[] bindArgs)方法的第一个参数为SQL语句,第二个参数为SQL语句中占位符参数的值,参数值在数组中的顺序要和占位符的位置对应。
Cursor cursor = db . rawQuery( “ select * from person ” , null);
while ( cursor . moveToNext()) {
int personid = cursor . getInt( 0); //获取第一列的值,第一列的索引从0开始
String name = cursor . getString( 1); //获取第二列的值
int age = cursor . getInt( 2); //获取第三列的值
}
cursor . close();
db . close();
rawQuery()方法的第一个参数为select语句;第二个参数为select语句中占位符参数的值,如果select语句没有使用占位符,该参数可以设置为null。带占位符参数的select语句使用例子如下:
Cursor cursor = db.rawQuery("select * from person where name like ? and age=?", new String[]{"%传智%", "4"});
Cursor是结果集游标,用于对结果集进行随机访问,如果大家熟悉jdbc, 其实Cursor与JDBC中的ResultSet作用很相似。使用moveToNext()方法可以将游标从当前行移动到下一行,如果已经移过了结果集的最后一行,返回结果为false,否则为true。另外Cursor 还有常用的moveToPrevious()方法(用于将游标从当前行移动到上一行,如果已经移过了结果集的第一行,返回值为false,否则为true )、moveToFirst()方法(用于将游标移动到结果集的第一行,如果结果集为空,返回值为false,否则为true )和moveToLast()方法(用于将游标移动到结果集的最后一行,如果结果集为空,返回值为false,否则为true ) 。
除了前面给大家介绍的execSQL()和rawQuery()方法, SQLiteDatabase还专门提供了对应于添加、删除、更新、查询的操作方法: insert()、delete()、update()和query() 。这些方法实际上是给那些不太了解SQL语法的菜鸟使用的,对于熟悉SQL语法的程序员而言,直接使用execSQL()和rawQuery()方法执行SQL语句就能完成数据的添加、删除、更新、查询操作。
Insert()方法用于添加数据,各个字段的数据使用ContentValues进行存放。 ContentValues类似于MAP,相对于MAP,它提供了存取数据对应的put(String key, Xxx value)和getAsXxx(String key)方法, key为字段名称,value为字段值,Xxx指的是各种常用的数据类型,如:String、Integer等。
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name", "传智播客");
values.put("age", 4);
long rowid = db.insert(“person”, null, values);//返回新添记录的行号,与主键id无关
不管第三个参数是否包含数据,执行Insert()方法必然会添加一条记录,如果第三个参数为空,会添加一条除主键之外其他字段值为Null的记录。Insert()方法内部实际上通过构造insert SQL语句完成数据的添加,Insert()方法的第二个参数用于指定空值字段的名称,相信大家对该参数会感到疑惑,该参数的作用是什么?是这样的:如果第三个参数values 为Null或者元素个数为0, 由于Insert()方法要求必须添加一条除了主键之外其它字段为Null值的记录,为了满足SQL语法的需要, insert语句必须给定一个字段名,如:insert into person(name) values(NULL),倘若不给定字段名 , insert语句就成了这样: insert into person() values(),显然这不满足标准SQL的语法。对于字段名,建议使用主键之外的字段,如果使用了INTEGER类型的主键字段,执行类似insert into person(personid) values(NULL)的insert语句后,该主键字段值也不会为NULL。如果第三个参数values 不为Null并且元素的个数大于0 ,可以把第二个参数设置为null。
delete()方法的使用:
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.delete("person", "personid<?", new String[]{"2"});
db.close();
上面代码用于从person表中删除personid小于2的记录。
update()方法的使用:
SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(“name”, “传智播客”);//key为字段名,value为值
db.update("person", values, "personid=?", new String[]{"1"});
db.close();
上面代码用于把person表中personid等于1的记录的name字段的值改为“传智播客”。
query()方法实际上是把select语句拆分成了若干个组成部分,然后作为方法的输入参数:
SQLiteDatabase db = databaseHelper.getWritableDatabase();
Cursor cursor = db.query("person", new String[]{"personid,name,age"}, "name like ?", new String[]{"%传智%"}, null, null, "personid desc", "1,2");
while (cursor.moveToNext()) {
int personid = cursor.getInt(0); //获取第一列的值,第一列的索引从0开始
String name = cursor.getString(1);//获取第二列的值
int age = cursor.getInt(2);//获取第三列的值
}
cursor.close();
db.close();
上面代码用于从person表中查找name字段含有“传智”的记录,匹配的记录按personid降序排序,对排序后的结果略过第一条记录,只获取2条记录。
query(table, columns, selection, selectionArgs, groupBy, having, orderBy, limit)方法各参数的含义:
table:表名。相当于select语句from关键字后面的部分。如果是多表联合查询,可以用逗号将两个表名分开。
columns:要查询出来的列名。相当于select语句select关键字后面的部分。
selection:查询条件子句,相当于select语句where关键字后面的部分,在条件子句允许使用占位符“?”
selectionArgs:对应于selection语句中占位符的值,值在数组中的位置与占位符在语句中的位置必须一致,否则就会有异常。
groupBy:相当于select语句group by关键字后面的部分
having:相当于select语句having关键字后面的部分
orderBy:相当于select语句order by关键字后面的部分,如:personid desc, age asc;
limit:指定偏移量和获取的记录数,相当于select语句limit关键字后面的部分。
使用SQLiteOpenHelper获取用于操作数据库的SQLiteDatabase实例
private static final String name = "itcast"; //数据库名称
private static final int version = 1; //数据库版本
...... 略
}
public class HelloActivity extends Activity {
@Override public void onCreate( Bundle savedInstanceState) {
......
Button button =( Button) this . findViewById( R . id . button);
button . setOnClickListener( new View . OnClickListener (){
public void onClick( View v) {
DatabaseHelper databaseHelper = new DatabaseHelper( HelloActivity . this);
SQLiteDatabase db = databaseHelper . getWritableDatabase();
db . execSQL( "insert into person(name, age) values(?,?)" , new Object []{ "传智播客" , 4 });
db . close();
}});
}
}
第一次调用getWritableDatabase()或getReadableDatabase()方法后,SQLiteOpenHelper会缓存当前的SQLiteDatabase实例,SQLiteDatabase实例正常情况下会维持数据库的打开状态,所以在你不再需要SQLiteDatabase实例时,请及时调用close()方法释放资源。一旦SQLiteDatabase实例被缓存,多次调用getWritableDatabase()或getReadableDatabase()方法得到的都是同一实例。
使用事务操作SQLite数据库
使用SQLiteDatabase的beginTransaction()方法可以开启一个事务,程序执行到endTransaction() 方法时会检查事务的标志是否为成功,如果程序执行到endTransaction()之前调用了setTransactionSuccessful() 方法设置事务的标志为成功则提交事务,如果没有调用setTransactionSuccessful() 方法则回滚事务。使用例子如下:
db . beginTransaction(); //开始事务
try {
db . execSQL( "insert into person(name, age) values(?,?)" , new Object []{ "传智播客" , 4 });
db . execSQL( "update person set name=? where personid=?" , new Object []{ "传智" , 1 });
db . setTransactionSuccessful(); //调用此方法会在执行到endTransaction() 时提交当前事务,如果不调用此方法会回滚事务
} finally {
db . endTransaction(); //由事务的标志决定是提交事务,还是回滚事务
}
db . close();
上面两条SQL语句在同一个事务中执行。
使用ContentProvider共享数据
当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。以前我们学习过文件的操作模式,通过指定文件的操作模式为Context.MODE_WORLD_READABLE 或Context.MODE_WORLD_WRITEABLE同样可以对外共享数据,但数据的访问方式会因数据存储的方式而不同,如:采用xml文件对外共享数据,需要进行xml解析来读写数据;采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据。而使用ContentProvider共享数据的好处是统一了数据访问方式。
当应用需要通过ContentProvider对外共享数据时,第一步需要继承ContentProvider并重写下面方法:
public class PersonContentProvider extends ContentProvider{
public boolean onCreate()
public Uri insert(Uri uri, ContentValues values)
public int delete(Uri uri, String selection, String[] selectionArgs)
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
public String getType(Uri uri)}
第二步需要在AndroidManifest.xml使用<provider>对该ContentProvider进行配置,为了能让其他应用找到该ContentProvider , ContentProvider 采用了authorities(主机名/域名)对它进行唯一标识,你可以把 ContentProvider看作是一个网站(想想,网站也是提供数据者),authorities 就是他的域名:
<manifest .... >
<application android:icon="@drawable/icon" android:label="@string/app_name">
<provider android:name=".PersonContentProvider" android:authorities="cn.itcast.providers.personprovider"/>
</application>
</manifest>
注意:一旦应用继承了ContentProvider类,后面我们就会把这个应用称为ContentProvider(内容提供者)。
package cn.itcast.db;
import cn.itcast.service.DBOpenHelper;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
public class PersonContentProvider extends ContentProvider {
private static UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
private static final int PERSONS = 1;
private static final int PERSON = 2;
private DBOpenHelper dbOpenHelper;
static{
matcher.addURI("cn.itcast.providers.personprovider", "person", PERSONS);
matcher.addURI("cn.itcast.providers.personprovider", "person/#", PERSON);
}
@Override
public boolean onCreate() {
dbOpenHelper = new DBOpenHelper(this.getContext());
return true;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
int num = 0 ;//已经删除的记录数量
switch (matcher.match(uri)) {
case PERSONS:
num = db.delete("person", selection, selectionArgs);
break;
case PERSON:
long id = ContentUris.parseId(uri);
String where = "personid="+ id;
if(selection!=null && !"".equals(selection)){ // personid=12 and name=?
where = where + " and "+ selection;
}
num = db.delete("person", where, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unkown Uri:"+ uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return num;
}
@Override
public String getType(Uri uri) {//返回当前操作的数据类型
switch (matcher.match(uri)) {
case PERSONS://操作的是集合类型数据
return "vnd.android.cursor.dir/person";
case PERSON:
return "vnd.android.cursor.item/person";
default:
throw new IllegalArgumentException("Unkown Uri:"+ uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
long id = 0 ;
switch (matcher.match(uri)) {
case PERSONS:
id = db.insert("person", "personid", values);//得到记录的id
getContext().getContentResolver().notifyChange(uri, null);
return ContentUris.withAppendedId(uri, id);//返回代表新增记录的Uri
case PERSON:
id = db.insert("person", "personid", values);//得到记录的id
String strUri = uri.toString();
Uri personUri = Uri.parse(strUri.substring(0, strUri.lastIndexOf("/")));
getContext().getContentResolver().notifyChange(personUri, null);
return ContentUris.withAppendedId(personUri, id);
default:
throw new IllegalArgumentException("Unkown Uri:"+ uri);
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
switch (matcher.match(uri)) {
case PERSONS:
return db.query("person", projection, selection, selectionArgs, null, null, sortOrder);
case PERSON:
long id = ContentUris.parseId(uri);
String where = "personid="+ id;
if(selection!=null && !"".equals(selection)){ // personid=12 and name=?
where = where + " and "+ selection;
}
return db.query("person", projection, where, selectionArgs, null, null, sortOrder);
default:
throw new IllegalArgumentException("Unkown Uri:"+ uri);
}
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
int num = 0 ;//已经修改的记录数量
switch (matcher.match(uri)) {
case PERSONS:
num = db.update("person", values, selection, selectionArgs);
break;
case PERSON:
long id = ContentUris.parseId(uri);
String where = "personid="+ id;
if(selection!=null && !"".equals(selection)){
where = where + " and "+ selection;
}
num = db.update("person", values, where, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unkown Uri:"+ uri);
}
getContext().getContentResolver().notifyChange(uri, null);//通知数据发生变化
return num;
}
}
Uri介绍
Uri代表了要操作的数据,Uri主要包含了两部分信息:1》需要操作的ContentProvider ,2》对ContentProvider中的什么数据进行操作,一个Uri由以下几部分组成:
ContentProvider(内容提供者)的scheme已经由Android所规定, scheme为:content://
主机名(或叫Authority)用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。
路径(path)可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:
要操作person表中id为10的记录,可以构建这样的路径:/person/10
要操作person表中id为10的记录的name字段, person/10/name
要操作person表中的所有记录,可以构建这样的路径:/person
要操作xxx表中的记录,可以构建这样的路径:/xxx
当然要操作的数据不一定来自数据库,也可以是文件、xml或网络等其他存储方式,如下:
要操作xml文件中person节点下的name节点,可以构建这样的路径:/person/name
如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:
Uri uri = Uri.parse("content://cn.itcast.provider.personprovider/person")
UriMatcher类使用介绍
因为Uri代表了要操作的数据,所以我们经常需要解析Uri,并从Uri中获取数据。Android系统提供了两个用于操作Uri的工具类,分别为UriMatcher 和ContentUris 。掌握它们的使用,会便于我们的开发工作。
UriMatcher类用于匹配Uri,它的用法如下:
首先第一步把你需要匹配Uri路径全部给注册上,如下:
//常量UriMatcher.NO_MATCH表示不匹配任何路径的返回码
UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//如果match()方法匹配content://cn.itcast.provider.personprovider/person路径,返回匹配码为1
sMatcher.addURI(“cn.itcast.provider.personprovider”, “person”, 1);//添加需要匹配uri,如果匹配就会返回匹配码
//如果match()方法匹配content://cn.itcast.provider.personprovider/person/230路径,返回匹配码为2
sMatcher.addURI(“cn.itcast.provider.personprovider”, “person/#”, 2);//#号为通配符
switch (sMatcher.match(Uri.parse("content://cn.itcast.provider.personprovider/person/10"))) {
case 1
break;
case 2
break;
default://不匹配
break;
}
注册完需要匹配的Uri后,就可以使用sMatcher.match(uri)方法对输入的Uri进行匹配,如果匹配就返回匹配码,匹配码是调用addURI()方法传入的第三个参数,假设匹配content://cn.itcast.provider.personprovider/person路径,返回的匹配码为1.
ContentUris类使用介绍
ContentUris类用于获取Uri路径后面的ID部分,它有两个比较实用的方法:
withAppendedId(uri, id)用于为路径加上ID部分:
Uri uri = Uri.parse("content://cn.itcast.provider.personprovider/person")
Uri resultUri = ContentUris.withAppendedId(uri, 10);
//生成后的Uri为:content://cn.itcast.provider.personprovider/person/10
parseId(uri)方法用于从路径中获取ID部分:
Uri uri = Uri.parse("content://cn.itcast.provider.personprovider/person/10")
long personid = ContentUris.parseId(uri);//获取的结果为:10
使用ContentProvider共享数据
ContentProvider类主要方法的作用:
public boolean onCreate()
该方法在ContentProvider创建后就会被调用, Android开机后, ContentProvider在其它应用第一次访问它时才会被创建。
public Uri insert(Uri uri, ContentValues values)
该方法用于供外部应用往ContentProvider添加数据。
public int delete(Uri uri, String selection, String[] selectionArgs)
该方法用于供外部应用从ContentProvider删除数据。
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
该方法用于供外部应用更新ContentProvider中的数据。
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
该方法用于供外部应用从ContentProvider中获取数据。
public String getType(Uri uri)
该方法用于返回当前Url所代表数据的MIME类型。如果操作的数据属于集合类型,那么MIME类型字符串应该以vnd.android.cursor.dir/开头,例如:要得到所有person记录的Uri为content://cn.itcast.provider.personprovider/person,那么返回的MIME类型字符串应该为:“vnd.android.cursor.dir/person”。如果要操作的数据属于非集合类型数据,那么MIME类型字符串应该以vnd.android.cursor.item/开头,例如:得到id为10的person记录,Uri为content://cn.itcast.provider.personprovider/person/10,那么返回的MIME类型字符串应该为:“vnd.android.cursor.item/person”。
使用ContentResolver操作ContentProvider中的数据
当外部应用需要对ContentProvider中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver 类来完成,要获取ContentResolver 对象,可以使用Activity提供的getContentResolver()方法。 ContentResolver 类提供了与ContentProvider类相同签名的四个方法:
public Uri insert(Uri uri, ContentValues values)
该方法用于往ContentProvider添加数据。
public int delete(Uri uri, String selection, String[] selectionArgs)
该方法用于从ContentProvider删除数据。
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
该方法用于更新ContentProvider中的数据。
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
该方法用于从ContentProvider中获取数据。
这些方法的第一个参数为Uri,代表要操作的ContentProvider和对其中的什么数据进行操作,假设给定的是: Uri.parse(“content://cn.itcast.providers.personprovider/person/10”),那么将会对主机名为cn.itcast.providers.personprovider的ContentProvider进行操作,操作的数据为person表中id为10的记录。
ContentResolver resolver = getContentResolver();
Uri uri = Uri . parse( "content://cn.itcast.provider.personprovider/person");
//添加一条记录
ContentValues values = new ContentValues();
values . put( "name" , "itcast");
values . put( "age" , 25);
resolver . insert( uri , values);
//获取person表中所有记录
Cursor cursor = resolver . query( uri , null , null , null , "personid desc");
while( cursor . moveToNext ()){
Log . i( "ContentTest" , "personid=" + cursor . getInt( 0 )+ ",name=" + cursor . getString( 1));
}
//把id为1的记录的name字段值更改新为liming
ContentValues updateValues = new ContentValues();
updateValues . put( "name" , "liming");
Uri updateIdUri = ContentUris . withAppendedId( uri , 2);
resolver . update( updateIdUri , updateValues , null , null);
//删除id为2的记录
Uri deleteIdUri = ContentUris . withAppendedId( uri , 2);
resolver . delete( deleteIdUri , null , null);
< uses - permission android: name = "android.permission.READ_CONTACTS" /> content: //com.android.contacts/contacts 操作的数据是联系人信息Uri
content: //com.android.contacts/data/phones 联系人电话Uri
content: //com.android.contacts/data/emails 联系人Email Uri 读取联系人信息
Cursor cursor = getContentResolver (). query( ContactsContract . Contacts . CONTENT_URI ,
null , null , null , null);
while ( cursor . moveToNext()) {
String contactId = cursor . getString( cursor . getColumnIndex( ContactsContract . Contacts . _ID));
String name = cursor . getString( cursor . getColumnIndex( ContactsContract . Contacts . DISPLAY_NAME)); Cursor phones = getContentResolver (). query( ContactsContract . CommonDataKinds . Phone . CONTENT_URI ,
null ,
ContactsContract . CommonDataKinds . Phone . CONTACT_ID + " = " + contactId ,
null , null);
while ( phones . moveToNext()) {
String phoneNumber = phones . getString( phones . getColumnIndex(
ContactsContract . CommonDataKinds . Phone . NUMBER));
Log . i( "RongActivity" , "phoneNumber=" + phoneNumber);
}
phones . close(); Cursor emails = getContentResolver (). query( ContactsContract . CommonDataKinds . Email . CONTENT_URI ,
null ,
ContactsContract . CommonDataKinds . Email . CONTACT_ID + " = " + contactId ,
null , null);
while ( emails . moveToNext()) {
// This would allow you get several email addresses
String emailAddress = emails . getString( emails . getColumnIndex( ContactsContract . CommonDataKinds . Email . DATA));
Log . i( "RongActivity" , "emailAddress=" + emailAddress);
}
emails . close();
}
cursor . close();
通信录操作
使用ContentResolver对通信录中的数据进行添加、删除、修改和查询操作:
加入读写联系人信息的权限
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.WRITE_CONTACTS" />
< uses - permission android: name = "android.permission.READ_CONTACTS" />
content://com.android.contacts/contacts 操作的数据是联系人信息Uri
content://com.android.contacts/data/phones 联系人电话Uri
content://com.android.contacts/data/emails 联系人Email Uri
读取联系人信息
Cursor cursor = getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,
null, null, null, null);
while (cursor.moveToNext()) {
String contactId = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));
String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
Cursor phones = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = "+ contactId,
null, null);
while (phones.moveToNext()) {
String phoneNumber = phones.getString(phones.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER));
Log.i("RongActivity", "phoneNumber="+phoneNumber);
}
phones.close();
Cursor emails = getContentResolver().query(ContactsContract.CommonDataKinds.Email.CONTENT_URI,
null,
ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = " + contactId,
null, null);
while (emails.moveToNext()) {
// This would allow you get several email addresses
String emailAddress =emails.getString(emails.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA));
Log.i("RongActivity", "emailAddress="+ emailAddress);
}
emails.close();
}
cursor.close();
==================== 添加联系人 ===========================
方法一:
/**
* 首先向RawContacts.CONTENT_URI执行一个空值插入,目的是获取系统返回的rawContactId
* 这时后面插入data表的依据,只有执行空值插入,才能使插入的联系人在通讯录里面可见
*/
public void testInsert() {
ContentValues values = new ContentValues();
//首先向RawContacts.CONTENT_URI执行一个空值插入,目的是获取系统返回的rawContactId
Uri rawContactUri = this.getContext().getContentResolver().insert(RawContacts.CONTENT_URI, values);
long rawContactId = ContentUris.parseId(rawContactUri);
//往data表入姓名数据
values.clear();
values.put(Data.RAW_CONTACT_ID, rawContactId);
values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);//内容类型
values.put(StructuredName.GIVEN_NAME, "李天山");
this.getContext().getContentResolver().insert(android.provider.ContactsContract.Data.CONTENT_URI,values);
//往data表入电话数据
values.clear();
values.put(Data.RAW_CONTACT_ID, rawContactId);
values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
values.put(Phone.NUMBER, "13921009789");
values.put(Phone.TYPE, Phone.TYPE_MOBILE);
this.getContext().getContentResolver().insert(android.provider.ContactsContract.Data.CONTENT_URI,values);
//往data表入Email数据
values.clear();
values.put(Data.RAW_CONTACT_ID, rawContactId);
values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
values.put(Email.DATA, "liming@itcast.cn");
values.put(Email.TYPE, Email.TYPE_WORK);
this.getContext().getContentResolver().insert(android.provider.ContactsContract.Data.CONTENT_URI,values);
}
方法二:批量添加,处于同一个事务中
public void testSave() throws Throwable{
//文档位置:reference\android\provider\ContactsContract.RawContacts.html
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
int rawContactInsertIndex = ops.size();
ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)
.withValue(RawContacts.ACCOUNT_TYPE, null)
.withValue(RawContacts.ACCOUNT_NAME, null)
.build());
//文档位置:reference\android\provider\ContactsContract.Data.html
ops.add(ContentProviderOperation.newInsert(android.provider.ContactsContract.Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)
.withValue(StructuredName.GIVEN_NAME, "赵薇")
.build());
ops.add(ContentProviderOperation.newInsert(android.provider.ContactsContract.Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)
.withValue(Phone.NUMBER, "13671323809")
.withValue(Phone.TYPE, Phone.TYPE_MOBILE)
.withValue(Phone.LABEL, "手机号")
.build());
ops.add(ContentProviderOperation.newInsert(android.provider.ContactsContract.Data.CONTENT_URI)
.withValueBackReference(Data.RAW_CONTACT_ID, rawContactInsertIndex)
.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE)
.withValue(Email.DATA, "liming@itcast.cn")
.withValue(Email.TYPE, Email.TYPE_WORK)
.build());
ContentProviderResult[] results = this.getContext().getContentResolver()
.applyBatch(ContactsContract.AUTHORITY, ops);
for(ContentProviderResult result : results){
Log.i(TAG, result.uri.toString());
}
}
当ContentProvider中的数据发生变化时可以向其用户发出通知
//可以在ContentProvider 发生数据变化时调用
//getContentResolver().notifyChange(uri, null)来通知注册在此URI上的访问者,例子如下:
public class PersonContentProvider extends ContentProvider {
public Uri insert( Uri uri , ContentValues values) {
db . insert( "person" , "personid" , values);
getContext (). getContentResolver (). notifyChange( uri , null);
}
}
//如果ContentProvider的访问者需要得到数据变化通知,必须使用ContentObserver对数据(数据采用uri描述)进行监听
//当监听到数据变化通知时,系统就会调用ContentObserver的onChange()方法:
getContentResolver (). registerContentObserver( Uri . parse( "content://cn.itcast.providers.personprovider/person" ),
true , new PersonObserver( new Handler()));
public class PersonObserver extends ContentObserver {
public PersonObserver( Handler handler) {
super( handler);
}
public void onChange( boolean selfChange) {
//此处可以进行相应的业务处理
}
}
从Internet获取数据
URL url = new URL( "http://www.sohu.com");
HttpURLConnection conn = ( HttpURLConnection) url . openConnection();
conn . setConnectTimeout( 5 * 1000); //设置连接超时
conn . setRequestMethod( “ GET ”); //以get方式发起请求
if ( conn . getResponseCode() != 200) throw new RuntimeException( "请求url失败");
InputStream is = conn . getInputStream(); //得到网络返回的输入流
String result = readData( is , "GBK");
conn . disconnect();
//第一个参数为输入流,第二个参数为字符集编码
public static String readData( InputStream inSream , String charsetName) throws Exception {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte [] buffer = new byte [ 1024 ];
int len = - 1;
while( ( len = inSream . read( buffer)) != - 1 ){
outStream . write( buffer , 0 , len);
}
byte [] data = outStream . toByteArray();
outStream . close();
inSream . close();
return new String( data , charsetName);
}
URL url = new URL( "http://photocdn.sohu.com/20100125/Img269812337.jpg");
HttpURLConnection conn = ( HttpURLConnection) url . openConnection();
conn . setConnectTimeout( 5 * 1000);
conn . setRequestMethod( "GET");
if ( conn . getResponseCode() != 200) throw new RuntimeException( "请求url失败");
InputStream is = conn . getInputStream();
readAsFile( is , "Img269812337.jpg"); public static void readAsFile( InputStream inSream , File file) throws Exception {
FileOutputStream outStream = new FileOutputStream( file);
byte [] buffer = new byte [ 1024 ];
int len = - 1;
while( ( len = inSream . read( buffer)) != - 1 ){
outStream . write( buffer , 0 , len);
}
outStream . close();
inSream . close();
}
多线程断点续传下载
使用多线程下载文件可以更快完成文件的下载,多线程下载文件之所以快,是因为其抢占的服务器资源多。如:假设服务器同时最多服务100个用户,在服务器中一条线程对应一个用户,100条线程在计算机中并非并发执行,而是由CPU划分时间片轮流执行,如果A应用使用了99条线程下载文件,那么相当于占用了99个用户的资源,假设一秒内CPU分配给每条线程的平均执行时间是10ms,A应用在服务器中一秒内就得到了990ms的执行时间,而其他应用在一秒内只有10ms的执行时间。就如同一个水龙头,每秒出水量相等的情况下,放990毫秒的水
肯定比放10毫秒的水要多。
多线程下载的实现过程:
1>首先得到下载文件的长度,然后设置本地文件
的长度。
HttpURLConnection.getContentLength();
RandomAccessFile file = new RandomAccessFile("QQWubiSetup.exe","rwd");
file.setLength(filesize);//设置本地文件的长度
2>根据文件长度和线程数计算每条线程下载的数据长度和下载位置。如:文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M,每条线程开始下载的位置如上图所示。
3>使用Http的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止,如:指定从文件的2M位置开始下载,下载到位置(4M-1byte)为止,代码如下:
HttpURLConnection.setRequestProperty("Range", "bytes=2097152-4194303");
4>保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据。
RandomAccessFile threadfile = new RandomAccessFile("QQWubiSetup.exe ","rwd");
threadfile.seek(2097152);//从文件的什么位置开始写入数据
@Test
public void download() throws Exception {
String path = "http://net.itcast.cn/QQWubiSetup.exe";
URL url = new URL( path);
HttpURLConnection conn = ( HttpURLConnection) url . openConnection();
conn . setConnectTimeout( 5 * 1000);
conn . setRequestMethod( "GET");
conn . setRequestProperty( "Accept" , "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
conn . setRequestProperty( "Accept-Language" , "zh-CN");
conn . setRequestProperty( "Charset" , "UTF-8");
conn . setRequestProperty( "User-Agent" , "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.2; Trident/4.0; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729)");
conn . setRequestProperty( "Connection" , "Keep-Alive");
System . out . println( conn . getResponseCode()); int filesize = conn . getContentLength(); //得到文件大小
conn . disconnect();
int threasize = 3; //线程数
int perthreadsize = filesize / 3 + 1;
RandomAccessFile file = new RandomAccessFile( "102.wma" , "rw");
file . setLength( filesize); //设置本地文件的大小
file . close();
for( int i = 0; i < threasize ; i ++){
int startpos = i * perthreadsize; //计算每条线程的下载位置
RandomAccessFile perthreadfile = new RandomAccessFile( "102.wma" , "rw"); //
perthreadfile . seek( startpos); //从文件的什么位置开始写入数据
new DownladerThread( i , path , startpos , perthreadsize , perthreadfile ). start();
}
//以下代码要求用户输入q才会退出测试方法,如果没有下面代码,会因为进程结束而导致进程内的下载线程被销毁
int quit = System . in . read();
while( 'q' != quit ){
Thread . sleep( 2 * 1000);
}
} private class DownladerThread extends Thread {
private int startpos; //从文件的什么位置开始下载
private int perthreadsize; //每条线程需要下载的文件大小
private String path;
private RandomAccessFile file;
private int threadid; public DownladerThread( int threadid , String path , int startpos , int perthreadsize , RandomAccessFile perthreadfile) {
this . path = path;
this . startpos = startpos;
this . perthreadsize = perthreadsize;
this . file = perthreadfile;
this . threadid = threadid;
} @Override
public void run() {
try {
URL url = new URL( path);
HttpURLConnection conn = ( HttpURLConnection) url . openConnection();
conn . setConnectTimeout( 5 * 1000);
conn . setRequestMethod( "GET");
conn . setRequestProperty( "Accept" , "image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*");
conn . setRequestProperty( "Accept-Language" , "zh-CN");
conn . setRequestProperty( "Charset" , "UTF-8");
conn . setRequestProperty( "Range" , "bytes=" + this . startpos + "-");
InputStream inStream = conn . getInputStream();
byte [] buffer = new byte [ 1024 ];
int len = 0;
int length = 0;
while( length < perthreadsize && ( len = inStream . read( buffer ))!=- 1 ){
file . write( buffer , 0 , len);
length += len; //累计该线程下载的总大小
}
file . close();
inStream . close();
System . out . println( threadid + "线程完成下载");
} catch ( Exception e) {
e . printStackTrace();
}
}
} }
向Internet发送请求参数
String requestUrl = "http://localhost:8080/itcast/contanctmanage.do";
Map < String , String > requestParams = new HashMap < String , String >();
requestParams . put( "age" , "12");
requestParams . put( "name" , "中国");
StringBuilder params = new StringBuilder();
for( Map . Entry < String , String > entry : requestParams . entrySet ()){
params . append( entry . getKey());
params . append( "=");
params . append( URLEncoder . encode( entry . getValue (), "UTF-8"));
params . append( "&");
}
if ( params . length() > 0) params . deleteCharAt( params . length() - 1);
byte [] data = params . toString (). getBytes();
URL realUrl = new URL( requestUrl);
HttpURLConnection conn = ( HttpURLConnection) realUrl . openConnection();
conn . setDoOutput( true); //发送POST请求必须设置允许输出
conn . setUseCaches( false); //不使用Cache
conn . setRequestMethod( "POST");
conn . setRequestProperty( "Connection" , "Keep-Alive"); //维持长连接
conn . setRequestProperty( "Charset" , "UTF-8");
conn . setRequestProperty( "Content-Length" , String . valueOf( data . length));
conn . setRequestProperty( "Content-Type" , "application/x-www-form-urlencoded");
DataOutputStream outStream = new DataOutputStream( conn . getOutputStream());
outStream . write( data);
outStream . flush();
if( conn . getResponseCode() == 200 ){
String result = readAsString( conn . getInputStream (), "UTF-8");
outStream . close();
System . out . println( result);
}
向Internet发送xml数据
StringBuilder xml = new StringBuilder();
xml . append( "<?xml version=\"1.0\" encoding=\"utf-8\" ?>");
xml . append( "<M1 V=10000>");
xml . append( "<U I=1 D=\"N73\">中国</U>");
xml . append( "</M1>");
byte [] xmlbyte = xml . toString (). getBytes( "UTF-8");
URL url = new URL( "http://localhost:8080/itcast/contanctmanage.do?method=readxml");
HttpURLConnection conn = ( HttpURLConnection) url . openConnection();
conn . setConnectTimeout( 5 * 1000);
conn . setDoOutput( true); //允许输出
conn . setUseCaches( false); //不使用Cache
conn . setRequestMethod( "POST");
conn . setRequestProperty( "Connection" , "Keep-Alive"); //维持长连接
conn . setRequestProperty( "Charset" , "UTF-8");
conn . setRequestProperty( "Content-Length" , String . valueOf( xmlbyte . length));
conn . setRequestProperty( "Content-Type" , "text/xml; charset=UTF-8");
DataOutputStream outStream = new DataOutputStream( conn . getOutputStream());
outStream . write( xmlbyte); //发送xml数据
outStream . flush();
if ( conn . getResponseCode() != 200) throw new RuntimeException( "请求url失败");
InputStream is = conn . getInputStream(); //获取返回数据
String result = readAsString( is , "UTF-8");
outStream . close();