使用 ContentProvider 共享数据 访问与添加通讯录

1. 统一的数据访问方式

当应用继承ContentProvider类,并重写该类用于提供数据和存储数据的方法,就可以向其他应用共享其数据。

文件的操作模式中,通过指定文件的操作模式为Context.MODE_WORLD_READABLE 或Context.MODE_WORLD_WRITEABLE同样可以对外共享数据,

但数据的访问方式会因数据存储的方式而不同,如:采用xml文件对外共享数据,需要进行xml解析来读写数据;

采用sharedpreferences共享数据,需要使用sharedpreferences API读写数据。

而使用ContentProvider共享数据的好处是统一了数据访问方式。

通俗一点,例如我要访问另一个应用怎么来描述我的需求呢?有种方式可向 Webservice 那样,统一一种描述语言,大家靠这个来互相理解。但是太麻烦了。现在的情况远远好于应用 Webservice 的场景,因为,应用们都在一部手机上,大家都归 Android 操作系统老大管。于是好办!

甲应用说中文,说:我想得到联系人“13999999” 的详细信息。

乙应用来做大洋彼岸,说:I want to get contacts, "13999999r" for more information.

丁说ri语,高呼呀嘛蝶。

这是在要求访问“通讯录”应用啊,是属于另一个应用了,在另一个进程里。难道我做一个“通信录”程序,为了要让别人访问我,我还得提供“中文”、“英文”、“ri文”三种访问模式?这没法搞了,于是由 Android 牵头,大家商议统一描述语言,于是弄出个 Uri。

那么以后这种需求 Android 规定,大家统一说一样的语言:

content://com.android.contacts/data/phones/filter/13999999

“通讯录”程序中需要提供一种对指定模式的 Uri 的解析机制,并且这种解析机制要注册在操作系统中,随时工作。

例如,只要解析到了“data/phones/filter/13999999” 这种模式,它立马截取出 “13999999” 然后再自己的持久化库中去找寻详细信息,再返回回去。

2. 继承 ContentProviter

public class PersonContentProvider extends ContentProvider;

和 Activity 一样, ContentProviter 属于 Android 的一种组件

3. 注册内容提供者

AndroidManifest.xml

<provider android:name=".PersonContentProvider"

android:authorities="wjh.android.provider.personprovider" />

android:name : 指向内容提供者的类

android:authorities : 应用中的一个唯一的字符串标识。可以将它看作域名。在本应用或其它应用中,可以通过这个域名来访问内容提供者。

4. ContentProvider 中的方法、

/**

* 其它应用可以通过此方法对内容提供者删除数据

*/

public int delete(Uri uri, String selection, String[] selectionArgs) {

return 0;

}

/**

* 其它应用可以通过此方法对内容提供者添加数据

*/

public Uri insert(Uri uri, ContentValues contentValues)

/**

* 其它应用可以通过此方法对内容提供者查询数据

*/

public Cursor query(Uri uri, String[] arg1, String arg2, String[] arg3,

String arg4)

/**

* 其它应用可以通过此方法对内容提供者更新数据

*/

public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3)

/**

* 内容类型

*/

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”。

5. URI

Uri代表了要操作的数据,Uri主要包含了两部分信息:

1. 需要操作的ContentProvider

2. 对ContentProvider中的什么数据进行操作,

一个 Uri 由以下几部分组成

scheme --> content:// (此 scheme 仅表示内容提供者的 scheme)

主机名或 Authority -->wjh.android.provider.personprovider

路径 --> person/10/name

组合成一个完整的 Uri 为:content://wjh.android.provider.personprovider/person/10/name

路径虽然可以随便定义,但为了方便其它人理解,应该遵循一定的命名规则:

要操作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 匹配

UriMatcher.addURI : 添加一个 Uri 匹配模式

UriMatcher,match : 匹配Uri , 返回 Boolean 。

ContentUris :用于对 uri 进行字符操作

ContentUris.withAppendedId(uri, append) : 给 uri 添加一个 ID

ContentUris.parseId : 从 Uri 中解析出 ID

6. ContentResolver : 用于操作 ContentProvider

当外部应用需要对 ContentProvider 中的数据进行添加、删除、修改和查询操作时,可以使用ContentResolver类来完成,

