Android读书笔记之ContentProvider实现数据共享

Android读书笔记之ContentProvider实现数据共享

一.ContentProvider简介

1.ContentProvider
让一个应用程序直接去操作另一个应用程序所记录的数据,比如操作它所记录的SharedPreferences, 文件或数据库等。这种方式不仅比较麻烦,而且还存在严重的安全漏洞。
如何完整开发一个ContentProvider:
1.定义自己的ContentProvider类,该类需要继承Android提供的ContentProvider基类。
2.在AndroidManifest.xml文件中注册这个ContentProvider。

<!-- 下面配置中name属性指定ContentProvider类,authorities就相当于为该ContentProvider指定域名-->
<provider android:name=".DictProvider"
		android:authorities="org.crazyit.providers.dictprovider"
		android:exported="true"/>

那么DictProvider到底如何暴露它所提供的数据呢? 应用程序对数据的操作无非就是CRUD操作,因此DictProvider除了需要继承ContentProvider之外,还需要提供如下几个方法:
如:
在这里插入图片描述

2.Uri简介:
ContentProvider要求的Uri与此类似,例如如下Uri:

content://org.crazyit.providers.dictprovier/words

可分为如下三个部分:
1> content//:这个部分是Android的ContentProvider规定的,就像上网的协议默认是http://一样。暴露ContentProvider,访问ContentProvider的协议默认是content://.
2> org.crazyit.providers.dictprovider:系统就是由这个部分来找到操作那个ContentProvider的,只要访问指定的ContentProvider,这个部分就是固定的。
3> words:资源部分。当访问者需要访问不同资源时,这个部分是动态改变的。

虽然大部分使用ContentProvider所操作的数据都来自于数据库,但有时候这些数据也可来自于文件,XML或网络等其他存储方式,此时支持的Uri也可以改为如下形式:

//表示操作word节点下的detail节点
content://org.crazyit.providers.dictprovider/words/detail/

为了将一个字符串转换成Uri,Uri工具了提供了parse静态方法,如下

Uri uri = Uri.parse("content://org.crazyit.providers.dictprovider/word/2");

3.使用ContentResolver操作数据

ContentProvider相当于暴露可供操作的数据,其他应用程序则通过ContentResolver来操作ContentProvider所暴露的数据,ContentResolver相当于HttpClient。
在这里插入图片描述
在这里插入图片描述

一般来说,ContentProvider是单实例模式的,当多个应用程序通过ContentResolver来操作ContentProvider提供的数据时,ContentResolver调用的数据操作将会委托给同一个ContentProvider处理。

二.开发ContentProvider

1.ContentProvider和ContentResolver的关系
ContentProvider,Uri和ContentResolver三者之间的关系如下所示:
在这里插入图片描述
以指定Uri为标识,ContentResolver可以实现"间接调用"ContentProvider的CRUD方法。

2.开发ContentProvider子类
开发ContentProvider子类只需要两步:
a.开发一个ContentProvier子类,该子类需要实现query(),insert(),update()和delete()等方法;
b.在AndroidManifest.xml文件中注册该ContentProvider,指定android:authorities属性。

下面示例ContentProvider,该ContentProvider虽然实现了query(), insert(), update()和delete()方法,但并未真正对地阐述进行访问,只是输出一行字符.

FirstProvider.java

public class FirstProvider extends ContentProvider
{
	// 第一次创建该ContentProvider时调用该方法
	@Override
	public boolean onCreate()
	{
		System.out.println("===onCreate方法被调用===");
		return true;
	}
	// 该方法的返回值代表了该ContentProvider所提供数据的MIME类型
	@Override
	public String getType(Uri uri)
	{
		return null;
	}
	// 实现查询方法,该方法应该返回查询得到的Cursor
	@Override
	public Cursor query(Uri uri, String[] projection, String where,
						String[] whereArgs, String sortOrder)
	{
		System.out.println(uri + "===query方法被调用===");
		System.out.println("where参数为:" + where);
		return null;
	}
	// 实现插入的方法,该方法应该返回新插入的记录的Uri
	@Override
	public Uri insert(Uri uri, ContentValues values)
	{
		System.out.println(uri + "===insert方法被调用===");
		System.out.println("values参数为:" + values);
		return null;
	}
	// 实现删除方法,该方法应该返回被更新的记录条数
	@Override
	public int update(Uri uri, ContentValues values, String where,
					  String[] whereArgs)
	{
		System.out.println(uri + "===update方法被调用===");
		System.out.println("where参数为:"
				+ where + ",values参数为:" + values);
		return 0;
	}
	// 实现删除方法,该方法应该返回被删除的记录条数
	@Override
	public int delete(Uri uri, String where, String[] whereArgs)
	{
		System.out.println(uri + "===delete方法被调用===");
		System.out.println("where参数为:" + where);
		return 0;
	}
}

