游戏开发3_02 交互式通信---ContentProvider
Android程序的主要4部分:
1、Activiyt
2、Broadcast Intent Receiver
3、Service
4、Content Provider
一个ContentProvider类实现了一组标准的方法接口,从而能够让其他的应用保存或读取此ContentProvider的各种数据类型。
下面列举一些常用的接口:
1、query(Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder):通过Uri进行查询,返回一个Cursor.
2、insert(Uri uri,ContentValues values):将一组数据插入到Uri指定的地方。
3、update(Uri uri,ContentValues values,String where,String[] selectionArgs):更新Uri指定位置的数据。
4、delete(Uri uri,String where,String[] selectionArgs):删除指定Uri并且符合一定条件的数据。
52、ContentResolver
外界程序通过ContentResolver接口可以访问ContentProvider提供的数据,在Activity当中通过getContentResolver()可以得到当前应用ContentResolver实例。其提供的接口与ContentProvider提供的接口对应:
1、query(Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder):通过Uri进行查询,返回一个Cursor.
2、insert(Uri uri,ContentValues values):将一组数据插入到Uri指定的地方。
3、update(Uri uri,ContentValues values,String where,String[] selectionArgs):更新Uri指定位置的数据。
4、delete(Uri uri,String where,String[] selectionArgs):删除指定Uri并且符合一定条件的数据。
【实例】
提供ContentProvider的程序:TestSQLite_2_ContentProvider
对其进行访问访问的程序:TestContentProvider_useStudentData
下面进行分析:
【1】要为当前应用程序的私有数据定义URI,就需要专门定义个继承自ContentProvider的类,然后实现各个不同的操作所调用的方法。
首先在该应用程序的某个类中定义所有与数据库操作有关的静态字段,以便打包成jar文件供其他应用程序调用(本例中引用打包好的jar包后会有“找不到类”的错误,故直接将该类,连同包名一同复制到另一个程序中,如)
此例子没有新定义一个专门的类来存放这些字段,而是在继承了SQLiteOpenHelper的类StudentData中定义:
package com.shutao.testsqlite2;
public class StudentData extends SQLiteOpenHelper {
/*
* 分别定义了数据库和表的名称、表中各个字段的名称、数据库的版本号
*/
public final static String DB_NAME = "student";
public final static String TABLE_NAME = "hero";
public final static String SNAME = "name";
public final static String SID = "_id";
public final static int DB_VERSION = 1;
/*
* 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
*/
public static final String AUTHORITY = "com.shutao.testsqlite2.provider.studentdata";
public static final int ITEM = 1;
public static final int ITEM_ID = 2;
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.shutao.testsqlite2.studentdata";
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.shutao.testsqlite2.studentdata";
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY
+ "/item");
public StudentData(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_NAME + "(" + SID
+ " VARCHAR PRIMARY KEY," + SNAME + " VARCHAR NOT NULL);");
}
@Override
public void onUpgrade(SQLiteDatabase db, int arg1, int arg2) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
}
}
【2】接下来定义一个继承自ContentProvider的类:MyProvider.java,来实现对数据进行操作的各个方法。这里将用到StudentData来辅助得到SQLiteDatabase对象:
package com.shutao.testsqlite2;
public class MyProvider extends ContentProvider {
StudentData sd;
private static final UriMatcher sMatcher;
static {
// 传入匹配码如果大于0表示匹配根路径或传入-1,即常量UriMatcher.NO_MATCH表示不匹配根路径
// addURI()方法是用来增加其他URI匹配路径的:
// 第一个参数代表传入标识ContentProvider的AUTHORITY字符串
// 第二个参数是要匹配的路径,#代表任意数字,另外还可以用*来匹配任意文本
// 第三个参数必须传入一个大于零的匹配码,用于match()方法对相匹配的URI返回相对应的匹配码
sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sMatcher.addURI(StudentData.AUTHORITY, "item", StudentData.ITEM);
sMatcher.addURI(StudentData.AUTHORITY, "item/#", StudentData.ITEM_ID);
}
/*
* 每当ContentProvider启动时都会回调onCreate()方法。此方法主要执行一些ContentProvider初始化
* 的工作,返回true表示初始化成功,返回false则初始化失败。
*/
@Override
public boolean onCreate() {
sd = new StudentData(this.getContext());
return true;
}
// getType()是用来返回数据的MIME类型的方法。使用sMatcher对URI进行匹配,并返回相应的MIME类型字符串
@Override
public String getType(Uri uri) {
switch (sMatcher.match(uri)) {
case StudentData.ITEM:
return StudentData.CONTENT_TYPE;
case StudentData.ITEM_ID:
return StudentData.CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
/*
* 插入数据,返回新插入数据的URI,只接受数据集的URI,即指向表的URI
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase sdb = sd.getWritableDatabase();
long rowId;
if (sMatcher.match(uri) != StudentData.ITEM) {
throw new IllegalArgumentException("Unknow URI " + uri);
}
rowId = sdb.insert(StudentData.TABLE_NAME, StudentData.SID, values);
if (rowId > 0) {
Uri noteUri = ContentUris.withAppendedId(StudentData.CONTENT_URI,
rowId);
this.getContext().getContentResolver().notifyChange(noteUri, null);
return noteUri;
}
throw new SQLException("Failed to insert row into " + uri);
}
/*
* 用于数据的删除,返回的是所影响数据的数目,首先利用数据库辅助对象获取一个SQLiteDatabase对象
* 然后根据传入Uri用sMatcher进行匹配,对单个数据或数据集进行删除或修改。notifyChange()方法
* 用来通知注册在次URI上的观察者(observer)数据发生了改变。
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase sdb = sd.getWritableDatabase();
int count;
switch (sMatcher.match(uri)) {
case StudentData.ITEM:
count = sdb.delete(StudentData.DB_NAME, selection, selectionArgs);
break;
case StudentData.ITEM_ID:
String id = uri.getPathSegments().get(1);
count = sdb.delete(StudentData.DB_NAME, StudentData.SID
+ "="
+ id
+ (!TextUtils.isEmpty(selection) ? " AND (" + selection
+ ")" : ""), selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
this.getContext().getContentResolver().notifyChange(uri, null);
return count;
}
/*
* 查询数据,将数据装入一个Cursor对象并返回
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase sdb = sd.getReadableDatabase();
Cursor c;
switch (sMatcher.match(uri)) {
case StudentData.ITEM:
c = sdb.query(StudentData.TABLE_NAME, projection, selection,
selectionArgs, null, null, sortOrder);
break;
case StudentData.ITEM_ID:
String id = uri.getPathSegments().get(1);
c = sdb.query(StudentData.TABLE_NAME, projection, StudentData.SID
+ "="
+ id
+ (!TextUtils.isEmpty(selection) ? " AND (" + selection
+ ")" : ""), selectionArgs, null, null, sortOrder);
break;
default:
Log.i("sMatcher.match(uri)", String.valueOf(sMatcher.match(uri)));
throw new IllegalArgumentException("Query with unknown URI: " + uri);
}
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
// 更新,与删除类方法类似
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase sdb = sd.getWritableDatabase();
int count;
switch (sMatcher.match(uri)) {
case StudentData.ITEM:
count = sdb.update(StudentData.TABLE_NAME, values, selection,
selectionArgs);
break;
case StudentData.ITEM_ID:
String id = uri.getPathSegments().get(1);
count = sdb.update(StudentData.DB_NAME, values, StudentData.SID
+ "="
+ id
+ (!TextUtils.isEmpty(selection) ? " AND (" + selection
+ ")" : ""), selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
this.getContext().getContentResolver().notifyChange(uri, null);
return count;
}
}
【3】完成以上方法以后,还要AndroidManifest.xml中对这个ContentProvider声明:
<provider
android:name="MyProvider"
android:authorities="com.shutao.testsqlite2.provider.studentdata"/>
<!-- 其中 android:name必须跟定义的ContentProvider的类名一样
android:authorities则指定了content://样式的URI中标识这个ContentProvider的字符串-->
【4】接下来在另一个程序中使用ContentResolver来操作数据:
package com.shutao.contentprovider;
public class TestContentProvider extends Activity {
private EditText stu_name;
private EditText stu_sid;
Button commit;
ListView studList;
ContentResolver resolver;
// 由于在AlertDialog中用到,所以定义为全局变量。
String sid = "";
private final int DIALOG_IN_USED = 1;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
stu_name = (EditText) this.findViewById(R.id.edit_name);
stu_sid = (EditText) this.findViewById(R.id.edit_sid);
commit = (Button) this.findViewById(R.id.butt_add);
studList = (ListView) this.findViewById(R.id.show_stud);
//【重要】得到ContentResolver()
resolver = this.getContentResolver();
String[] projection = { StudentData.SNAME, StudentData.SID };
Cursor c = resolver.query(StudentData.CONTENT_URI, null, null, null,
StudentData.SID);
CursorAdapter adapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_2, c, projection, new int[] {
android.R.id.text1, android.R.id.text2 });
studList.setAdapter(adapter);
commit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (!(stu_name.getText().length() > 0)) {
Toast.makeText(TestContentProvider.this,
"Please input the student's name!",
Toast.LENGTH_LONG).show();
} else if (!(stu_sid.getText().length() > 0)) {
Toast
.makeText(TestContentProvider.this,
"Please input the student's ID!",
Toast.LENGTH_LONG).show();
} else {
String name = stu_name.getText().toString();
sid = stu_sid.getText().toString();
Uri quri = Uri.parse(StudentData.CONTENT_URI + "/" + sid);
Cursor c = resolver.query(quri, null, null, null, null);
if (c.moveToFirst()) {
// 采用Toast方式提醒
/*
* Toast.makeText(TestContentProvider.this,
* "Fail to add!This ID is already in used!",
* Toast.LENGTH_LONG).show();
*/
// 采用AlertDialog方式提醒
TestContentProvider.this.showDialog(DIALOG_IN_USED);
} else {
ContentValues values = new ContentValues();
values.put(StudentData.SNAME, name);
values.put(StudentData.SID, sid);
resolver.insert(StudentData.CONTENT_URI, values);
}
}
}
});
}
//如果插入的Id已经存在则用提示框进行提示
@Override
protected Dialog onCreateDialog(int id, Bundle args) {
// // TODO Auto-generated method stub
// return super.onCreateDialog(id, args);
switch (id) {
case DIALOG_IN_USED:
return new AlertDialog.Builder(this).setTitle("Fail to add!")
.setIcon(R.drawable.fail).setMessage(
"This ID:" + sid + " is already in used!")
.setPositiveButton("确定",
// 特别注意:必须为:new DialogInterface.OnClickListener()
// 不能为new
// OnClickListener(),否则会跟View.OnClickListener冲突。
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
int which) {
// TODO Auto-generated method stub
}
}).create();
default:
return super.onCreateDialog(id, args);
}
}
}
53、ContentProvider与ContentResolver中用到的Uri
【注】:在ContentProvider与ContentResolver当中用到了Uri的形式通常有两种,一种是指定全部数据,另一种是指定某个ID的数据,如:
1、content://contacts/people/:这个Uri指定的就是全部联系人的数据。
2、content://contacts/people/1:这个Uri指定的就是ID为1的联系人的数据。
Uri一般由3部分组成:
1、"content://"
2、要获得数据的一个字符串片段
3、最后就是ID,如果没有ID则返回全部数据。
但因为URI通常比较长,容易出错,所以在Android当中定义了一些辅助类,并定义了一些常量来代替这些这些字符串的使用,如:
Contacts.People.CONTENT_URI:联系人的URI.
在Android中,我们的应用有的时候需要对外提供数据接口,可以有如下几种方法:1)AIDL 2)Broadcast 3)ContentProvider。
使用AIDL需要我们编写AIDL接口以及实现,而且对方也要有相应的接口描述,有点麻烦;使用Broadcast,我们不需要任何接口描述,只要协议文档就可以了,但是有点不好就是,这种方式不直接而且是异步的;使用ContentProvider我们不需要接口描述,只需要知道协议,同时这种方式是同步的,使用方便。下面是ContentProvider实现:
/** *//**
*
*/
package com.backgroundservice;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.util.Log;
/** *//**
* TODO
*
* @author tianlu
* @version 1.0 Create At : 2010-2-18 下午01:58:39
*/
public class TestContentProvider extends ContentProvider {
private SQLiteDatabase mDb;
private DatabaseHelper mDbHelper = null;
private static final String DATABASE_NAME = "rssitems.db";
private static final String DATABASE_TABLE_NAME = "rssItems";
private static final int DB_VERSION = 1;
private static final int ALL_MESSAGES = 1;
private static final int SPECIFIC_MESSAGE = 2;
// Set up our URL matchers to help us determine what an
// incoming URI parameter is.
private static final UriMatcher URI_MATCHER;
static {
URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
URI_MATCHER.addURI("test", "item", ALL_MESSAGES);
URI_MATCHER.addURI("test", "item/#", SPECIFIC_MESSAGE);
}
// Here's the public URI used to query for RSS items.
public static final Uri CONTENT_URI = Uri
.parse("content://test/item");
// Here are our column name constants, used to query for field values.
public static final String ID = "_id";
public static final String NAME = "NAME";
public static final String VALUE = "VALUE";
public static final String DEFAULT_SORT_ORDER = ID + " DESC";
private static class DatabaseHelper extends AbstractDatabaseHelper {
@Override
protected String[] createDBTables() {
// TODO Auto-generated method stub
String sql = "CREATE TABLE " + DATABASE_TABLE_NAME + "(" + ID
+ " INTEGER PRIMARY KEY AUTOINCREMENT, " + NAME + " TEXT,"
+ VALUE + " TEXT);";
return new String[] { sql };
}
@Override
protected String[] dropDBTables() {
// TODO Auto-generated method stub
return null;
}
@Override
protected String getDatabaseName() {
// TODO Auto-generated method stub
return DATABASE_NAME;
}
@Override
protected int getDatabaseVersion() {
// TODO Auto-generated method stub
return DB_VERSION;
}
@Override
protected String getTag() {
// TODO Auto-generated method stub
return TestContentProvider.class.getSimpleName();
}
}
/** *//**
*
*/
public TestContentProvider() {
// TODO Auto-generated constructor stub
}
/**//*
* (non-Javadoc)
*
* @see android.content.ContentProvider#delete(android.net.Uri,
* java.lang.String, java.lang.String[])
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// NOTE Argument checking code omitted. Check your parameters!
int rowCount = mDb.delete(DATABASE_TABLE_NAME, selection, selectionArgs);
// Notify any listeners and return the deleted row count.
getContext().getContentResolver().notifyChange(uri, null);
return rowCount;
}
/**//*
* (non-Javadoc)
*
* @see android.content.ContentProvider#getType(android.net.Uri)
*/
@Override
public String getType(Uri uri) {
switch (URI_MATCHER.match(uri)) {
case ALL_MESSAGES:
return "vnd.android.cursor.dir/rssitem"; // List of items.
case SPECIFIC_MESSAGE:
return "vnd.android.cursor.item/rssitem"; // Specific item.
default:
return null;
}
}
/**//*
* (non-Javadoc)
*
* @see android.content.ContentProvider#insert(android.net.Uri,
* android.content.ContentValues)
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
// NOTE Argument checking code omitted. Check your parameters! Check that
// your row addition request succeeded!
long rowId = -1;
rowId = mDb.insert(DATABASE_TABLE_NAME, NAME, values);
Uri newUri = Uri.withAppendedPath(CONTENT_URI, ""+rowId);
Log.i("TestContentProvider", "saved a record " + rowId + " " + newUri);
// Notify any listeners and return the URI of the new row.
getContext().getContentResolver().notifyChange(CONTENT_URI, null);
return newUri;
}
/**//*
* (non-Javadoc)
*
* @see android.content.ContentProvider#onCreate()
*/
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
try
{
mDbHelper = new DatabaseHelper();
mDbHelper.open(getContext());
mDb = mDbHelper.getMDb();
}catch(Exception e){
e.printStackTrace();
}
return true;
}
/**//*
* (non-Javadoc)
*
* @see android.content.ContentProvider#query(android.net.Uri,
* java.lang.String[], java.lang.String, java.lang.String[],
* java.lang.String)
*/
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// We won't bother checking the validity of params here, but you should!
// SQLiteQueryBuilder is the helper class that creates the
// proper SQL syntax for us.
SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder();
// Set the table we're querying.
qBuilder.setTables(DATABASE_TABLE_NAME);
// If the query ends in a specific record number, we're
// being asked for a specific record, so set the
// WHERE clause in our query.
if((URI_MATCHER.match(uri)) == SPECIFIC_MESSAGE){
qBuilder.appendWhere("_id=" + uri.getLastPathSegment());
Log.i("TestContentProvider", "_id=" + uri.getLastPathSegment());
}
// Make the query.
Cursor c = qBuilder.query(mDb,
projection,
selection,
selectionArgs,
null,
null,
sortOrder);
Log.i("TestContentProvider", "get records");
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
}
/**//*
* (non-Javadoc)
*
* @see android.content.ContentProvider#update(android.net.Uri,
* android.content.ContentValues, java.lang.String, java.lang.String[])
*/
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// NOTE Argument checking code omitted. Check your parameters!
int updateCount = mDb.update(DATABASE_TABLE_NAME, values, selection, selectionArgs);
// Notify any listeners and return the updated row count.
getContext().getContentResolver().notifyChange(uri, null);
return updateCount;
}
}
配置文件如下:
<provider android:name="TestContentProvider"
android:authorities="test">
</provider>在客户端中可以使用如下方法进行调用:
ContentValues values = new ContentValues();
values.put(TestContentProvider.NAME, "testname1");
values.put(TestContentProvider.VALUE, "testvalu1e");
Uri newAddUri = TestActivity.this.getContentResolver().insert(TestContentProvider.CONTENT_URI, values);
Cursor c = TestActivity.this.managedQuery(newAddUri, new String[]{TestContentProvider.NAME}, null, null, null);
Log.i("TestActivity", "" + c.getCount());
if(c.moveToNext())
{
Log.i("TestActivity", c.getString(0));
}上面的代码是先进行插入,然后进行查询并打印。就是如此简单,所有的应用如果需要都可以对外方便的提供数据接口,同时其他应用也可以很方便的进行调用