要获取ContentResolver 对象,可以使用Activity提供的getContentResolver()方法。

ContentResolver 类提供了与ContentProvider类相同签名的四个方法:

该方法用于往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)

这些方法的第一个参数为Uri,

代表要操作的ContentProvider和对其中的什么数据进行操作,

假设给定的是: Uri.parse(“content://cn.itcast.providers.personprovider/person/10”),

那么将会对主机名为cn.itcast.providers.personprovider的ContentProvider进行操作,

操作的数据为person表中id为10的记录。

其中主机名是要访问的应用在 AndroidManifest.xml 所注册的 provider 的 authorities 属性

7. 代码示例:

应用一:内容提供者所在的应用

1, AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
   package="wjh.android.db" android:versionCode="1" android:versionName="1.0">
   <application android:icon="@drawable/icon" android:label="@string/app_name">
  	   <uses-library android:name="android.test.runner" />
	   <activity android:name=".MainActivity" android:label="@string/app_name">
		<intent-filter>
			<action android:name="android.intent.action.MAIN" />
			<category android:name="android.intent.category.LAUNCHER" />
		</intent-filter>
	  </activity>
	  <provider android:name=".PersonContentProvider"   <!-- 注册内容提供者  -->
			android:authorities="wjh.android.provider.personprovider" />
   </application>
   <uses-sdk android:minSdkVersion="8" />
</manifest>

2. 准备:数据库(这只是内容提供者的一种形式,还可以是操作文件,如 XML 等等)

public class DBOpenHandler extends SQLiteOpenHelper {
	/**
	 * @param context 当前应用上下文
	 */
	public DBOpenHandler(Context context) {
		super(context, "myAndroid.db", null, 2);
	}
	@Override
	public void onCreate(SQLiteDatabase db) {
		String sql_createTable = "CREATE TABLE person (personid integer primary key autoincrement, name)";
		db.execSQL(sql_createTable);
	}
	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		String sql = "ALTER TABLE person ADD phone";
		db.execSQL(sql);
		db.execSQL("ALTER TABLE person ADD amount integer");
	}
}

3. TContentProvider

/**
 * 内容提供者
 */
public class PersonContentProvider extends ContentProvider {
	private static final UriMatcher MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
	private static final int PERSONS = 1;
	private static final int PERSON = 2;
	private DBOpenHandler dbOpenHandler;

	// 静态初始化 Uri 匹配模式
	static {
		// content://wjh.android.provider.personprovider/person : 表示对 person 表所有数据进行操作
		MATCHER.addURI("wjh.android.provider.personprovider", "person", PERSONS);

		// content://wjh.android.provider.personprovider/person/3 : 表示对 Perosn 表 Id 为 3 的单体记录进行操作
		MATCHER.addURI("wjh.android.provider.personprovider", "person/#", PERSON);
	}

	/**
	 * 其它应用可以通过此方法对内容提供者删除数据
	 */
	public int delete(Uri uri, String selection, String[] selectionArgs) {
		SQLiteDatabase db = dbOpenHandler.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)) {
				where = where + "and" + selection;
			}
			num = db.delete("person", where, selectionArgs);
			break;
		default :
			throw new IllegalArgumentException("Unkown Uri :" + uri);
		}
		return num;
	}

	/**
	 * 获取内容提供者内容的类型
	 */
	@Override
	public String getType(Uri uri) {
		SQLiteDatabase db = dbOpenHandler.getWritableDatabase();
		switch (MATCHER.match(uri)) {
		case PERSONS :
			// vnd.android.cursor.dir : 集合类型
			return "vnd.android.cursor.dir/person";
		case PERSON:
			// vnd.android.cursor.item : 单条记录
			return "vnd.android.cursor.item/person";
		default :
			throw new IllegalArgumentException("Unkown Uri :" + uri);
		}
	}
	/**
	 * 其它应用可以通过此方法对内容提供者添加数据
	 */
	public Uri insert(Uri uri, ContentValues contentValues) {
		SQLiteDatabase db = dbOpenHandler.getWritableDatabase();
		switch (MATCHER.match(uri)) {
		case PERSONS :
			long rowid = db.insert("person", "name", contentValues);
			return ContentUris.withAppendedId(uri, rowid);
		default :
			throw new IllegalArgumentException("Unkown Uri :" + uri);
		}
	}
	public boolean onCreate() {
		dbOpenHandler = new DBOpenHandler(getContext());
		return true;
	}
	/**
	 * 其它应用可以通过此方法对内容提供者查询数据
	 */
	public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
			String sortOrder) {
		SQLiteDatabase db = dbOpenHandler.getWritableDatabase();
		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)) {
				where = where + "and" + selection;
			}
			return db.query("person", projection, where, selectionArgs, null, null, sortOrder);
		default :
			throw new IllegalArgumentException("Unkown Uri :" + uri);
		}
	}
	/**
	 * 其它应用可以通过此方法对内容提供者更新数据
	 */
	public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
		SQLiteDatabase db = dbOpenHandler.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);
		}
		return num;
	}
}