3.配置ContentProvider
只要为<application…/>元素添加了<provider…/>子元素即可配置ContentProvider。

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="org.crazyit.content" >

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
		<!-- 注册一个ContentProvider -->
		<provider
			android:exported="true"
			android:name=".FirstProvider"
			android:authorities="org.crazyit.providers.firstprovider">
		</provider>
	</application>

</manifest>

配置ContentProvider时通常指定如下属性:
name:指定该ContentProvider的实现类的类名;
authorities:指定该ContentProvider对应的Uri(相当于为该ContentProvider分配一个域名);
android:exported:指定该ContentProvider是否允许其他应用调用。如果将属性设为false,那么该ContentProvider将不允许其他应用调用。

4.使用ContentResolver调用方法

Context提供了getContentResovler()方法,这表明Activity,Service等组件都可以通过getContentResovler()方法获取ContentResolver对象。

下面示例的界面布局文件中只包含了4个按钮,分别用于激发调用ContentProvider的query(),insert(),update(),delete()方法。

ManiActivity.java

public class MainActivity extends Activity
{
	ContentResolver contentResolver;
	Uri uri = Uri.parse("content://org.crazyit.providers.firstprovider/");
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		// 获取系统的ContentResolver对象
		contentResolver = getContentResolver();
	}
	public void query(View source)
	{
		// 调用ContentResolver的query()方法
		// 实际返回的是该Uri对应的ContentProvider的query()的返回值
		Cursor c = contentResolver.query(uri, null
			, "query_where", null, null);
		Toast.makeText(this, "远程ContentProvide返回的Cursor为:" + c,
				Toast.LENGTH_SHORT).show();
	}
	public void insert(View source)
	{
		ContentValues values = new ContentValues();
		values.put("name", "fkjava");
		// 调用ContentResolver的insert()方法。
		// 实际返回的是该Uri对应的ContentProvider的insert()的返回值
		Uri newUri = contentResolver.insert(uri, values);
		Toast.makeText(this, "远程ContentProvide新插入记录的Uri为:"
				+ newUri, Toast.LENGTH_SHORT).show();
	}
	public void update(View source)
	{
		ContentValues values = new ContentValues();
		values.put("name", "fkjava");
		// 调用ContentResolver的update()方法。
		// 实际返回的是该Uri对应的ContentProvider的update()的返回值
		int count = contentResolver.update(uri, values
				, "update_where", null);
		Toast.makeText(this, "远程ContentProvide更新记录数为:"
				+ count, Toast.LENGTH_SHORT).show();
	}
	public void delete(View source)
	{
		// 调用ContentResolver的delete()方法
		// 实际返回的是该Uri对应的ContentProvider的delete()的返回值
		int count = contentResolver.delete(uri
				, "delete_where", null);
		Toast.makeText(this, "远程ContentProvide删除记录数为:"
				+ count, Toast.LENGTH_SHORT).show();
	}
}

界面文件如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="query"
        android:text="@string/query"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="insert"
        android:text="@string/insert"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="update"
        android:text="@string/update"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="delete"
        android:text="@string/delete"/>
</LinearLayout>

输出结果:

2022-01-02 09:24:46.185 15433-15473/? I/System.out: ------> log control false account 164566
2022-01-02 09:25:13.279 15756-15756/com.danny.firstprovider I/System.out: ===onCreate方法被调用===
2022-01-02 09:27:36.846 15756-15776/com.danny.firstprovider I/System.out: content://com.danny.firstprovider/===query方法被调用===
2022-01-02 09:27:36.846 15756-15776/com.danny.firstprovider I/System.out: where参数为:query_where
2022-01-02 09:28:56.822 15756-15776/com.danny.firstprovider I/System.out: content://com.danny.firstprovider/===insert方法被调用===
2022-01-02 09:28:56.823 15756-15776/com.danny.firstprovider I/System.out: values参数为:name=fkjava
2022-01-02 09:29:02.771 15756-15776/com.danny.firstprovider I/System.out: content://com.danny.firstprovider/===update方法被调用===
2022-01-02 09:29:02.771 15756-15776/com.danny.firstprovider I/System.out: where参数为:update_where,values参数为:name=fkjava
2022-01-02 09:29:06.446 15756-15776/com.danny.firstprovider I/System.out: content://com.danny.firstprovider/===delete方法被调用===
2022-01-02 09:29:06.446 15756-15776/com.danny.firstprovider I/System.out: where参数为:delete_where
2022-01-02 09:29:34.160 15756-15776/com.danny.firstprovider I/System.out: content://com.danny.firstprovider/===query方法被调用===
2022-01-02 09:29:34.160 15756-15776/com.danny.firstprovider I/System.out: where参数为:query_where
2022-01-02 09:29:37.955 15756-15776/com.danny.firstprovider I/System.out: content://com.danny.firstprovider/===delete方法被调用===
2022-01-02 09:29:37.955 15756-15776/com.danny.firstprovider I/System.out: where参数为:delete_where

