在上一节《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绑定,将姓名和手机号插入到数据库中。

更多相关文章

  1. 谈谈android数据存储方式
  2. 【Android Training - 04】保存数据 [ Lesson 3 - 保存数据到SQL
  3. Android JNI 使用的数据结构JNINativeMethod详解 ||建立Android
  4. Android之SQlite数据库
  5. android平台下基于ffmpeg的swscale模块实现对YUV和RGB数据进行转
  6. Android通过加载其他应用的Dex文件破解关键数据
  7. Android入门篇六:使用意图传递数据之返回结果
  8. Android之ListActivity:布局与数据绑定

随机推荐

  1. android ctrl +鼠标左键直接打开xml文件
  2. android加载大量图片内存溢出bitmap size
  3. android 获取相册图片及路径
  4. 3.体验android 4.2.2
  5. 动手学Android之九——列表没那么简单
  6. 解决下载android sdk慢的问题
  7. android Process.killProcess 和 System.
  8. SwipeRecyclerview使用中一些常见错误处
  9. 最新Android(安卓)4.x 搭建开发环境
  10. Android(安卓)厘米转dip、px转dip 地图比