应用二:用于测试访问应用一的内容提供者

1, AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="wjh.android.other"
      android:versionCode="1"
      android:versionName="1.0">

    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <uses-library android:name="android.test.runner" />
        <activity android:name=".OtherActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

    <uses-sdk android:minSdkVersion="7" />
	<instrumentation android:name="android.test.InstrumentationTestRunner"
		android:targetPackage="wjh.android.other" android:label="Tests for My App" />
</manifest>

2,TestContentProvider : 测试类,测试访问应用一的内容提供者

public class TestContentProvider extends AndroidTestCase {
	private static final String TAG = "TestContentProvider";

	/**
	 * 添加
	 */
	public void testSave() throws Throwable {
		ContentResolver contentResolver  = this.getContext().getContentResolver();

		String uriStr = "content://wjh.android.provider.personprovider/person";
		Uri uri = Uri.parse(uriStr);

		ContentValues values = new ContentValues();
		values.put("name", "XXX");
		values.put("phone", "135123456");
		values.put("amount", "22222222222");

		uri = contentResolver.insert(uri, values );
		Log.i(TAG, uri.toString());
	}
	/**
	 * 更新
	 */
	public void testUpdate() throws Throwable {
		ContentResolver contentResolver  = this.getContext().getContentResolver();

		String uriStr = "content://wjh.android.provider.personprovider/person/3";
		Uri uri = Uri.parse(uriStr);

		ContentValues values = new ContentValues();
		values.put("name", "XXX");
		values.put("phone", "135123456");
		values.put("amount", "4444444");

		contentResolver.update(uri, values, null, null);
	}

	/**
	 * 查找
	 */
	public void testFind() throws Throwable {
		ContentResolver contentResolver  = this.getContext().getContentResolver();

		String uriStr = "content://wjh.android.provider.personprovider/person";
		Uri uri = Uri.parse(uriStr);

		Cursor cursor = contentResolver.query(uri, null, null, null, "personid asc");

		// 打印在日志信息上
		while(cursor.moveToNext()) {
			int personId = cursor.getInt(cursor.getColumnIndex("name"));
			String name = cursor.getString(cursor.getColumnIndex("name"));
			String phone = cursor.getString(cursor.getColumnIndex("phone"));
			int amount = cursor.getInt(cursor.getColumnIndex("amount"));
			Log.i(TAG, "Person [amount=" + amount + ", id=" + personId + ", name=" + name
				+ ", phone=" + phone + "]");

		}
	}

	/**
	 * 删除
	 */
	public void testDelete() throws Throwable {
		ContentResolver contentResolver  = this.getContext().getContentResolver();

		String uriStr = "content://wjh.android.provider.personprovider/person/3";
		Uri uri = Uri.parse(uriStr);

		contentResolver.delete(uri, null, null);
	}
}


8. 监听内容提供者的数据变化

1. 内容提供者在数据发送更新时,需要发出一条更新通知,来通知注册在此URI上的访问者。

getContext().getContentResolver().notifyChange(uri, null);

例:在上例中,为 insert、update、delete方法执行成功后,发出更新通知

public Uri insert(Uri uri, ContentValues contentValues) {
	SQLiteDatabase db = dbOpenHandler.getWritableDatabase();

	switch (MATCHER.match(uri)) {
	case PERSONS :
		long rowid = db.insert("person", "name", contentValues);

		// 由内容提供者发出内容更新的通知。
		getContext().getContentResolver().notifyChange(uri, null);

		return ContentUris.withAppendedId(uri, rowid);
	default :
		throw new IllegalArgumentException("Unkown Uri :" + uri);
        }
}