5.创建ContentProvider的说明
ContentProvider不像Activity存在复杂的生命周期,ContentProvider只有一个onCreate生命周期方法,当其他应用通过ContentResolver第一次访问该ContentProvider时,onCreate方法将会被回调,onCreate方法只会被调用一次;ContentProvider提供的query,insert,update,delete方法则由其他应用通过ContentResolver调用。

UriMatcher工具类提供如下方法:
在这里插入图片描述
例如:我们先通过如下代码来创建UriMatcher对象

UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI("org.crazyit.providers.dictprovider","words", 1);
//#为通配符
matcher.addURI("org.crazyit.providers.dictprovider","words/#", 2);

这意味着如下匹配结果:

//返回标识码1
matcher.match(Uri.parse("content://org.crazyit.providers.dictprovider/words"));
//返回标识码2
matcher.match(Uri.parse("content://org.crazyit.providers.dictprovider/words/3"));

除此之外,Android还提供了一个ContentUris工具类,他是一个操作Uri字符串的工具类,提供了如下两个工具方法。
在这里插入图片描述

实例.使用ContentProvider共享生词本数据
工具类只是包含一些public staitic的常量。
Words.java

package org.crazyit.content;

import android.net.Uri;
import android.provider.BaseColumns;

public final class Words
{
	// 定义该ContentProvider的Authorities
	public static final String AUTHORITY
			= "org.crazyit.providers.dictprovider";
	// 定义一个静态内部类,定义该ContentProvider所包含的数据列的列名
	public static final class Word implements BaseColumns
	{
		// 定义Content所允许操作的三个数据列
		public final static String _ID = "_id";
		public final static String WORD = "word";
		public final static String DETAIL = "detail";
		// 定义该Content提供服务的两个Uri
		public final static Uri DICT_CONTENT_URI = Uri
			.parse("content://" + AUTHORITY + "/words");
		public final static Uri WORD_CONTENT_URI = Uri
			.parse("content://"	+ AUTHORITY + "/word");
	}
}


接下来开发一个ContentProvider子类,并重写其中的增,删,改,查等方法

