Android高级应用2----ContentProvider(访问手机短信和通讯录数据)

在上一节《Android高级应用1----Service和AIDL》中有介绍过AIDL,作为服务进程间数据访问的接口,而对于像Android自带的SQLite数据库,如果其他的应用程序想要访问该数据库,进行CRUD,同样也需要数据接口,那么这个就是通过ContentProvider来实现,这个虽然在开发过程中,并不会用到太多,大多数是通过后台提供的数据接口来处理,但别忘记,ContentProvider也是Android的4大组件之一。

1、ContentProvider

1个应用程序想要为外界提供数据接口,就必须要使用ContentProvider对外提供
Android高级应用2----ContentProvider(访问手机短信和通讯录数据)_第1张图片
Authorities是非常重要的一个参数,这个是数据接口Uri的重要组成部分,也是是否允许对方访问数据的凭证之一。

public class MyContentProvider extends ContentProvider {
    public MyContentProvider() {
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // Implement this to handle requests to delete one or more rows.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public String getType(Uri uri) {
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO: Implement this to handle requests to insert a new row.
        throw new UnsupportedOperationException("Not yet implemented");
    }
	//做数据库的初始化
    @Override
    public boolean onCreate() {
        // TODO: Implement this to initialize your content provider on startup.
        MySQLiteOpenHelper helper = new MySQLiteOpenHelper(getContext());
        database = helper.getReadableDatabase();
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        // TODO: Implement this to handle query requests from clients.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // TODO: Implement this to handle requests to update one or more rows.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}

创建了ContentProvider,会提供4个重要的增删改查方法,需要传入Uri。

2、ContentResolver

如果其他应用程序,想要访问主App的数据,就得通过ContentResolver来实现;ContentResolver能够实现增删改查4项操作,那么就可以通过主App提供的数据接口,来进行远程数据库的CRUD操作。

那么现在首先是需要在主App中创建数据库,在创建数据库以及表的时候,就需要使用传统的SQLiteOpenHelper来创建,具体就不在此赘述,很基础的问题。

创建好数据库之后,就需要在其他应用程序中,通过数据接口访问主App的数据库,进行数据的操作。

(1)添加数据

		case R.id.btn_add:
                //添加数据
                ContentValues values = new ContentValues();
                //要与数据库中的表的字段一致
                values.put("name",name);
                values.put("age",age);
                values.put("sex",gender);
                resolver.insert(Uri.parse("content://com.lay.provider"),values);
                break;

ContentResolver的insert方法,是需要传入两个参数,一个参数是Uri,是统一资源标识符,也就是数据接口,格式为:content://authorities(在AndroidMainfest文件中注册的);第二个参数为新增的数据信息。

2020-06-05 16:40:28.998 9476-9489/com.example.providerdemo E/TAG: 其他App调用了添加方法

ContentProvider方的insert方法中,也同样存在两个参数Uri和ContentValues,这和ContentResolver是对等的,因此values也是ContentResolver的values。

@Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO: Implement this to handle requests to insert a new row.
        Log.e("TAG","其他App调用了添加方法");
        //执行真正的数据插入操作
        database.insert(StudentTable.TAB_NAME,null,values);
        //将插入的id与Uri拼接
        return  ContentUris.withAppendedId(uri,id);
    }

(2)查询数据

@Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        // TODO: Implement this to handle query requests from clients.
        Cursor cursor = database.query(StudentTable.TAB_NAME, projection, selection, selectionArgs,
                null, null, sortOrder);
        return cursor;
    }

在其他应用程序中查询数据时,可以使用SimpleCursorAdapter 来作为ListView的适配器显示数据库中某表的全部数据。

		case R.id.btn_query:
                //查询
                Cursor cursor = resolver.query(contentUri, null, null, null, null);
                SimpleCursorAdapter adapter =
                        new SimpleCursorAdapter(this,
                                R.layout.layout_student_info_item,
                                cursor,
                                new String[]{"_id","name","age","sex"},
                                new int[]{R.id.tv_number,R.id.tv_name,R.id.tv_age,R.id.tv_sex},
                                CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
                //设置ListView适配器
                lv_show.setAdapter(adapter);
                break;

(3)删除数据

 @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // Implement this to handle requests to delete one or more rows.
//        throw new UnsupportedOperationException("Not yet implemented");
        int id = database.delete(StudentTable.TAB_NAME, selection, selectionArgs);
        return id;
    }

当删除数据成功之后,会返回一个int类型的值,当int类型的值大于0时,代表删除成功。

case R.id.btn_delete:
                String ids = et_number.getText().toString();
                //占位符
                int result = resolver.delete(contentUri, "name = ?", new String[]{ids});
                Toast.makeText(MainActivity.this,"成功删除"+result,Toast.LENGTH_SHORT).show();
                break;