2. 访问者需要监听内容提供者的通知

需要调用 ContentResolver 的 registerContentObserver 方法来注册一个监听器

registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)

@param uri :监听哪个 uri

@param notifyForDescendents : true

@param ContentObserver : 使用哪个监听器来处理更新事务

示例:

public class OtherActivity extends Activity {
    private static final String TAG = "OtherActivity";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        String uriStr = "content://wjh.android.provider.personprovider/person";
	Uri uri = Uri.parse(uriStr);
	// 注册对 content://wjh.android.provider.personprovider/person 内容更新的观察
        getContentResolver().registerContentObserver(uri, true, new PersonContenObserver(new Handler()));
    }

    /**
     * 更新监察者。
     */
    private final class PersonContenObserver extends ContentObserver {

    	public PersonContenObserver(Handler handler) {
			super(handler);
	}

	@Override
	public void onChange(boolean selfChange) {
		ContentResolver contentResolver  = getContentResolver();
		String uriStr = "content://wjh.android.provider.personprovider/person";
		Uri uri = Uri.parse(uriStr);
		Cursor cursor = contentResolver.query(uri, null, null, null, "personid asc");
		while(cursor.moveToNext()) {
			int personId = cursor.getInt(cursor.getColumnIndex("name"));
			String name = cursor.getString(cursor.getColumnIndex("name"));
			String phone = cursor.getString(cursor.getColumnIndex("phone"));
			int amount = cursor.getInt(cursor.getColumnIndex("amount"));

			// 将获取的数据通过日志信息打印出来 ,实际应用中,可以用于同步应用中的数据
			Log.i(TAG, "Person [amount=" + amount + ", id=" + personId + ", name=" + name
				+ ", phone=" + phone + "]");

		}
		super.onChange(selfChange);
	}
  }
}

 

实际应用,“通讯录” 数据读取、添加联系人信息

** 查看通讯录数据库:

首先启动模拟器、再打开“File Explorer” 视图,

依次展开:data -- data -- com.android.providers.contacts(注意和 com.android.contacts 区分) -- databases,如图:

点击右上角的导出图标,如图:

将数据库文件导出到电脑磁盘上。

然后,你需要拥有一个能打开 SQLite 数据库的工具。例如我用的 SQLite Expert Personal。可以在网上搜索下载。

打开数据库后,就比较纠结了,需要观察表结构。如果确实看得比较纠结,干脆甩开,以免打击学习激情。

或者一狠心,自己写一个管理联系人的程序,弄个自己的通讯录数据库。

raw_contacts 表: 联系人 ID

data表 : 联系人的数据表。通过raw_contacts_id 与 raw_contacts 表联系

存放的联系人信息如:手机号、姓名、Email 等。

联系人的每一项数据,都会在 data 表产生一条记录。例如,记录手机号的是单独一条记录,记录 Email 的是单独一条记录。并且都用 data1 字段来存放。

也就是说,联系人的所有重要信息,都以单独记录的方式,保存在这张表里的 data1字段。用 raw_contacts_id 来描述信息所属的联系人。用 mimitype_id 来描述 data1 字段存储的数据类型(是 Email 数据么?手机号数据么?座机数据么?)。

假如data1要存放的数据是 “由几个数据组合起来的” ,例如:姓名中的 FirstName 和 LastName。

那么就往后存到 data2、data3 里面。组合起来的完整姓名存放在 data1

区分这条数据到底是短信数据、电话数据、Emai等,则是依靠 mimitype_id 字段来区分

mimitype_id 其实是 mimitypes 的外键。观察此表得知:1 表示 email、6 表示姓名、5 表示电话号码。

data2:也用来说明 data1。例如,如果是此记录记录的数据是电话号码,那么若是住宅电话,此字段为1;手机号码为2;单位电话为3......

这些字段的意义很重要,建议读者花10分钟大致看一眼(强烈建议不要花太多时间去研究这个,意义不大),再结合后面的程序,相信会很容易理解。