package org.crazyit.content;

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 DictProvider extends ContentProvider
{
	private static UriMatcher matcher
			= new UriMatcher(UriMatcher.NO_MATCH);
	private static final int WORDS = 1;
	private static final int WORD = 2;
	private MyDatabaseHelper dbOpenHelper;
	static
	{
		// 为UriMatcher注册两个Uri
		matcher.addURI(Words.AUTHORITY, "words", WORDS);
		matcher.addURI(Words.AUTHORITY, "word/#", WORD);
	}
	// 第一次调用该DictProvider时,系统先创建DictProvider对象,并回调该方法
	@Override
	public boolean onCreate()
	{
		dbOpenHelper = new MyDatabaseHelper(this.getContext(),
				"myDict.db3", 1);
		return true;
	}
	// 返回指定Uri参数对应的数据的MIME类型
	@Override
	public String getType(Uri uri)
	{
		switch (matcher.match(uri))
		{
			// 如果操作的数据是多项记录
			case WORDS:
				return "vnd.android.cursor.dir/org.crazyit.dict";
			// 如果操作的数据是单项记录
			case WORD:
				return "vnd.android.cursor.item/org.crazyit.dict";
			default:
				throw new IllegalArgumentException("未知Uri:" + uri);
		}
	}
	// 查询数据的方法
	@Override
	public Cursor query(Uri uri, String[] projection, String where,
						String[] whereArgs, String sortOrder)
	{
		SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
		switch (matcher.match(uri))
		{
			// 如果Uri参数代表操作全部数据项
			case WORDS:
				// 执行查询
				return db.query("dict", projection, where,
						whereArgs, null, null, sortOrder);
			// 如果Uri参数代表操作指定数据项
			case WORD:
				// 解析出想查询的记录ID
				long id = ContentUris.parseId(uri);
				String whereClause = Words.Word._ID + "=" + id;
				// 如果原来的where子句存在,拼接where子句
				if (where != null && !"".equals(where))
				{
					whereClause = whereClause + " and " + where;
				}
				return db.query("dict", projection, whereClause, whereArgs,
						null, null, sortOrder);
			default:
				throw new IllegalArgumentException("未知Uri:" + uri);
		}
	}
	// 插入数据方法
	@Override
	public Uri insert(Uri uri, ContentValues values)
	{
		// 获得数据库实例
		SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
		switch (matcher.match(uri))
		{
			// 如果Uri参数代表操作全部数据项
			case WORDS:
				// 插入数据,返回插入记录的ID
				long rowId = db.insert("dict", Words.Word._ID, values);
				// 如果插入成功返回uri
				if (rowId > 0)
				{
					// 在已有的 Uri的后面追加ID
					Uri wordUri = ContentUris.withAppendedId(uri, rowId);
					// 通知数据已经改变
					getContext().getContentResolver()
							.notifyChange(wordUri, null);
					return wordUri;
				}
				break;
			default :
				throw new IllegalArgumentException("未知Uri:" + uri);
		}
		return null;
	}
	// 修改数据的方法
	@Override
	public int update(Uri uri, ContentValues values, String where,
					  String[] whereArgs)
	{
		SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
		// 记录所修改的记录数
		int num = 0;
		switch (matcher.match(uri))
		{
			// 如果Uri参数代表操作全部数据项
			case WORDS:
				num = db.update("dict", values, where, whereArgs);
				break;
			// 如果Uri参数代表操作指定数据项
			case WORD:
				// 解析出想修改的记录ID
				long id = ContentUris.parseId(uri);
				String whereClause = Words.Word._ID + "=" + id;
				// 如果原来的where子句存在,拼接where子句
				if (where != null && !where.equals(""))
				{
					whereClause = whereClause + " and " + where;
				}
				num = db.update("dict", values, whereClause, whereArgs);
				break;
			default:
				throw new IllegalArgumentException("未知Uri:" + uri);
		}
		// 通知数据已经改变
		getContext().getContentResolver().notifyChange(uri, null);
		return num;
	}
	// 删除数据的方法
	@Override
	public int delete(Uri uri, String where, String[] whereArgs)
	{
		SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
		// 记录所删除的记录数
		int num = 0;
		// 对uri进行匹配
		switch (matcher.match(uri))
		{
			// 如果Uri参数代表操作全部数据项
			case WORDS:
				num = db.delete("dict", where, whereArgs);
				break;
			// 如果Uri参数代表操作指定数据项
			case WORD:
				// 解析出所需要删除的记录ID
				long id = ContentUris.parseId(uri);
				String whereClause = Words.Word._ID + "=" + id;
				// 如果原来的where子句存在,拼接where子句
				if (where != null && !where.equals(""))
				{
					whereClause = whereClause + " and " + where;
				}
				num = db.delete("dict", whereClause, whereArgs);
				break;
			default:
				throw new IllegalArgumentException("未知Uri:" + uri);
		}
		// 通知数据已经改变
		getContext().getContentResolver().notifyChange(uri, null);
		return num;
	}
}


接下来需要在AndroidMainifest.xml文件中注册该ContentProvider,需要在AndroidManifest.xml文件中增加如下配置片段。

<!--注册一个ContentProvider-->
<provider andorid:name=".DictProvider"
	andorid:authorities="org.crazyit.providers.dictprovider"
	android:exported="true"
/>

接下来在访问前面DictProvider所共享的数据:

package org.crazyit.resolver;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import org.crazyit.content.Words;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;


public class MainActivity extends Activity
{
	ContentResolver contentResolver;
	Button insert = null;
	Button search = null;
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		// 获取系统的ContentResolver对象
		contentResolver = getContentResolver();
		insert = (Button) findViewById(R.id.insert);
		search = (Button) findViewById(R.id.search);
		// 为insert按钮的单击事件绑定事件监听器
		insert.setOnClickListener(new OnClickListener()
		{
			@Override
			public void onClick(View source)
			{
				// 获取用户输入
				String word = ((EditText) findViewById(R.id.word))
					.getText().toString();
				String detail = ((EditText) findViewById(R.id.detail))
					.getText().toString();
				// 插入生词记录
				ContentValues values = new ContentValues();
				values.put(Words.Word.WORD, word);
				values.put(Words.Word.DETAIL, detail);
				contentResolver.insert(
					Words.Word.DICT_CONTENT_URI, values);
				// 显示提示信息
				Toast.makeText(MainActivity.this, "添加生词成功!"
					, Toast.LENGTH_SHORT).show();
			}
		});
		// 为search按钮的单击事件绑定事件监听器
		search.setOnClickListener(new OnClickListener()
		{
			@Override
			public void onClick(View source)
			{
				// 获取用户输入
				String key = ((EditText) findViewById(R.id.key))
					.getText().toString();
				// 执行查询
				Cursor cursor = contentResolver.query(
					Words.Word.DICT_CONTENT_URI, null,
					"word like ? or detail like ?", new String[] {
					"%" + key + "%", "%" + key + "%" }, null);
				// 创建一个Bundle对象
				Bundle data = new Bundle();
				data.putSerializable("data", converCursorToList(cursor));
				// 创建一个Intent
				Intent intent = new Intent(MainActivity.this,
					ResultActivity.class);
				intent.putExtras(data);
				// 启动Activity
				startActivity(intent);
			}
		});
	}
	private ArrayList<Map<String, String>> converCursorToList(Cursor cursor)
	{
		ArrayList<Map<String, String>> result = new ArrayList<>();
		// 遍历Cursor结果集
		while (cursor.moveToNext())
		{
			// 将结果集中的数据存入ArrayList中
			Map<String, String> map = new HashMap<>();
			// 取出查询记录中第2列、第3列的值
			map.put(Words.Word.WORD, cursor.getString(1));
			map.put(Words.Word.DETAIL, cursor.getString(2));
			result.add(map);
		}
		return result;
	}
}


