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:该监听器所监听的ContentProvider的Uri;
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组件去监听。