既然知道了数据存放的方式了,知道它们的字段的名称了,表名也知道了。(当然,编程时我们尽量使用 Android 提供的常量。否则也许今天写的程序,用到了某某字段,睡一觉起来 Android 升完级,不认了。)

好了,sql 语句的几个要素都成立了。要存取联系人好了做吧。

当然啦,这个过程要通过 “联系人” 对外提供的接口来完成,毕竟这个程序是人家的,这个表也是人家的,我们不能直接访问第三方程序的数据库。要提供这个接口的方式可多了,ContentProvider 无疑是最佳的选择。

说穿了,其实就是我们在我们的程序中,组拼出我们需要的 sql 语句,通过 ContentProvider 通信机制,将 sql 语句送到 “联系人”程序去执行而已。并不神奇和复杂,不是么?

calls表:存放的呼叫记录。在《Android--删除某联系人的通话记录》中需要操作它

源码在:

${对应版本SDK源码目录}/ContactsProvider/src/com/android/providers/contacts/ContactsProvider2.java

可以通过查看源码,获知 Uri

...

matcher.addURI(ContactsContract.AUTHORITY, "data", DATA);

matcher.addURI(ContactsContract.AUTHORITY, "data/#", DATA_ID);

matcher.addURI(ContactsContract.AUTHORITY, "data/phones", PHONES);

matcher.addURI(ContactsContract.AUTHORITY, "data/phones/#", PHONES_ID);

matcher.addURI(ContactsContract.AUTHORITY, "data/phones/filter", PHONES_FILTER);

matcher.addURI(ContactsContract.AUTHORITY, "data/phones/filter/*", PHONES_FILTER);

matcher.addURI(ContactsContract.AUTHORITY, "data/emails", EMAILS);

matcher.addURI(ContactsContract.AUTHORITY, "data/emails/#", EMAILS_ID);

matcher.addURI(ContactsContract.AUTHORITY, "data/emails/lookup/*", EMAILS_LOOKUP);

matcher.addURI(ContactsContract.AUTHORITY, "data/emails/filter", EMAILS_FILTER);

matcher.addURI(ContactsContract.AUTHORITY, "data/emails/filter/*", EMAILS_FILTER);

matcher.addURI(ContactsContract.AUTHORITY, "data/postals", POSTALS);

matcher.addURI(ContactsContract.AUTHORITY, "data/postals/#", POSTALS_ID);

...

要按照电话号码获取某一个联系人可以使用:

Uri uri = Uri.parse("content://com.android.contacts/data/phones/filter/151016899999");

** 示例代码:(上代码之前,最好的建议是:边看这个例子,边打开数据库和源码瞅瞅,对 ContentProvider 的理解会深入很多)

1. AndroidManifest.xml 加入权限

<uses-permission android:name="android.permission.READ_CONTACTS" />

<uses-permission android:name="android.permission.WRITE_CONTACTS" />

2.

package wjh.android.contact;
import java.util.ArrayList;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.provider.ContactsContract.Contacts.Data;
import android.test.AndroidTestCase;
import android.util.Log;
/**
 * 通讯录操作示例
 *
 */
public class ContactTest extends AndroidTestCase {
	private static final String TAG = "ContactTest";
	/**
	 * 获取通讯录中所有的联系人
	 */
	public void testGetContacts() throws Throwable {
		ContentResolver contentResolver = this.getContext().getContentResolver();
		String uriStr = "content://com.android.contacts/contacts";
		Uri uri = Uri.parse(uriStr);
		Cursor cursor = contentResolver.query(uri, null, null, null, null);
		// 遍历联系人
		while (cursor.moveToNext()) {
			// 联系人 ID
			int contactId = cursor.getInt(cursor.getColumnIndex(ContactsContract.Contacts._ID));
			// 联系人显示名称
			String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));