三.操作系统的ContentProvider
Android系统本身提供了大量的ContentProvider,例如联系人信息,系统的多媒体信息等。

1.Android系统用于管理联系人的ContentProvider的几个uri如下
ContactsContract.Contacts.CONTENT_URI:管理联系人的uri;
ContactsContract.CommonDataKinds.Phone.CONTENT_URI:管理联系人的电话的uri;
ContactsContract.CommonDataKinds.Email.CONTENT_URI:管理联系人的E-mail的uri;

实例:使用ContentProvider管理联系人
下面实例程序中包含两个按钮,其中一个按钮用于查询系统的联系人数据,另一个按钮用于添加在三个文本框中输入联系人名字,电话号码,E-mail地址。

public class MainActivity extends Activity
{
	Button search;
	Button add;
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		// 获取系统界面中查找、添加两个按钮
		search = (Button) findViewById(R.id.search);
		add = (Button) findViewById(R.id.add);
		search.setOnClickListener(new OnClickListener()
		{
			@Override
			public void onClick(View source)
			{
				// 定义两个List来封装系统的联系人信息、指定联系人的电话号码、Email等详情
				final ArrayList<String> names = new ArrayList<>();
				final ArrayList<ArrayList<String>> details = new ArrayList<>();
				// 使用ContentResolver查找联系人数据
				Cursor cursor = getContentResolver().query(
					ContactsContract.Contacts.CONTENT_URI, null, null,
					null, null);
				// 遍历查询结果,获取系统中所有联系人
				while (cursor.moveToNext())
				{
					// 获取联系人ID
					String contactId = cursor.getString(cursor
						.getColumnIndex(ContactsContract.Contacts._ID));
					// 获取联系人的名字
					String name = cursor.getString(cursor.getColumnIndex(
						ContactsContract.Contacts.DISPLAY_NAME));
					names.add(name);
					// 使用ContentResolver查找联系人的电话号码
					Cursor phones = getContentResolver().query(
						ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
						null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID
						+ " = " + contactId, null, null);
					ArrayList<String> detail = new ArrayList<>();
					// 遍历查询结果,获取该联系人的多个电话号码
					while (phones.moveToNext())
					{
						// 获取查询结果中电话号码列中数据
						String phoneNumber = phones.getString(phones
							.getColumnIndex(ContactsContract
									.CommonDataKinds.Phone.NUMBER));
						detail.add("电话号码:" + phoneNumber);
					}
					phones.close();
					// 使用ContentResolver查找联系人的E-mail地址
					Cursor emails = getContentResolver().query(
						ContactsContract.CommonDataKinds.Email.CONTENT_URI,
						null, ContactsContract.CommonDataKinds.Email
						.CONTACT_ID + " = " + contactId, null, null);
					// 遍历查询结果,获取该联系人的多个E-mail地址
					while (emails.moveToNext())
					{
						// 获取查询结果中E-mail地址列中数据
						String emailAddress = emails.getString(emails
							.getColumnIndex(ContactsContract
									.CommonDataKinds.Email.DATA));
						detail.add("邮件地址:" + emailAddress);
					}
					emails.close();
					details.add(detail);
				}
				cursor.close();
				// 加载result.xml界面布局代表的视图
				View resultDialog = getLayoutInflater().inflate(
					R.layout.result, null);
				// 获取resultDialog中ID为list的ExpandableListView
				ExpandableListView list = (ExpandableListView) resultDialog
					.findViewById(R.id.list);
				// 创建一个ExpandableListAdapter对象
				ExpandableListAdapter adapter =
					new BaseExpandableListAdapter()
					{
						// 获取指定组位置、指定子列表项处的子列表项数据
						@Override
						public Object getChild(int groupPosition,
											   int childPosition)
						{
							return details.get(groupPosition).get(
									childPosition);
						}
						@Override
						public long getChildId(int groupPosition,
											   int childPosition)
						{
							return childPosition;
						}
						@Override
						public int getChildrenCount(int groupPosition)
						{
							return details.get(groupPosition).size();
						}
						private TextView getTextView()
						{
							AbsListView.LayoutParams lp = new AbsListView
								.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT
								, 64);
							TextView textView = new TextView(
								MainActivity.this);
							textView.setLayoutParams(lp);
							textView.setGravity(Gravity.CENTER_VERTICAL
								| Gravity.LEFT);
							textView.setPadding(36, 0, 0, 0);
							textView.setTextSize(20);
							return textView;
						}
						// 该方法决定每个子选项的外观
						@Override
						public View getChildView(int groupPosition,
							int childPosition, boolean isLastChild,
							View convertView, ViewGroup parent)
						{
							TextView textView = getTextView();
							textView.setText(getChild(groupPosition,
								childPosition).toString());
							return textView;
						}
						// 获取指定组位置处的组数据
						@Override
						public Object getGroup(int groupPosition)
						{
							return names.get(groupPosition);
						}
						@Override
						public int getGroupCount()
						{
							return names.size();
						}
						@Override
						public long getGroupId(int groupPosition)
						{
							return groupPosition;
						}
						// 该方法决定每个组选项的外观
						@Override
						public View getGroupView(int groupPosition,
							boolean isExpanded, View convertView,
							ViewGroup parent)
						{
							TextView textView = getTextView();
							textView.setText(getGroup(groupPosition)
								.toString());
							return textView;
						}
						@Override
						public boolean isChildSelectable(int groupPosition,
							int childPosition)
						{
							return true;
						}
						@Override
						public boolean hasStableIds()
						{
							return true;
						}
					};
				// 为ExpandableListView设置Adapter对象
				list.setAdapter(adapter);
				// 使用对话框来显示查询结果
				new AlertDialog.Builder(MainActivity.this)
					.setView(resultDialog).setPositiveButton("确定", null)
					.show();
			}
		});
		// 为add按钮的单击事件绑定监听器
		add.setOnClickListener(new OnClickListener()
		{
			@Override
			public void onClick(View v)
			{
				// 获取程序界面中的三个文本框的内容
				String name = ((EditText) findViewById(R.id.name))
					.getText().toString();
				String phone = ((EditText) findViewById(R.id.phone))
					.getText().toString();
				String email = ((EditText) findViewById(R.id.email))
					.getText().toString();
				// 创建一个空的ContentValues
				ContentValues values = new ContentValues();
				// 向RawContacts.CONTENT_URI执行一个空值插入
				// 目的是获取系统返回的rawContactId
				Uri rawContactUri = getContentResolver().insert(
					ContactsContract.RawContacts.CONTENT_URI, values);
				long rawContactId = ContentUris.parseId(rawContactUri);
				values.clear();
				values.put(Data.RAW_CONTACT_ID, rawContactId);
				// 设置内容类型
				values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
				// 设置联系人名字
				values.put(StructuredName.GIVEN_NAME, name);
				// 向联系人URI添加联系人名字
				getContentResolver().insert(android.provider.ContactsContract
					.Data.CONTENT_URI, values);
				values.clear();
				values.put(Data.RAW_CONTACT_ID, rawContactId);
				values.put(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
				// 设置联系人的电话号码
				values.put(Phone.NUMBER, phone);
				// 设置电话类型
				values.put(Phone.TYPE, Phone.TYPE_MOBILE);
				// 向联系人电话号码URI添加电话号码
				getContentResolver().insert(android.provider.ContactsContract
					.Data.CONTENT_URI, values);
				values.clear();
				values.put(Data.RAW_CONTACT_ID, rawContactId);
				values.put(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
				// 设置联系人的E-mail地址
				values.put(Email.DATA, email);
				// 设置该电子邮件的类型
				values.put(Email.TYPE, Email.TYPE_WORK);
				// 向联系人E-mail URI添加E-mail数据
				getContentResolver().insert(android.provider.ContactsContract
					.Data.CONTENT_URI, values);
				Toast.makeText(MainActivity.this, "联系人数据添加成功",
					Toast.LENGTH_SHORT).show();
			}
		});
	}
}

