导语
内容提供者ContentProvider介绍
一、内容提供者ContentProvider
1.1 概念
- 应用的数据库是不允许其他应用访问的,数据库文件 一般是私有的。 -rw-rw- — 别的应用程序是没办法访问私有的数据库。
- 数据库文件私有的目的: 保证应用程序数据的安全。每个应用程序都是独立的,不可以操作另外一个应用程序数据库的数据。
- 有一些特殊的需求,需要把自己私有的数据库暴露给别的应用程序让别的应用程序访问。
- 内容提供者就是做这个事情的。 - 内容提供者的作用就是让别的应用访问到你的私有数据
 ###1.2 自定义内容提供者
 
- 内容提供者的作用就是让别的应用访问到你的私有数据
- 步骤1.自定义java类,继承ContentProvider类,重写增删改查方法,在方法中写增删改查数据库的代码(重写增删改查方法分为两小步,1获取操作数据库的db对象,2利用db对象去重写增删改查方法),举例增方法 
 public class PeopleProvider extends ContentProvider {- private MyOpenHelper oh;//数据库创建的帮助类 private SQLiteDatabase db;//定义数据库实例对象,这两行代码实际完成一个功能,就是获取操作数据库的db对象 //用于区分同一数据库下,不同表的操作 UriMatcher um = new UriMatcher(UriMatcher.NO_MATCH); { //arg0:主机名,必须与内容提供者主机名一致 //arg1:路径,随便写,但一般与表名相同,路径中可以携带数据 um.addURI("com.itheima.people", "person", 1);//content://com.itheima.people/person um.addURI("com.itheima.people", "handsome", 2);//content://com.itheima.people/handsome um.addURI("com.itheima.people", null, 3);//content://com.itheima.people um.addURI("com.itheima.people", "person/#", 4);//content://com.itheima.people/person/2 } //内容提供者创建时此方法执行(写死了此内容提供者的数据库) @Override public boolean onCreate() { //获取操作数据库的db对象 oh = new MyOpenHelper(getContext()); db = oh.getWritableDatabase(); return false; } //根据Uri的不同,操作不同的表 //此方法是由其他应用调用的,所以插入什么数据由其他应用决定 @Override public Uri insert(Uri uri, ContentValues values) { if(um.match(uri) == 1){//判断操作数据库中的那张表 long id = db.insert("person", null, values); } else if(um.match(uri) == 2){//判断操作数据库中的那张表 long id = db.insert("handsome", null, values); } else{ throw new IllegalArgumentException("别tm瞎传uri"); } return uri; } }
- 步骤2.在清单文件中定义内容提供者的标签,配置包括:内容提供者provider。配置完整类路径name,主机名authorities。注意必须要有authorities属性,这是内容提供者的主机名,功能类似地址, - <provider android:name="com.itheima.contentprovider.PersonProvider" android:authorities="com.itheima.people" android:exported="true" ></provider>
###1.3如何在其它应用使用内容提供者查询数据
- 创建一个其他应用,访问自定义的内容提供者,实现对数据库的插入操作
public void click(View v){
            //得到内容分解器对象(获取内容提供者的解析器)
            ContentResolver cr = getContentResolver();
            //封装要插入的数据
            ContentValues cv = new ContentValues();
            cv.put(“name”, “小方”);
            cv.put(“phone”, 138856);
            cv.put(“money”, 3000);
            //通过内容提供者的解析器,实现对数据库的插入操作
            //arg0:用来匹配内容提供者主机名的Uri
            //arg1:要插入的数据
            //(每个应用的内容提供者内部已经通过Uri对自己应用数据库进行UriMatcher操作,只需要其它应用通过解析器匹配具体内容提供者的主机名就能调用方法完成对数据库的操作),url:内容提供者的主机名,要加前缀content://
            cr.insert(Uri.parse(“content://com.itheima.person”), cv);
        }
–示例–
public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    //通过内容提供者,在其应用数据库插入数据
    public void insert(View v){
        //通过内容提供者的解析器,向01数据库插入数据
        ContentResolver cr = getContentResolver() ;
        ContentValues values = new ContentValues();
        values.put("name", "巫师2");
        //values.put("money", "12306");//handsome表没有money字段值,写上报错
        cr.insert(Uri.parse("content://com.itheima.people/handsome"), values);
        values.clear();
        values.put("name", "小黑2");
        values.put("money", "1000000");
        cr.insert(Uri.parse("content://com.itheima.people/person"), values);
    }
    //通过内容提供者,在其应用数据库删除数据
    public void delete(View v){
        //通过内容提供者的解析器,向01数据库删除数据
        ContentResolver cr = getContentResolver() ;
        int i = cr.delete(Uri.parse("content://com.itheima.people/person"), "name=?", new String[]{"猴子"});
        System.out.println(i);
    }
    //通过内容提供者,在其应用数据库更新数据
    public void update(View v){
        //通过内容提供者的解析器,向01数据库更新数据
        ContentResolver cr = getContentResolver() ;
        ContentValues values = new  ContentValues();
        values.put("name", "混沌骑士");
        int i =cr.update(Uri.parse("content://com.itheima.people/person"), values, "name=?", new String[]{"恩赐"});
        System.out.println(i);
    }
    //通过内容提供者,在其应用数据库查询数据
    public void query(View v){
        //通过内容提供者的解析器,向01数据库查询数据
        ContentResolver cr = getContentResolver() ;
        Cursor cursor = cr.query(Uri.parse("content://com.itheima.people/person"), null, null, null, null);
        while(cursor.moveToNext()){
            String id = cursor.getString(0);//一条记录的id字段值
            String name = cursor.getString(1);//一条记录的名字字段值
            String money = cursor.getString(cursor.getColumnIndex("money"));//一条记录的money字段值
            System.out.println(id+";"+name+";"+money);
        }
    }
    //通过内容提供者,在其应用数据库根据id查询单条记录
    public void queryid(View v){
        //通过内容提供者的解析器,向01数据库查询数据
        ContentResolver cr = getContentResolver() ;
        Cursor cursor = cr.query(Uri.parse("content://com.itheima.people/person"), null, "_id=?", new String[]{"3"}, null);
        while(cursor.moveToNext()){
            String id = cursor.getString(0);//一条记录的id字段值
            String name = cursor.getString(1);//一条记录的名字字段值
            String money = cursor.getString(cursor.getColumnIndex("money"));//一条记录的money字段值
            System.out.println(id+";"+name+";"+money);
        }
    }
}
###1.4 UriMatcher
- UriMatcher作用:用于判断一条uri跟指定的多条uri中的哪条匹配(用于匹配数据库中的不同数据表,对匹配到的表进行操作)
 #####实现UriMatcher步骤
- 1.在内容提供者类中,创建UriMatcher对象,添加匹配规则
//指定多条uri
        um.addURI(“com.itheima.person”, “person”, PERSON_CODE);
        um.addURI(“com.itheima.person”, “company”, COMPANY_CODE);
        //#号可以代表任意数字
        um.addURI(“com.itheima.person”, “person/#”, QUERY_ONE_PERSON_CODE);
–示例–
UriMatcher um = new UriMatcher(UriMatcher.NO_MATCH);
{
    //arg0:主机名,必须与内容提供者主机名一致
    //arg1:路径,随便写,但一般与表名相同,路径中可以携带数据
    //arg2:若匹配怎返回该值
    um.addURI("com.itheima.people", "person", 1);//content://com.itheima.people/person
    um.addURI("com.itheima.people", "handsome", 2);//content://com.itheima.people/handsome
    um.addURI("com.itheima.people", null, 3);//content://com.itheima.people
    um.addURI("com.itheima.people", "person/#", 4);//content://com.itheima.people/person/2
}
- 2.在内容提供者类中,根据传入的uri,通过Uri匹配器可以实现操作不同的表
@Override
        public Uri insert(Uri uri, ContentValues values) {
            if(um.match(uri) == PERSON_CODE){
                db.insert(“person”, null, values);
            }
            else if(um.match(uri) == COMPANY_CODE){
                db.insert(“company”, null, values);
            }
            else{
                throw new IllegalArgumentException();
            }
            return uri;
        }
–示例–
//接UriMatcher,根据传入的uri判断将对那张表进行操作,此插入方法是由其他应用调用的,所以插入什么数据由其他应用决定,
@Override
public Uri insert(Uri uri, ContentValues values) {
    if(um.match(uri) == 1){
        long id = db.insert("person", null, values);
    }
    else if(um.match(uri) == 2){
        long id = db.insert("handsome", null, values);
    }
    else{
        throw new IllegalArgumentException("别tm瞎传uri");
    }
    return uri;
}
- 3.另外:路径中可以携带数据,如果路径中带有数字,把数字提取出来的api
int id = (int) ContentUris.parseId(uri);
–示例–
  @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(“handsome”, projection, selection, selectionArgs, null, null, sortOrder, null);
            }
            else if(um.match(uri) == 4){
                //把路径中携带的数字取出来
                long id = ContentUris.parseId(uri);
                cursor = db.query(“person”, projection, “_id = ?”, new String[]{id + “”}, null, null, sortOrder, null);
            }
            return cursor;
        }
###1.5读取系统短信数据库
- 只需要关注sms表 - 通过阅读源码 短信的内容提供者 - android:authorities="sms"`
- content://sms/ 这个就是短信内容提供者的路径 
 
- 只需要关注4个字段- body:短信内容
- address:短信的发件人或收件人号码(跟你聊天那哥们的号码)
- date:短信时间
- type:1为收到,2为发送
 
- 读取系统短信,首先查询源码获得短信数据库内容提供者的主机名和路径,然后
   //获取内容提供者的解析器
        ContentResolver cr = getContentResolver();
        //查询获取每个记录有指定字段值的所有记录
        Cursor c = cr.query(Uri.parse(“content://sms”), new String[]{“body”, “date”, “address”, “type”}, null, null, null);
        //遍历每条记录
        while(c.moveToNext()){
            //获取每条记录的指定字段值
            String body = c.getString(0);
            String date = c.getString(1);
            String address = c.getString(2);
            String type = c.getString(3);
            System.out.println(body+”;” + date + “;” + address + “;” + type);
        }
- 读取系统短信、备份短信需要权限 - <uses-permission android:name="android.permission.READ_SMS"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
–Activity界面读取短信到控制台,短信备份到xml文件示例
//创建存放短信对象的集合
List<Sms> smsList = new ArrayList<Sms>();
//读取系统短信,封装到短信对象
public void click1(View v){
    //通过内容提供者获取系统短信,封装到短信对象中
    ContentResolver cr = getContentResolver();
    Cursor cursor = cr.query(Uri.parse("content://sms"), new String[]{"body", "date", "type", "address"}, null, null, null);
    while(cursor.moveToNext()){
        String body = cursor.getString(0);
        long date = cursor.getLong(1);
        int type = cursor.getInt(2);
        String address = cursor.getString(3);
        Sms sms = new Sms(body, type, date, address);
        smsList.add(sms);
    }
}
//短信备份到xml文件(要先执行click1)
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, "smss");
        for (Sms 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.getAddresss());
            xs.endTag(null, "address");
            xs.endTag(null, "sms");
        }
        xs.endTag(null, "smss");
        xs.endDocument();
    } catch (Exception e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
}
####1.5.1插入系统短信
ContentResolver cr = getContentResolver();
ContentValues cv = new ContentValues();
cv.put("body", "您尾号为XXXX的招行储蓄卡收到转账1,000,000人民币");
cv.put("address", 95555);
//1为收到短信,2为发出短信
cv.put("type", 1);
cv.put("date", System.currentTimeMillis());
cr.insert(Uri.parse("content://sms"), cv);
- 插入查询系统短信需要注册权限 - <uses-permission android:name="android.permission.READ_SMS" /> <uses-permission android:name="android.permission.WRITE_SMS"/>
–插入系统短信示例–
public void click(View v){
    Thread t = new Thread(){
        @Override
        public void run() {
            try {
                //7.5秒后插入短信
                sleep(7500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //通过内容提供者把短信插入短信数据库中
            ContentResolver cr = getContentResolver();
            ContentValues values = new ContentValues();
            values.put("address", "95555");
            values.put("body", "您尾号为XXXX招行储蓄卡收到转账1,000,000元人民币");
            values.put("date", System.currentTimeMillis());
            values.put("type", 1);
            cr.insert(Uri.parse("content://sms"), values);
        }
    };
    t.start();
}
###1.6 联系人的内容提供者
####1.6.1 联系人数据库最重要三张表
- raw_contacts 表 保存联系人的id
- data 表 保存联系人的数据
- mimetypes 表 保存数据的类型
- raw_contacts表:- contact_id:联系人id
 
- data表:联系人的具体信息,一个信息占一行- data1:信息的具体内容
- raw_contact_id:联系人id,描述信息属于哪个联系人
- mimetype_id:描述信息是属于什么类型
 
- mimetypes表:通过mimetype_id到该表查看具体类型
####1.6.2 如何查询联系人的数据库
- 查询raw_contacts 把 联系人的 id获取出来
- 根据id 查询data表 把这个联系人的数据取出来
- 根据mimetypes表定义的数据类型 知道这个数据是电话 邮箱 姓名。
####1.6.3 联系人删除的时候重要结论
删除联系人并不是把联系人的数据全部清空,
只是把联系人的contact_id给设置为null。
联系人真正的数据并没有被清除还是保留在数据库里的。
设计的目的: 为了方便云同步。
####1.6.3 读取联系人
- 先查询raw_contacts表拿到联系人id - Cursor cursor = cr.query(Uri.parse("content://com.android.contacts/raw_contacts"), new String[]{"contact_id"}, null, null, null);
- 然后拿着联系人id去data表查询属于该联系人的信息 - Cursor c = cr.query(Uri.parse("content://com.android.contacts/data"), new String[]{"data1", "mimetype"}, "raw_contact_id = ?", new String[]{contact_id}, null);
- 得到data1字段的值,就是联系人的信息,通过mimetype判断是什么类型的信息 - while(c.moveToNext()){ String data1 = c.getString(0); String mimetype = c.getString(1); if("vnd.android.cursor.item/email_v2".equals(mimetype)){ contact.setEmail(data1); } else if("vnd.android.cursor.item/name".equals(mimetype)){ contact.setName(data1); } else if("vnd.android.cursor.item/phone_v2".equals(mimetype)){ contact.setPhone(data1); } }
- 添加权限 - <uses-permission android:name="android.permission.READ_CONTACTS"/>- –读取联系人示例 - public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void click(View v){ //获取内容提供者的解析器 ContentResolver cr = getContentResolver(); //查询raw_contacts表,获取contact_id字段的记录(所有记录) Cursor cursor = cr.query(Uri.parse("content://com.android.contacts/raw_contacts"), new String[]{"contact_id"}, null, null, null); //遍历每条记录 while(cursor.moveToNext()){ //得到每条记录中字段为contact_id的值(另外:在查询数据库表时,查询了多个字段,那么角标依次向后排,可获取一条记录中其他字段值) String contact_id = cursor.getString(0); //根据获取到的contact_id作为查询data表的条件,获取指定字段值的所有记录 Cursor cursorData = cr.query(Uri.parse("content://com.android.contacts/data"), new String[]{"data1", "mimetype"}, "raw_contact_id = ?", new String[]{contact_id}, null); //创建联系人对象,有name,phone,email 3个成员变量 Contact contact = new Contact(); //遍历从data表获取的所有符合条件的记录,从cursor中取出该联系人的信息 while(cursorData.moveToNext()){ //获取同一raw_contact_id下的每条记录字段data1和mimetype的值 String data1 = cursorData.getString(0); String mimetype = cursorData.getString(1); //根据mimetype值不同,将同在一条记录中的data1的字段值封装到联系人对象中。 if("vnd.android.cursor.item/name".equals(mimetype)){ contact.setName(data1); } else if("vnd.android.cursor.item/phone_v2".equals(mimetype)){ contact.setPhone(data1); } else if("vnd.android.cursor.item/email_v2".equals(mimetype)){ contact.setEmail(data1); } } System.out.println(contact.toString()); } } }- ####1.6.4 插入联系人 
- 先查询raw_contacts表,确定新的联系人的id应该是多少
- 把确定的联系人id插入raw_contacts表 - cv.put("contact_id", _id); cr.insert(Uri.parse("content://com.android.contacts/raw_contacts"), cv);
- 在data表插入数据 - 插3个字段:data1、mimetype、raw_contact_id,对于同一个联系人raw_contact_id是不变的,data1和mimetype是对应改变的,多条记录才能完成对一个联系人的插入 - cv = new ContentValues(); cv.put("data1", "赵六"); cv.put("mimetype", "vnd.android.cursor.item/name"); cv.put("raw_contact_id", _id); cr.insert(Uri.parse("content://com.android.contacts/data"), cv); cv = new ContentValues(); cv.put("data1", "1596874"); cv.put("mimetype", "vnd.android.cursor.item/phone_v2"); cv.put("raw_contact_id", _id); cr.insert(Uri.parse("content://com.android.contacts/data"), cv);
 
- 添加权限 - <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
    Cursor cursor = cr.query(Uri.parse("content://com.android.contacts/raw_contacts"), new String[]{"_id"}, null, null, null);
    //设置主键初始为1
    int contact_id = 1;
    //指针移动至最后一行,若成功返回true
    if(cursor.moveToLast()){
        //获取每条记录中的_id
        int _id = cursor.getInt(0);
        //给conttcact_id重新赋值
        contact_id = ++_id;
    }
    //往raw_contacts表中插入联系人id
    ContentValues values = new ContentValues();
    values.put("contact_id", contact_id);
    cr.insert(Uri.parse("content://com.android.contacts/raw_contacts"), values);
    //往data表中插入联系人信息
    values.clear();
    values.put("data1", "包九日的儿子");
    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", "13888888");
    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);
}
#二、内容观察者ContentObserver
###(应用:比如数据库发生变化,发出通知,被注册的内容观察者接收并进行处理)
####2.1 概念
- 用来接收内容提供者发出的通知
 ####2.2 内容观察者具体实现
- 当数据库数据改变时,内容提供者会发出通知,在内容提供者的uri上注册一个内容观察者,就可以收到数据改变的通知
- 1.定义观察者类
- 2.注册内容观察者(使用内容提供者解析器注册内容观察者) - //Activity一运行就执行此方法 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //2.注册内容观察者 ContentResolver cr = getContentResolver(); //arg0:指定接收哪个内容提供者发出的通知(在内容提供者的uri上注册一个内容观察者) //arg1:根据boolean值判断匹配精度,一般写true //true:表示只要是以content://sms开头的uri上的数据改变,就能收到通知 //false:表示必须精确匹配content://sms这个uri才能收到通知,例如content://sms/inbox就收不到了 //arg2: 传入一个观察者对象 cr.registerContentObserver(Uri.parse("content://sms"), true, new MyObserver(new Handler())); } //1.定义观察者类 class MyObserver extends ContentObserver{ public MyObserver(Handler handler) { super(handler); } //收到通知时此方法调用 @Override public void onChange(boolean selfChange) { super.onChange(selfChange); System.out.println("短信数据库改变了"); } }- ####2.3在内容提供者中发通知的代码 - ContentResolver cr = getContext().getContentResolver(); //发出通知,所有注册在这个uri上的内容观察者都可以收到通知 //当数据库数据改变时,发送通知 //arg0:所有注册在这个uri上的内容观察者都能收到此通知 cr.notifyChange(uri, null);
–在内容提供者中发通知的代码示例–
//重写内容提供者的增删改方法,并发出通知
//此方法是由其他应用调用的,所以插入什么数据由其他应用决定
@Override
public Uri insert(Uri uri, ContentValues values) {
    if(um.match(uri) == 1){
        long id = db.insert("person", null, values);
        //当数据库数据改变时,发送通知
        //arg0:所有注册在这个uri上的内容观察者都能收到此通知
        getContext().getContentResolver().notifyChange(uri, null);
    }
    else if(um.match(uri) == 2){
        long id = db.insert("handsome", null, values);
        //当数据库数据改变时,发送通知
        //arg0:所有注册在这个uri上的内容观察者都能收到此通知
        getContext().getContentResolver().notifyChange(uri, null);
    }
    else{
        throw new IllegalArgumentException("别tm瞎传uri");
    }
    return uri;
}