			// 联系人电话号码需要对另一个表进行查询,所以用到另一个 uri:content://com.android.contacts/data/phones
			Cursor phones = getContext().getContentResolver().query(
					                        ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
					                        null,
					                        ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = "   // 根据上一步获取的联系人 id 来查询
							                                         + contactId, null, null);
			String phone = "";
			while (phones.moveToNext()) {
                                // "data1"字段,所以此处也可以直接写 "data1",但不推荐
				phone = phones.getString(phones.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
			}

			// 再查询 Email。uri 为 : content://com.android.contacts/data/emails
			Cursor emails = getContext().getContentResolver().query(
					                       ContactsContract.CommonDataKinds.Email.CONTENT_URI,
					                       null,
					                       ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = "
							                                        + contactId, null, null);
			while (emails.moveToNext()) {
                                /* 一样是 "data1" 字段。现在明白了吧?一个联系人的信息,其实被分成了好几条记录来保存,data1分别保存了各种重要的信息。
                                 * 是时候参照打开数据库我前面所说,去瞄一眼它的表结构了!
                                 */
				String emailAddress = emails.getString(emails.getColumnIndex(ContactsContract.CommonDataKinds.Email.DATA));
				Log.i("RongActivity", "emailAddress=" + emailAddress);
			}
			emails.close();
			Log.i(TAG, "Contact [contactId= "+ contactId +"name=" + name + ", phone=" + phone + "]");
		}
	}

	/**
	 * 首先向RawContacts.CONTENT_URI执行一个空值插入,目的是获取系统返回的rawContactId
	 * 这时后面插入data表的依据,只有执行空值插入,才能使插入的联系人在通讯录里面可见
	 */
	public void testInsert() {
		ContentValues values = new ContentValues();
		//首先向RawContacts.CONTENT_URI执行一个空值插入(raw_contacts 表), 为了获取生成的联系人 ID
		Uri rawContactUri = this.getContext().getContentResolver().insert(RawContacts.CONTENT_URI, values);

		//然后获取系统返回的rawContactId , 就是新加入的这个联系人的 ID
		long rawContactId = ContentUris.parseId(rawContactUri);

		 /* Andorid 中,将联系人的姓名、电话、Email
		  * 分别存放在 data 表的同一个字段的三条记录当中
		  * 因此要  Insert 三次 */		

		//往data表入姓名数据
		values.clear();

		// raw_contacts_id 字段,是 raw_contacts表id 的外键,用于说明此记录属于哪一个联系人
		values.put(Data.RAW_CONTACT_ID, rawContactId); 

		// mimitype_id 字段,用于描述此数据的类型,电话号码?Email?....
		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, "15101689230");
		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, "wenlin56@sina.com");
		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());
		// 更新手机号码:Data.RAW_CONTACT_ID 获取上一条语句插入联系人时产生的 ID
		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, "15101689231")  // "data1"
		                                          .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, "wenlin56@yahoo.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());
		}
	}
}
  1. }
时间: 2024-08-31 12:30:58

使用 ContentProvider 共享数据 访问与添加通讯录的相关文章

在ASP.NET 2.0中操作数据之一:创建一个数据访问层_自学过程

导言 作为web开发人员,我们的生活围绕着数据操作.我们建立数据库来存储数据,写编码来访问和修改数据,设计网页来采集和汇总数据.本文是研究在ASP.NET 2.0中实现这些常见的数据访问模式之技术的长篇系列教程的第一篇.我们将从创建一个软件框架开始,这个框架的组成部分包括一个使用强类型的DataSet的数据访问层(DAL),一个实施用户定义的业务规则的业务逻辑层(BLL),以及一个由共享页面布局的ASP.NET网页组成的表现层.在打下这个后端的基础工作之后,我们将开始转向报表,示范如何显示,汇总

ASP.NET2.0数据操作之创建数据访问层(2)