通过ContentResolver将联系人信息,电话信息,E-mail信息查询出来之后,使用一个ExpandableListView来显示所有联系人的信息。

注意要在AndoridManifest.xml为该应用程序授权:

<!--授予读联系人ContentProvider的权限-->
<uses-permission android:name="andorid.permission.READ_CONTACTS"/>
<!--授予写联系人ContentProvider的权限-->
<users-permission andorid:name="android.permission.WRITE_CONTACTS"/>

2.使用ContentProvider管理多媒体内容
Android提供了Camera程序来支持拍照,拍摄视频,用户拍摄的照片,视频都将存放在固定的位置。
Android为多媒体提供的ContentProvider的Uri如下:
在这里插入图片描述
实例:
界面中包含两个按钮,一个查看按钮,用于查看多媒体数据中的所有图片;一个添加按钮,用于向多媒体数据中添加图片。
该程序中查看按钮所绑定的事件监听器的代码如下:
MainActivity.java

package com.danny.mediaprovider;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentValues;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.provider.MediaStore.Images.Media;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SimpleAdapter;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


public class MainActivity extends Activity
{
    Button add;
    Button view;
    ListView show;
    ArrayList<String> names = new ArrayList<>();
    ArrayList<String> descs = new ArrayList<>();
    ArrayList<String> fileNames = new ArrayList<>();
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        add = (Button) findViewById(R.id.add);
        view = (Button) findViewById(R.id.view);
        show = (ListView) findViewById(R.id.show);
        

