内容提供者
应用的数据库是不允许其他应用访问的,内容提供者的作用就是把私有数据暴露给其他应用,通常,是把私有数据库的数据暴露给其他应用。
我们可以通过自定义内容提供者来了解内容提供者的原理
自定义内容提供者步骤:
第一步:我们想写一个myOpenHelper继承SQLiteOpenHelper,来创建一个数据库
第二步 :写myContentProviderjava类继承ContentProvider的内容提供者类,重写增删改查的方法。 内容提供者需要在清单文件中注册
具体代码:
//内容提供者需要在清单文件中注册
<provider android:name="com.xiaochen.neirong.myContentProvider" //指定具体类
android:authorities="com.xiaochen.people" //自定义authorities 其他应用吐过这个值找到对应此内容提供者
android:exported="true"></provider> //值为true表示该组件的一个实例,可以运行给所有的用户
public class PersonProvider extends ContentProvider {
private MyOpenHelper oh;
SQLiteDatabase db;
//创建uri匹配器对象,在谷歌源码中设置为静态
static UriMatcher um = new UriMatcher(UriMatcher.NO_MATCH); //NO_MATCH 表示不匹配任何路径时返回码 是-1
//检测其他用户传入的uri与匹配器定义好的uri中,哪条匹配
static {
um.addURI("com.xiaochen.people", "person", 1);//content://com.itheima.people/person 如果匹配 返回1
um.addURI("com.xiaochen.people", "teacher", 2);//content://com.itheima.people/teacher 如果匹配 返回2
um.addURI("com.xiaochen.people", "person/#", 3);//content://com.itheima.people/person/4 #代表携带任意数字 这里一般作为id携带 *代表携带任意文本
}
//内容提供者创建时调用
@Override
public boolean onCreate() {
oh = new MyOpenHelper(getContext());
db = oh.getWritableDatabase();
return false;
}
//提供查查服务
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Cursor cursor = null;
if(um.match(uri) == 1){
cursor = db.query("person", projection, selection, selectionArgs, null, null, sortOrder, null);
}
else if(um.match(uri) == 2){
cursor = db.query("teacher", projection, selection, selectionArgs, null, null, sortOrder, null);
}
else if(um.match(uri) == 3){
//把uri末尾携带的数字取出来
long id = ContentUris.parseId(uri);
cursor = db.query("person", projection, "_id = ?", new String[]{id + ""}, null, null, sortOrder, null);
}
else{//抛出非法参数异常
throw new IllegalArgumentException("uri又有问题哟亲么么哒");
}
return cursor;
}
@Override
public String getType(Uri uri) {
if(um.match(uri) == 1){
return "vnd.android.cursor.dir/person";
}
else if(um.match(uri) == 3){
return "vnd.android.cursor.item/person";
}
return null;
}
//此方法供其他应用调用,用于往people数据库里插数据
//values:由其他应用传入,用于封装要插入的数据
//uri:内容提供者的主机名,也就是地址
@Override
public Uri insert(Uri uri, ContentValues values) {
//使用uri匹配器匹配传入的uri
if(um.match(uri) == 1){
db.insert("person", null, values);
//发送数据改变的通知
//uri:通知发送到哪一个uri上,所有注册在这个uri上的内容观察者都可以收到这个通知
getContext().getContentResolver().notifyChange(uri, null);
}
else if(um.match(uri) == 2){
db.insert("teacher", null, values);
getContext().getContentResolver().notifyChange(uri, null);
}
else{
throw new IllegalArgumentException("uri有问题哟亲么么哒");
}
return uri;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int i = db.delete("person", selection, selectionArgs);
return i;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int i = db.update("person", values, selection, selectionArgs);
return i;
}
}
我们在其他应用中访问自定义内容提供者
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
//点击插入按钮,拿到内容提供者,将数据插入数据库
public void insert(View v){
//,我们想要使用内容提供者的话,通过拿到ContentResolver
ContentResolver cr = getContentResolver();
//
ContentValues values = new ContentValues();
values.put("name", "赵帅哥");
// values.put("money", "13000");
//url:内容提供者的主机名
//values:要插入的数据
cr.insert(Uri.parse("content://com.xiaochen.people/teacher"), values);
}
public void delete(View v){
ContentResolver cr = getContentResolver();
int i = cr.delete(Uri.parse("content://com.itheima.people"), "name = ?", new String[]{"小志"});
System.out.println(i);
}
public void update(View v){
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put("name", "sb志");
int i = cr.update(Uri.parse("content://com.itheima.people"), values, "name = ?", new String[]{"大志"});
System.out.println(i);
}
public void select(View v){
ContentResolver cr = getContentResolver();
Cursor cursor = cr.query(Uri.parse("content://com.itheima.people/person/4"), null, null, null, null);
while(cursor.moveToNext()){
String name = cursor.getString(1);
String money = cursor.getString(2);
System.out.println(name + ";" + money);
}
}
}
我们来操作系统的内容提供者
我们通过内容提供者来获取系统短信,备份系统短信和插入系统短信。
获取系统短信,备份系统短信
找到系统的短信数据库 文件在data/data/com.android.provides.teltphony/databases/mmssms.db
导出来之后会发现有很多表,我们需要关注sms表
sms表中关注四个字段
body:短信内容
address:短信的发件人或收件人号码(跟你聊天那哥们的号码)
date:短信时间
type:1为收到,2为发送
在获取系统短信的时候我们需要查看安卓源码,来忽的短信数据库内容提供者的主机名和路径
查看主机名 系统自带内容提供者的路径packages/providers/TelephonyProvider 在清单文件中可以查看到主机名 发现主句名是sms ,同时可以看到权限 读写短信的权限
查看路径 在对应的java类中可以看到匹配规则,查看路径
代码 我们通过内容提供者获取到系统的短信,拿到短信后封装到javabean中,备份短信然后利用xml序列化器,将数据存放到xml文件中
读取系统短信和往sd卡中写入数据需要两个权限
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
public class MainActivity extends Activity {
//定义集合封装短信
List<Message> smsList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
smsList = new ArrayList<Message>();
}
public void click(View v){
//访问内容提供者获取短信
ContentResolver cr = getContentResolver();
// 短信内容提供者的主机名 不写路径获取到所有短信
Cursor cursor = cr.query(Uri.parse("content://sms"), new String[]{"address", "date", "body", "type"},
null, null, null);
while(cursor.moveToNext()){
String address = cursor.getString(0);
long date = cursor.getLong(1);
String body = cursor.getString(2);
String type = cursor.getString(3);
Message sms = new Message(body, type, address, date);
smsList.add(sms);
}
}
public void click2(View v){
XmlSerializer xs = Xml.newSerializer();
File file = new File("sdcard/sms.xml");
FileOutputStream fos;
try {
fos = new FileOutputStream(file);
xs.setOutput(fos, "utf-8");
xs.startDocument("utf-8", true);
xs.startTag(null, "message");
for (Message sms : smsList) {
xs.startTag(null, "sms");
xs.startTag(null, "body");
xs.text(sms.getBody());
xs.endTag(null, "body");
xs.startTag(null, "date");
xs.text(sms.getDate() + "");
xs.endTag(null, "date");
xs.startTag(null, "type");
xs.text(sms.getType());
xs.endTag(null, "type");
xs.startTag(null, "address");
xs.text(sms.getAddress());
xs.endTag(null, "address");
xs.endTag(null, "sms");
}
xs.endTag(null, "message");
xs.endDocument();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
插入系统短信
读写短信权限
<uses-permission android:name="android.permission.WRITE_SMS"/>
<uses-permission android:name="android.permission.READ_SMS"/>
public void click(View v){
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put("address", 138438);
values.put("type", 1);
values.put("date", System.currentTimeMillis());
values.put("body", "我是马云");
cr.insert(Uri.parse("content://sms"), values);
}
获取系统联系人
联系人数据库 找到系统的短信数据库 文件在data/data/com.android.provides.contacts/databases/contacts2.db
关注三个表
raw_contacts表:
contact_id:联系人id
data表:联系人的具体信息,一个信息占一行
data1:信息的具体内容
raw_contact_id:联系人id,描述信息属于哪个联系人
mimetype_id:描述信息是属于什么类型
mimetypes表:通过mimetype_id到该表查看具体类型
查看主机名 系统自带内容提供者的路径packages/providers/contactsProvides 在清单文件中可以查看到主机名 发现主句名是com.android.comtacts,同时可以看到权限 读写短信的权限
查看路径 在对应的java类中可以看到匹配规则,查看路径
需要权限: <uses-permission android:name="android.permission.READ_CONTACTS"/>
public void click(View v){
//通过内容提供者访问联系人数据库
ContentResolver cr = getContentResolver(); //查询raw_contacts //查询contact_id字段
Cursor cursorContactId = cr.query(Uri.parse("content://com.android.contacts/raw_contacts"), new String[]{"contact_id"}, null, null, null);
while(cursorContactId.moveToNext()){
//获取联系人id
String contactId = cursorContactId.getString(0); //查询data表 //更具联系人id查询联系人信息
Cursor cursorData = cr.query(Uri.parse("content://com.android.contacts/data"), new String[]{"data1", "mimetype"}, //这里直接传入mimetype它会直接去mimetypes表中查询具体类型
"raw_contact_id = ?", new String[]{contactId}, null);
//获取所有字段的名字
// String[] names = cursorData.getColumnNames();
// for (String string : names) {
// System.out.println(string);
// }
Contact con = new Contact();
while(cursorData.moveToNext()){
String data1 = cursorData.getString(0);
String mimetype = cursorData.getString(1);
//通过mimetype的判断,把data1存入对应的属性
if("vnd.android.cursor.item/email_v2".equals(mimetype)){
con.setEmail(data1);
}
else if("vnd.android.cursor.item/phone_v2".equals(mimetype)){
con.setPhone(data1);
}
else if("vnd.android.cursor.item/name".equals(mimetype)){
con.setName(data1);
}
}
System.out.println(con.toString());
}
}
}
插入联系人
插入联系人需要两个权限
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
public void click(View v){
ContentResolver cr = getContentResolver();
//先查询raw_contacts表,获取最新联系人的主键,然后主键+1,就是要插入的联系人的id 这里通过主键拿 因为如果在手机上中将一个联系人删除 只是将contact_id设为null值 联系人具体信息在data表中还是存在的 如果拿到最后一个是null 给null加1这样是不行的
Cursor cursorContactId = cr.query(Uri.parse("content://com.android.contacts/raw_contacts"), new String[]{"_id"}, null, null, null);
//默认联系人id就是1
int contact_id = 1;
if(cursorContactId.moveToLast()){
//拿到主键
int _id = cursorContactId.getInt(0);
//主键+1,就是要插入的联系人id
contact_id = ++_id;
}
ContentValues values = new ContentValues();
values.put("contact_id", contact_id);
//把联系人id插入raw_contacts数据库
cr.insert(Uri.parse("content://com.android.contacts/raw_contacts"), values);
values.clear();
values.put("data1", "二bi");
values.put("mimetype", "vnd.android.cursor.item/name");
values.put("raw_contact_id", contact_id);
cr.insert(Uri.parse("content://com.android.contacts/data"), values);
values.clear();
values.put("data1", "1344567");
values.put("mimetype", "vnd.android.cursor.item/phone_v2");
values.put("raw_contact_id", contact_id);
cr.insert(Uri.parse("content://com.android.contacts/data"), values);
}
}
内容观察者:
当数据库数据改变时,内容提供者会发出通知,在内容提供者的uri上注册一个内容观察者,就可以收到数据改变的通知
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//注册一个内容观察者,监听短信数据库内容的改变
ContentResolver cr = getContentResolver();
//uri:监听哪个uri上的内容提供者的通知
//notifyForDescendents:如果是true,那么只要以content://sms开头的uri的数据改变,都能收到通知,比如content://sms/inbox
cr.registerContentObserver(Uri.parse("content://sms"), true, new MyObserver(new Handler()));
}
class MyObserver extends ContentObserver{
public MyObserver(Handler handler) {
super(handler);
// TODO Auto-generated constructor stub
}
//收到数据改变的通知,此方法调用
@Override
public void onChange(boolean selfChange) {
// TODO Auto-generated method stub
super.onChange(selfChange);
System.out.println("短信数据库改变");
}
}
}
在自定义内容提供者中,发送通知的代码
ContentResolver cr = getContext().getContentResolver();
//发出通知,所有注册在这个uri上的内容观察者都可以收到通知
cr.notifyChange(uri, null);