asp.net|创建|访问|数据 这最后一个复选框,"生成DB直接方法(GenerateDBDirectMethods)",如果选了的话,会为TableAdapter自动生 成Insert(),Update(),和Delete()方法.如果你不选这个选项 的话,所有的更新都需要通过TableAdapter唯一的Update()方法来实现,该方法接受一个强类型的DataSet,或者一个DataTable,或者单个DataRow,或者一个DataRow数组.(假如你 在图9所示的高级属性里

Firefox、Edge 添加 G Suite 特性,支持脱机数据访问

G Suite,也就是原来的 Google 云端合作平台 Google Apps,包含了 Gmail.日历.云端硬盘.文档.表格.表单.幻灯片.协作平台.管理控制台等产品功能.作为自家的产品,G Suite 在 Chrome 浏览器上的运行能力自不用多说,还支持离线使用,即使没有网络连接,也可以打开和编辑文档. 现在,Google 表示正改造其 G Suite 工具,目标是可以在其他浏览器上同样实现脱机工作.为了扩展离线支持,Google 重新构建了 G Suite 工具中的 Google 文档

Android编程实现两个Activity之间共享数据及互相访问的方法_Android

本文实例讲述了Android编程实现两个Activity之间共享数据及互相访问的方法.分享给大家供大家参考,具体如下: 本人从windows编程转过来学习Android开发,一直在想如果两个Activity之间能够像C#或delphi中的Form一样,可以直接访问其成员(字符.数值.成员对象等),并能调用其公开的方法,那应该比用Intent来传递数据直接方便的多,于是偿试了如下办法,测试基本没有问题,发出来大家讨论一下.本人学习android不久,幼稚的地方希望大家不要见笑 原理:假设有两个Ac

其实添加数据也可以这样简单——表单的第一步抽象(针对数据访问层)《怪怪设计论: 抽象无处不在 》有感

更正:不好意思,昨天晚上思路有点混乱.有几个前提忘记说明了,现在补充一下. 1.缩小范围.按照由简到难的思路,这里先讨论最简单的添加数据的情况.就是单表的添加和修改:这里讨论的是webform的情况. 2.第一步抽象是针对数据访问层的抽象. 如果我没有理解错的话,现在大多数人的做法是:有一个表(或者几个有关联的表)在数据层里就要有一个"函数"与之对应, 如果采用的是SQL语句的方式的话,那么函数的内筒就是组合SQL语句的代码, 如果采用的是存储过程的方式的话,那么函数的内筒就是给存储过

Android编程实现两个Activity之间共享数据及互相访问的方法

本文实例讲述了Android编程实现两个Activity之间共享数据及互相访问的方法.分享给大家供大家参考,具体如下: 本人从windows编程转过来学习Android开发,一直在想如果两个Activity之间能够像C#或delphi中的Form一样,可以直接访问其成员(字符.数值.成员对象等),并能调用其公开的方法,那应该比用Intent来传递数据直接方便的多,于是偿试了如下办法,测试基本没有问题,发出来大家讨论一下.本人学习android不久,幼稚的地方希望大家不要见笑 原理:假设有两个Ac

数据访问函数库的使用方法(一)——添加修改数据

由于这个类库是需要实例化的,如果每一次都要实例化,然后用完了在销毁,无形中就多了不少的代 码,而且很容易忘记销毁实例. 同时在用户的一次访问的过程中不断地实例化.销毁,也是比较浪费资源的. 所以我建立了一个基类,在基类里面同意获得实例.统一销毁实例,这样在编码的时候就不用考虑有 没有实例化,也不用担心是否销毁实例了, 另外用起来(使用方式)也和静态类的使用方式很像了. 基类里的代码: (ps:我习惯在.aspx.cs里面直接调用 数据访问函数库,所以这个基类是继承 System.Web.UI.P

了解 XML实现通用的数据访问

xml|访问|数据   了解可扩展标记语言 (XML) 如何帮助我们实现通用的数据访问.XML 是一种基于 Unicode 的纯文本元语言,即一种用于定义标记语言的语言.它不依赖于任何编程语言.操作系统或软件供应商.XML 可以提供对各种数据处理.构建.转换和查询技术的访问.(本文包含一些指向英文站点的链接.)  简介     最初设想的可扩展标记语言 (XML) 用于为 Web 定义新文档的格式.XML 由标准通用标记语言 (SGML) 衍生而来,可以认为它是一种元语言,即一种定义标记语言的语

设计.NET应用程序数据访问层五大原则

程序|访问|设计|数据 摘要:大多数使用.NET框架组件工作的开发人员的一个核心工作是实现数据访问功能,他们建立的数据访问层(data access layer)是应用程序的精华部分.本文概述了使用Visual Studio .NET和.NET框架组件建立数据访问层需要考虑的五个想法.这些技巧包括通过使用基类(base class)利用面相对象技术和.NET框架组件基础结构,使类容易继承,在决定显示方法和外部界面前仔细地检验需求. 如果你正在建立以数据为中心(data-centric)的.NET