        // 为view按钮的单击事件绑定监听器
        view.setOnClickListener(new OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                // 清空names、descs、fileNames集合里原有的数据
                names.clear();
                descs.clear();
                fileNames.clear();
                // 通过ContentResolver查询所有图片信息
                Cursor cursor = getContentResolver().query(
                        Media.EXTERNAL_CONTENT_URI, null, null, null, null);
                while (cursor.moveToNext())
                {
                    // 获取图片的显示名
                    String name = cursor.getString(cursor
                            .getColumnIndex(Media.DISPLAY_NAME));
                    // 获取图片的详细描述
                    String desc = cursor.getString(cursor
                            .getColumnIndex(Media.DESCRIPTION));
                    // 获取图片的保存位置的数据
                    byte[] data = cursor.getBlob(cursor
                            .getColumnIndex(Media.DATA));
                    // 将图片名添加到names集合中
                    names.add(name);
                    // 将图片描述添加到descs集合中
                    descs.add(desc);
                    // 将图片保存路径添加到fileNames集合中
                    fileNames.add(new String(data, 0, data.length - 1));
                }
                // 创建一个List集合,List集合的元素是Map
                List<Map<String, Object>> listItems = new ArrayList<>();
                // 将names、descs两个集合对象的数据转换到Map集合中
                for (int i = 0; i < names.size(); i++)
                {
                    Map<String, Object> listItem = new HashMap<>();
                    listItem.put("name", names.get(i));
                    listItem.put("desc", descs.get(i));
                    listItems.add(listItem);
                }
                // 创建一个SimpleAdapter
                SimpleAdapter simpleAdapter = new SimpleAdapter(
                        MainActivity.this, listItems ,
                        R.layout.line, new String[] { "name", "desc" }
                        , new int[] {R.id.name, R.id.desc });
                // 为show ListView组件设置Adapter
                show.setAdapter(simpleAdapter);
            }
        });
        // 为show ListView的列表项单击事件添加监听器
        show.setOnItemClickListener(new OnItemClickListener()
        {
            @Override
            public void onItemClick(AdapterView<?> parent
                    , View source, int position, long id)
            {
                // 加载view.xml界面布局代表的视图
                View viewDialog = getLayoutInflater().inflate(
                        R.layout.view, null);
                // 获取viewDialog中ID为image的组件
                ImageView image = (ImageView) viewDialog
                        .findViewById(R.id.image);
                // 设置image显示指定图片
                image.setImageBitmap(BitmapFactory.decodeFile(
                        fileNames.get(position)));
                // 使用对话框显示用户单击的图片
                new AlertDialog.Builder(MainActivity.this)
                        .setView(viewDialog).setPositiveButton("确定", null)
                        .show();
            }
        });

        // 为add按钮的单击事件绑定监听器
        add.setOnClickListener(new OnClickListener()
        {
            @Override
            public void onClick(View v)
            {
                // 创建ContentValues对象,准备插入数据
                ContentValues values = new ContentValues();
                values.put(Media.DISPLAY_NAME, "jinta");
                values.put(Media.DESCRIPTION, "金塔");
                values.put(Media.MIME_TYPE, "image/jpeg");
                // 插入数据,返回所插入数据对应的Uri
                Uri uri = getContentResolver().insert(
                        Media.EXTERNAL_CONTENT_URI, values);
                // 加载应用程序下的jinta图片
                Bitmap bitmap = BitmapFactory.decodeResource(
                        MainActivity.this.getResources(),
                        R.drawable.jinta);
                System.out.println("======");
                OutputStream os = null;
                try
                {
                    // 获取刚插入的数据的Uri对应的输出流
                    os = getContentResolver().openOutputStream(uri); // ①
                    // 将bitmap图片保存到Uri对应的数据节点中
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os);
                    os.close();
                }
                catch (IOException e)
                {
                    e.printStackTrace();
                }
            }
        });

    }
}