(4)更新数据

@Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // TODO: Implement this to handle requests to update one or more rows.
//        throw new UnsupportedOperationException("Not yet implemented");
        int update = database.update(StudentTable.TAB_NAME, values, selection, selectionArgs);
        return update;
    }

ContentValues 指的是要更改的字段以及字段值。

		case R.id.btn_update:
                //update table name = xx,age =xx where id = ?;
                ContentValues values1 = new ContentValues();
                values1.put("name","小李");
                values1.put("age",23);
                int update = resolver.update(contentUri,
                        values1, "name = ?", new String[]{"xx"});
                Toast.makeText(MainActivity.this,"更新成功"+update,Toast.LENGTH_SHORT).show();
                break;

3、Uri

在上面的例子中,使用的Uri就是content://authorities,但是如果使用这种Uri数据接口是非常不安全的,第三方如果获取到authorities,就可以无限攻击我们的数据库,这样我们的数据没有安全性的保证,因此在定义数据接口时,要定义约定好的接口,再者就是主App,要对第三方访问的数据接口做判断,是否符合通信标准,这就需要使用UriMatcher

(1)UriMatcher

UriMatcher类的主要作用就是在ContentProvider创建时,制定好匹配规则,当其他App调用该接口时,需要去验证是否匹配,根据uri给出相应的数据。

在ContentProvider创建时,就是onCreate方法

		//如果没有匹配到,就是NO_MATCH
        matcher = new UriMatcher(UriMatcher.NO_MATCH);
        //制定规则
        matcher.addURI("com.lay.provider","insert",INSERT_CODE);

在这里制定了一个插入操作的uri,也就是说在执行插入操作的时候,必须要符合这个规则,才可以将数据插入数据库。

@Override
    public Uri insert(Uri uri, ContentValues values) {
        Log.e("TAG","其他App调用了添加方法");
        
        long id = 0;
        
        //与第三方访问App传过来的uri匹配
        int code = matcher.match(uri);
        switch (code){
            case INSERT_CODE:
                id = database.insert(StudentTable.TAB_NAME, null, values);
                break;
        }
        
        return  ContentUris.withAppendedId(uri,id);
    }

插入操作,使用新制定的uri。

		case R.id.btn_add:
                String name = et_name.getText().toString();
                String age = et_age.getText().toString();
                //添加数据
                ContentValues values = new ContentValues();
                values.put("name",name);
                values.put("age",age);
                values.put("sex",gender);
                Uri parse = Uri.parse("content://com.lay.provider/insert");
                Uri uri = resolver.insert(parse, values);
                long id = ContentUris.parseId(uri);
                Toast.makeText(MainActivity.this,"插入成功"+id,Toast.LENGTH_SHORT).show();
                break;

(2)解析Uri

先看代码吧

          case R.id.btn_add:
                resolver.insert(
                        Uri.parse("content://com.lay.provider/anyway?name=张三&age=23&sex=男"),
                        new ContentValues());
                break;

在插入操作的时候,主机名是必须的,但是path并没有什么特殊的要求,随便写的,在后边有一串类似于http的get请求的参数,创建了一个空的ContentValues;

@Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO: Implement this to handle requests to insert a new row.
        Log.e("TAG","其他App调用了添加方法");
        long id = 0;
        //与第三方访问App传过来的uri匹配

        if(values.size() > 0 ){
            id = database.insert(StudentTable.TAB_NAME,null,values);
        }else{
            //解析Uri
            //content://com.lay.provider/anyway?name=张三&age=23&sex=男
            String authority = uri.getAuthority();  //com.lay.provider
            String query = uri.getQuery(); //name=张三&age=23&sex=男
            String name = uri.getQueryParameter("name");
            String age = uri.getQueryParameter("age");
            String sex = uri.getQueryParameter("sex");

            ContentValues values1 = new ContentValues();
            values1.put("name",name);
            values1.put("age",age);
            values1.put("sex",sex);

            database.insert(StudentTable.TAB_NAME,null,values1);
            Log.e("TAG","主机名"+authority+",参数:"+query);
        }
        return  ContentUris.withAppendedId(uri,id);

重点看,当ContentValues 为空时,解析Uri,通过uri的各类API,来解析出uri携带的参数,也就是需要插入数据库的参数,重新创建一个新的ContentValues 类,插入数据库。

2020-06-05 20:39:23.028 2992-3053/com.example.providerdemo E/TAG: 主机名com.lay.provider,参数:name=张三&age=23&sex=

4、访问系统通讯录和消息

