目标一、内容提供者概述
目标数据模型Uri 二、创建内容提供者
目标创建内容提供者的步骤 三、访问其他应用程序
目标3.1 查询其他程序数据3.2 UriMatcher类3.3 实战演练—读取手机通讯录 四、内容观察者
目标4.1 什么是内容观察者
**1.** **创建内容观察者****2.** **注册内容观察者****3. 取消内容观察者** 4.2 实战演练—监测数据变化
目标掌握内容提供者的创建方式,能够独立完成创建内容提供者掌握使用内容提供者访问其他应用程序的步骤,能够实现读取手机通讯录的功能掌握内容观察者的使用方式,能够使用内容观察者观察其他程序的数据变化
在第5章数据存储中学习了Android数据持久化技术,包括文件存储、SharedPreferences存储以及数据库存储,这些持久化技术所保存的数据都只能在当前应用程序中访问。但在Android开发中,有时也会访问其他应用程序的数据。为了实现这种跨程序共享数据的功能,Android系统提供了一个组件ContentProvider(内容提供者)。本章将针对内容提供者进行详细地讲解。
一、内容提供者概述 目标熟悉内容提供者,能够归纳内容提供者的工作原理
内容提供者(ContentProvider)是Android系统四大组件之一,它是不同应用程序之间进行数据共享的标准API,通过ContentResolver类可以访问ContentProvider中共享的数据。ContentProvider的工作原理如下:
数据模型 ContentProvider使用基于数据库模型的简单表格来提供需要共享的数据,在该表格中,每一行表示一条记录,而每一列代表特定类型和含义的数据,并且其中每一条数据记录都包含一个名为“_ID”的字段类标识每条数据。
Uri ContentResolver提供一系列增删改查的方法对数据进行操作,并且这些方法以Uri的形式对外提供数据。Uri为内容提供者中的数据建立了唯一标识符。它主要由三部分组成,scheme、authorities和path。
二、创建内容提供者 目标掌握内容提供者的创建方式,能够独立完成创建内容提供者 创建内容提供者的步骤
(1)选中程序包名右击选择【New】->【Other】->【Content Provider】选项
(2)输入内容提供者的Class Name(类名称)和URI Authorities(唯一标识,通常使用包名)
(3)点击“Finish”按钮完成创建
内容提供者创建完成后,Android Studio会自动在AndroidManifest.xml中对内容提供者进行注册。
三、访问其他应用程序 目标......
掌握使用内容提供者访问其他应用程序的步骤,能够实现读取手机通讯录的功能 3.1 查询其他程序数据
通过ContentProvider查询其他程序数据的具体步骤如下:
- 通过parse()方法解析Uri
Uri uri = Uri.parse("content://cn.itcast.mycontentprovider/person");
- 通过query()方法查询数据
//获取ContentResolver对象 ContentResolver resolver = context.getContentResolver(); Cursor cursor = resolver.query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder);
- 通过while()循环语句遍历查询到的数据
while (cursor.moveToNext()) {
String address = cursor.getString(0);
long date = cursor.getLong(1);
int type = cursor.getInt(2);
}
cursor.close(); //关闭cursor
3.2 UriMatcher类
如果一个ContentProvider中含有多个数据源(比如多个表)时,就需要对不同的Uri进行区分,此时可以用UriMatcher类对Uri进行匹配,匹配步骤如下:
1. 初始化UriMatcher类
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
2.注册需要的Uri
//第一个参数:Uri的authority部分
//第二个参数:Uri的path部分
//第三个参数:Uri匹配成功后返回的匹配码
matcher.addURI("cn.itcast.contentprovider", "people", PEOPLE);
matcher.addURI("cn.itcast.contentprovider", "person/#", PEOPLE_ID);
3. 与已经注册的Uri进行匹配
Uri uri = Uri.parse("content://" + "cn.itcast.contentprovider" + "/people");
int match = matcher.match(uri);
switch (match){
case PEOPLE:
//匹配成功后做的相关操作
case PEOPLE_ID:
//匹配成功后做的相关操作
default:
return null;
}
3.3 实战演练—读取手机通讯录
本节我们会通过一个读取手机通讯录的案例来演示如何使用ContentResolver操作Android设备的通讯录中暴露的数据。本案例的界面效果如下图所示。
放置界面控件 reslayoutactivity_contact.xml
搭建列表条目 reslayoutcontact_item.xml
封装实体类 contactsContactInfo.java
package cn.itcast.contacts;
public class ContactInfo {
private String contactName; //联系人名称
private String phoneNumber; //电话号码
public String getContactName() {
return contactName;
}
public void setContactName(String contactName) {
this.contactName = contactName;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
编写列表适配器 contactsContactAdapter.java
package cn.itcast.contacts; import android.content.Context; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import java.util.List; public class ContactAdapter extends RecyclerView.Adapter{ private Context mContext; private List contactInfoList; public ContactAdapter(Context context, List contactInfoList) { this.mContext = context; this.contactInfoList = contactInfoList; } // inflate()加载布局文件contact_item.xml @Override public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { MyViewHolder holder = new MyViewHolder( LayoutInflater.from(mContext).inflate( R.layout.contact_item, parent, false)); return holder; } //setText()将传递来的数据绑定到界面控件上 @Override public void onBindViewHolder(MyViewHolder holder, int position) { holder.tv_name.setText(contactInfoList.get(position).getContactName()); holder.tv_phone.setText(contactInfoList.get(position).getPhoneNumber()); } // 获取contactInfoList中数据的总条数 @Override public int getItemCount() { return contactInfoList.size(); } class MyViewHolder extends RecyclerView.ViewHolder { TextView tv_name, tv_phone; ImageView iv_photo; // findViewById()来获取界面上的控件 public MyViewHolder(View view) { super(view); tv_name = view.findViewById(R.id.tv_name); tv_phone = view.findViewById(R.id.tv_phone); iv_photo = view.findViewById(R.id.iv_photo); } } }
显示界面数据 contactsContactActivity.java
package cn.itcast.contacts;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.Build;
import android.provider.ContactsContract;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class ContactActivity extends AppCompatActivity {
private ContactAdapter adapter;
private RecyclerView rv_contact;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contact);
init();
}
// 5.将获取的数据显示到通讯录界面上
private void setData() {
// getContacts()用来获取手机通讯录中的数据
List contactInfos = getContacts();
// 创建数据适配器adapter
adapter = new ContactAdapter(ContactActivity.this, contactInfos);
// 将adapter对象设置到列表控件rv_contact上
rv_contact.setAdapter(adapter);
}
// 4.获取手机通讯录的数据
public List getContacts() {
List contactInfos = new ArrayList<>();
// 获取通讯录数据,并将其存放到cursor对象中
Cursor cursor = getContentResolver().query(ContactsContract.
Contacts.CONTENT_URI, null, null, null, null);
if (contactInfos != null) contactInfos.clear(); //清除集合中的数据
// 获取联系人名称和电话号码,并将其设置到contactInfo类的对象中
while (cursor.moveToNext()) {
String id = cursor.getString(
cursor.getColumnIndex(ContactsContract.Contacts._ID));
String name = cursor.getString(cursor.getColumnIndex(ContactsContract.
Contacts.DISPLAY_NAME));
int isHas = Integer.parseInt(cursor.getString(cursor.getColumnIndex(
ContactsContract.Contacts.HAS_PHONE_NUMBER)));
if (isHas > 0) {
Cursor c = getContentResolver().query(ContactsContract.
CommonDataKinds.Phone.CONTENT_URI, null,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID +
" = " + id, null, null);
// 将contactInfo类的对象添加到集合contactInfos里
while (c.moveToNext()) {
ContactInfo info = new ContactInfo();
info.setContactName(name);
String number = c.getString(c.getColumnIndex(ContactsContract.
CommonDataKinds.Phone.NUMBER)).trim();
number = number.replace(" ", "");
number = number.replace("-", "");
info.setPhoneNumber(number);
contactInfos.add(info);
}
c.close();
}
}
cursor.close();
return contactInfos;
}
// 1.初始化界面控件
private void init() {
// 获取列表控件rv_contact
rv_contact = findViewById(R.id.rv_contact);
// 设置列表的方向为垂直方向
rv_contact.setLayoutManager(new LinearLayoutManager(this));
// 申请读取手机通讯录的权限
getPermissions();
}
String[] permissionList;
// 2.申请读取手机通讯录的权限
public void getPermissions() {
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
permissionList = new String[]{"android.permission.READ_CONTACTS"};
ArrayList list = new ArrayList();
// 循环判断 permissionList 所需权限中有哪个尚未被授权,并将它加入到 list 中
for (int i = 0; i < permissionList.length; i++) {
if (ActivityCompat.checkSelfPermission(this, permissionList[i])
!= PackageManager.PERMISSION_GRANTED)
list.add(permissionList[i]);
}
// 存在未被授权的的权限,因此调用requestPermissions()方法来申请这些权限
if (list.size() > 0) {
ActivityCompat.requestPermissions(this,
list.toArray(new String[list.size()]), 1);
} else {
setData();//后续创建该方法
}
} else {
setData(); //后续创建该方法
}
}
// 3.获取申请通讯录权限信息是否成功的返回信息
// requestCode 请求码
// permissions 系统的权限数组
// grantResults 请求权限的状态
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1) {
for (int i = 0; i < permissions.length; i++) {
// 判断返回的权限数组permissions是否包含 读取通讯录的权限: "android.permission.READ_CONTACTS"
// 并且该权限的状态值grantResults是否为 允许读取Android设备的通讯录: PackageManager.PERMISSION_GRANTED
if (permissions[i].equals("android.permission.READ_CONTACTS")
&& grantResults[i] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "读取通讯录权限申请成功",
Toast.LENGTH_SHORT).show();
setData();//后续创建该方法
} else {
Toast.makeText(this, "读取通讯录权限申请失败",
Toast.LENGTH_SHORT).show();
}
}
}
}
}
四、内容观察者
目标
掌握内容观察者的使用方式,能够使用内容观察者观察其他程序的数据变化 4.1 什么是内容观察者
内容观察者(ContentObserver)用于观察指定Uri所代表的数据的变化,当ContentObserver观察到指定Uri代表的数据发生变化时,就会触发onChange()方法,此时在onChange()方法中使用ContentResovler可以查询到变化的数据。
要使用ContentObserver观察数据变化,就必须在ContentProvider中调用ContentResolver的notifyChange()方法。
通过ContentObserver中的onChange()方法观察特定的Uri代表的数据的具体步骤如下:
1. 创建内容观察者private class MyObserver extends ContentObserver{ //创建内容观察者
public MyObserver(Handler handler) {
super(handler);
}
// 当观察到Uri代表的数据发生变化时调用此方法,并在该方法中处理相关逻辑
public void onChange(boolean selfChange) {
super.onChange(selfChange);
}
}
2. 注册内容观察者
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://aaa.bbb.ccc");
//注册内容观察者
//第一个参数uri 内容提供者的Uri
//第二个参数true 表示可以匹配Uri派生的其他Uri,false时只匹配当前提供的Uri
//第三个参数new MyObserver(new Handler()) 表示创建的内容观察者
resolver.registerContentObserver(uri, true, new MyObserver(new Handler()));
注册内容观察者的方法原型为:
public final void registerContentObserver(Uri uri,
boolean notifyForDescendents, ContentObserver observer)
功能:为指定的Uri注册一个ContentObserver派生类实例,当指定的Uri发生改变时,回调该实例对象去处理。
参数1:uri 需要观察的Uri
参数2:notifyForDescendents false表示只匹配该Uri true表示可以同时匹配其派生的Uri
参数3:observer 创建的内容观察者对象
3. 取消内容观察者@Override
protected void onDestroy() {
super.onDestroy();
// 取消注册的内容观察者
getContentResolver().unregisterContentObserver(new MyObserver(new Handler()));
}
注意:
在内容观察者监听的ContentProvider中,重写的insert()方法、delete()方法、update()方法中会调用ContentResolver的notifyChange()方法。
notifyChange(uri,null):用来通知所有注册在该Uri上的监听者,该ContentProvider共享的数据发生了变化
第一个参数: 表示Uri第二个参数: 表示内容观察者,null表示该ContentProvider共享的数据发生了变化 4.2 实战演练—监测数据变化
本节就通过检测数据变化的案例来讲解如何使用内容观察者。本案例的界面效果如下图所示。
放置界面控件 reslayoutactivity_main.xml
创建数据库 contentobserverdbPersonDBOpenHelper.java
package cn.itcast.contentobserverdb;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class PersonDBOpenHelper extends SQLiteOpenHelper {
//构造方法,调用该方法创建一个person.db数据库
public PersonDBOpenHelper(Context context) {
// context 上下文
// "person.db" 数据库的名称
// null 数据库的查询结果集
// 1 数据库的版本号
// 当调用super()方法时,程序会创建一个名为person的数据库
super(context, "person.db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
//创建该数据库的同时新建一个info表,表中有_id,name这两个字段
db.execSQL("create table info (_id integer primary key autoincrement, name varchar(20))");
}
// 当数据库版本升级时会回调该方法,在该方法中对数据库进行升级
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
创建内容提供者
package cn.itcast.contentobserverdb;
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 PersonProvider extends ContentProvider {
//定义一个uri路径的匹配器,如果路径匹配不成功返回-1
private static UriMatcher mUriMatcher = new UriMatcher(-1);
private static final int SUCCESS = 1; //匹配路径成功时的返回码
private PersonDBOpenHelper helper; //数据库操作类的对象
//添加路径匹配器的规则,该静态代码块在程序启动时执行,且只执行一次。
static {
mUriMatcher.addURI("cn.itcast.contentobserverdb", "info", SUCCESS);
}
@Override
public boolean onCreate() { //当内容提供者被创建时调用
helper = new PersonDBOpenHelper(getContext());
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
//匹配查询的Uri路径,若成功则返回SUCCESS,失败则抛出不合法的参数异常:IllegalArgumentException
int code = mUriMatcher.match(uri);
if (code == SUCCESS) {
SQLiteDatabase db = helper.getReadableDatabase();
// 查询info表的信息并返回
return db.query("info", projection, selection, selectionArgs,
null, null, sortOrder);
} else {
throw new IllegalArgumentException("路径不正确,无法查询数据!");
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
int code = mUriMatcher.match(uri);
if (code == SUCCESS) {
SQLiteDatabase db = helper.getReadableDatabase();
long rowId = db.insert("info", null, values);
if (rowId > 0) {
// rowId > 0表示数据添加成功,通过withAppendedId()方法重新构建一个Uri
// uri 内容提供者PersonProvider的Uri
// rowId 添加的数据在数据库表中的行id
Uri insertedUri = ContentUris.withAppendedId(uri, rowId);
//notifyChange()方法通知注册在该程序Uri上的内容观察者有数据发生变化了
getContext().getContentResolver().notifyChange(insertedUri, null);
return insertedUri;
}
db.close();
return uri;
} else {
throw new IllegalArgumentException("路径不正确,无法插入数据!");
}
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int code = mUriMatcher.match(uri);
if (code == SUCCESS) {
SQLiteDatabase db = helper.getWritableDatabase();
int count = db.delete("info", selection, selectionArgs);
//提示数据库的内容变化了
if (count > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
db.close();
return count;
} else {
throw new IllegalArgumentException("路径不正确,无法随便删除数据!");
}
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int code = mUriMatcher.match(uri);
if (code == SUCCESS) {
SQLiteDatabase db = helper.getWritableDatabase();
int count = db.update("info", values, selection, selectionArgs);
//提示数据库的内容变化了
if (count > 0) {
getContext().getContentResolver().notifyChange(uri, null);
}
db.close();
return count;
} else {
throw new IllegalArgumentException("路径不正确,无法更新数据!");
}
}
@Override
public String getType(Uri uri) {
return null;
}
}
编写界面交互代码
package cn.itcast.contentobserverdb;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
public class MainActivity extends AppCompatActivity implements
View.OnClickListener {
private ContentResolver resolver;
private Uri uri;
private ContentValues values;
private Button btnInsert;
private Button btnUpdate;
private Button btnDelete;
private Button btnSelect;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView(); //初始化界面
createDB(); //创建数据库
}
// 初始化界面控件并设置控件的点击监听事件
private void initView() {
btnInsert = findViewById(R.id.btn_insert);
btnUpdate = findViewById(R.id.btn_update);
btnDelete = findViewById(R.id.btn_delete);
btnSelect = findViewById(R.id.btn_select);
btnInsert.setOnClickListener(this);
btnUpdate.setOnClickListener(this);
btnDelete.setOnClickListener(this);
btnSelect.setOnClickListener(this);
}
private void createDB() {
//创建helper对象的同时,也创建了person.db数据库
PersonDBOpenHelper helper = new PersonDBOpenHelper(this);
// 获取数据库类SQLiteDatabase的对象db
SQLiteDatabase db = helper.getWritableDatabase();
// 向数据库info表中插入三条数据
for (int i = 0; i < 3; i++) {
ContentValues values = new ContentValues();
values.put("name", "itcast" + i);
db.insert("info", null, values);
}
db.close();
}
@Override
public void onClick(View v) {
//得到一个内容提供者的解析对象
resolver = getContentResolver();
//获取一个Uri路径
uri = Uri.parse("content://cn.itcast.contentobserverdb/info");
//新建一个ContentValues对象,该对象以key-values的形式来添加数据到数据库表中
values = new ContentValues();
switch (v.getId()) {
case R.id.btn_insert:
Random random = new Random();
// 将要添加的数据添加到values对象中
values.put("name", "add_itcast" + random.nextInt(10));
// 将values对象里的数据添加到数据库中
// uri 内容提供者PersonProvider的Uri
// values 要添加的数据
Uri newuri = resolver.insert(uri, values);
// Toast和Log用来提示添加成功的信息
Toast.makeText(this, "添加成功", Toast.LENGTH_SHORT).show();
Log.i("数据库应用", "添加");
break;
case R.id.btn_delete:
//返回删除数据的条目数
int deleteCount = resolver.delete(uri, "name=?",
new String[]{"itcast0"});
Toast.makeText(this, "成功删除了" + deleteCount + "行",
Toast.LENGTH_SHORT).show();
Log.i("数据库应用", "删除");
break;
case R.id.btn_select:
List
至此,操作数据库的程序就创建完成了,接下来创建监测数据库变化的程序,具体步骤如下:
monitordataMainActivity.java
package cn.itcast.monitordata;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 该uri路径指向数据库应用中的数据库info表
Uri uri = Uri.parse("content://cn.itcast.contentobserverdb/info");
//注册内容观察者,
// 参数uri指向要监测的数据库info表,
//参数true定义了监测的范围,
// 最后一个参数是一个内容观察者对象
getContentResolver().registerContentObserver(uri, true,
new MyObserver(new Handler()));
}
private class MyObserver extends ContentObserver {
public MyObserver(Handler handler) {//handler 是一个消息处理器。
super(handler);
}
@Override
//当info表中的数据发生变化时则执行该方法
public void onChange(boolean selfChange) {
Log.i("监测数据变化", "有人动了你的数据库!");
super.onChange(selfChange);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//取消注册内容观察者
getContentResolver().unregisterContentObserver(new MyObserver(
new Handler()));
}
}
注意:
内容观察者的目的是观察特定Uri引起的数据库的变化,继而做一些相应的处理,这种方式效率高内存消耗少,需要初学者掌握。
ntentobserverdb/info");
//注册内容观察者,
// 参数uri指向要监测的数据库info表,
//参数true定义了监测的范围,
// 最后一个参数是一个内容观察者对象
getContentResolver().registerContentObserver(uri, true,
new MyObserver(new Handler()));
}
private class MyObserver extends ContentObserver {
public MyObserver(Handler handler) {//handler 是一个消息处理器。
super(handler);
}
@Override
//当info表中的数据发生变化时则执行该方法
public void onChange(boolean selfChange) {
Log.i("监测数据变化", "有人动了你的数据库!");
super.onChange(selfChange);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//取消注册内容观察者
getContentResolver().unregisterContentObserver(new MyObserver(
new Handler()));
}
}
注意: 内容观察者的目的是观察特定Uri引起的数据库的变化,继而做一些相应的处理,这种方式效率高内存消耗少,需要初学者掌握。