界面文件:
main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="match_parent"
	android:layout_height="match_parent">
<LinearLayout 
	android:orientation="horizontal"
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	android:gravity="center_horizontal"
	>
<Button 
	android:id="@+id/add"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:text="@string/add"
	/>
<Button 
	android:id="@+id/view"
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:text="@string/view"
	/>	
</LinearLayout>	
<ListView 
	android:id="@+id/show"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	/>		
</LinearLayout>

line.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:paddingLeft="12dp">
<TextView 
	android:id="@+id/name"
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	android:textSize="20dip"
	/>
<TextView 
	android:id="@+id/desc"
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	android:lines="2"
	/>
</LinearLayout>

view.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="match_parent"
	android:layout_height="match_parent">
<ImageView 
	android:id="@+id/image"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:scaleType="fitCenter"
	/>		
</LinearLayout>

四.监听ContentProvider的数据改变

有些时候,应用程序需要实时监听ContentProvider所共享数据的改变,并随着ContentProvider的数据的改变而提供响应,这就需要ContentObserver了。

1.ContentObserver简介

在ContentProvider时,不管实现insert,delete,update方法中的哪一个,只要该方法导致ContentProvider数据的改变,程序就调用如下代码:

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

该代码用于通知所有注册在该uri上的监听者:该ContentProvider所共享的数据发生变化。

为了监听指定ContentProvider的数据变化,需要通过ContentResolver向指定uri注册ContentObserver监听器。
ContentResolver提供了如下方法来注册监听器。

registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)
uri:该监听器所监听的ContentProviderUri;
notifyForDescendents:如果该参数设为true,假如注册监听的uri为content://abc,那么uri为content://abc//xyz、content://abc/xyz/foo的数据的改变也会触发该监听器的;
如果设为false,那么就只有content://abc。
observer:监听器实例。

如下为指定Uri注册监听器:

getContentResolver().registerContentObserver(Uri.parse("content://sms"),
	true, new SmsObserver(new Handler()));

实例:监听用户发出的短信

本实例通过监听uri为content://sms的数据改变即可监听到用户短信的数据改变,并在监听器的onChange(boolean selfChange)方法里查询Uri为content://sms/outbox的数据,这样即可获取用户正在发送的短信。

MainActivity.java

public class MainActivity extends Activity
{
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		// 为content://sms的数据改变注册监听器
		getContentResolver().registerContentObserver(
			Uri.parse("content://sms"), true,
			new SmsObserver(new Handler()));
	}
	// 提供自定义的ContentObserver监听器类
	private final class SmsObserver extends ContentObserver
	{
		public SmsObserver(Handler handler)
		{
			super(handler);
		}
		public void onChange(boolean selfChange)
		{
			// 查询发送箱中的短信(处于正在发送状态的短信放在发送箱)
			Cursor cursor = getContentResolver().query(
				Uri.parse("content://sms/outbox")
				, null, null, null, null);
			// 遍历查询得到的结果集,即可获取用户正在发送的短信
			while (cursor.moveToNext())
			{
				StringBuilder sb = new StringBuilder();
				// 获取短信的发送地址
				sb.append("address=").append(cursor
					.getString(cursor.getColumnIndex("address")));
				// 获取短信的标题
				sb.append(";subject=").append(cursor
					.getString(cursor.getColumnIndex("subject")));
				// 获取短信的内容
				sb.append(";body=").append(cursor
					.getString(cursor.getColumnIndex("body")));
				// 获取短信的发送时间
				sb.append(";time=").append(cursor
					.getLong(cursor.getColumnIndex("date")));
				System.out.println("发送短信:" + sb.toString());
			}
		}
	}
}

上面程序中的监听Uri为content://sms的数据改变,就可以监听到用户短信数据的改变,第二行用户查询content://sms/outbox的全部数据,即发件箱的全部内容。

这个监听用户发送短信的程序采用Activity来实现并不合适,用户必须要先打开该Activity,并在保存该Activity不关闭的情况下,用户所发送的短信才会被监听到。实际开发应该是利用service组件去监听。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值