对于系统的通讯录和消息,有自己的Uri ,那么通过ContentResolver,就可以实现查询(别忘记需要加权限)。

(1)读取手机短信息

短信的Uri:content://sms
收件箱:content://sms/inbox
发件箱:content://sms/sent
草稿箱:content://sms/draft

		//动态获取权限
        if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS)
                != PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_SMS},SMS_CODE);
        }


        lv_message = findViewById(R.id.lv_message);

        resolver = getContentResolver();
        //查询短消息Uri
        Uri uri = Uri.parse("content://sms");
        //查询全部
        Cursor cursor = resolver.query(uri, null, null, null, null);

        msgList = new ArrayList<>();
        
        while (cursor.moveToNext()){


            String address = cursor.getString(cursor.getColumnIndex("address"));
            String body = cursor.getString(cursor.getColumnIndex("body"));

            Message message = new Message(address,body);
            msgList.add(message);

        }

        //设置适配器
        adapter = new ListViewAdapter(this,msgList);
        lv_message.setAdapter(adapter);

(2)读取手机联系人

连续人和短消息不同的是,短消息是所有的内容都在一张表中,通过查询可以拿到所有的数据;

但是联系人的姓名以及其他信息(手机号等)是在不同的表中的,也就说不能通过一张表拿到全部信息;但是在姓名表中,除了可以拿到姓名之外,还可以拿到主键,那么该主键就是从表的外键,可以根据主键查询对应的数据。

通讯录UriContactsContract.Contacts.CONTENT_URI
姓名ContactsContract.Contacts.DISPLAY_NAME
主键ContactsContract.Contacts._ID

电话号码UriContactsContract.CommonDataKinds.Phone.CONTENT_URI
外键idContactsContract.CommonDataKinds.Phone.CONTACT_ID
电话号码ContactsContract.CommonDataKinds.Phone.NUMBER

		resolver = getContentResolver();

        //获取姓名
        Cursor cursor = resolver.query
                (ContactsContract.Contacts.CONTENT_URI,
                        null, null, null, null);

        contactList = new ArrayList<>();

        while (cursor.moveToNext()){

            //获取姓名
            String name = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
            //主键
            String _id = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts._ID));

            //查询电话号码

            String selections = ContactsContract.CommonDataKinds.Phone.CONTACT_ID+"=?";
            //根据从键的id查询电话号码
            Cursor cursor1 = resolver.query
                    (ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                            null,
                            selections,
                            new String[]{_id},
                            null);

            while (cursor1.moveToNext()){
                //拿到电话号码
                String number = cursor1.getString(cursor1.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));

                Contact contact = new Contact(name,number);
                contactList.add(contact);
            }

        }

        //设置适配器
        adapter = new ContactListViewAdapter(this,contactList);
        lv_contract.setAdapter(adapter);

别忘记添加权限:

		 //获取读取联系人权限
        if(ContextCompat.checkSelfPermission(this,Manifest.permission.READ_CONTACTS)
                != PackageManager.PERMISSION_GRANTED){
            //没有获取权限就去拿
            ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS},CONTRACTS_CODE);
        }

(3)添加手机联系人

之前是查询query联系操作,现在可以完成insert操作。

 ContentResolver resolver = getContentResolver();

                //首先插入一条空数据,得到id
                ContentValues values = new ContentValues();
                Uri uri = resolver.insert(ContactsContract.RawContacts.CONTENT_URI, values);
                long id = ContentUris.parseId(uri);

                //插入联系人
                values.put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME,"王春华");
                //姓名与ID绑定
                values.put(ContactsContract.Data.RAW_CONTACT_ID,id);
                //姓名类型
                values.put(ContactsContract.Data.MIMETYPE,
                        ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE);
                //添加
                resolver.insert(ContactsContract.Data.CONTENT_URI,values);

                //插入电话号码
                values.clear();
                values.put(ContactsContract.CommonDataKinds.Phone.NUMBER,"13155555555");
                //电话号码与ID绑定
                values.put(ContactsContract.Data.RAW_CONTACT_ID,id);
                values.put(ContactsContract.Data.MIMETYPE,
                        //电话号码的Item类型
                        ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);
                //电话号码的类型(移动 座机。。。。)
                values.put(ContactsContract.CommonDataKinds.Phone.TYPE,
                        ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
                //添加
                resolver.insert(ContactsContract.Data.CONTENT_URI,values);

对于插入操作,涉及到的常量非常多,关键的思想就是,因为手机号和姓名不在同一张表中,但是他们的id的对应的,所以首先需要拿到id;在拿到id之后,分别与该id绑定,将姓名和手机号插入到数据库中。

你可能感兴趣的:(数据库,java,sqlite,android)