您现在的位置是:首页 > 正文

安卓基础:内容提供者ContentProvider

2024-01-30 22:21:09阅读 0
一、基本概念
内容提供器(Content Provider)主要用于在不同的应用程序之间实现数据共享的功能,
它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据
的安全性。
二、自定义内容提供者实现增删改查
1、创建一PersonProvider继承ContentProvider,实现六个方法,增删改查以及onCreate和getType。(因篇幅限制,这里只贴出了具有代表性的几个方法)     
public class PersonProvider extends ContentProvider {

     private MyOpenHelper oh;
     SQLiteDatabase db;
  
     //内容提供者创建时调用
     @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 = db.query("person", projection, selection, selectionArgs, null, null, sortOrder, null);
          return cursor;
     }

     @Override
     public String getType(Uri uri) {          
          return null;
     }
     //此方法供其他应用调用,用于往people数据库里插数据
     //values:由其他应用传入,用于封装要插入的数据
     //uri:内容提供者的主机名,也就是地址
     @Override
     public Uri insert(Uri uri, ContentValues values) {
          //使用uri匹配器匹配传入的uri  
              db.insert("person", null, values); 
          return uri;
     }
2、 因为我们的内容提供者主要是在数据库中进行增删改查,所以我们再创建一个MyOpenHelper继承SQLiteOpenHelper,创建一个 people.db
然后创建一个安卓Junit测试类,创建出我们的数据库文件。因为代码很简单,这里就不再贴出。
3、在AndroidManifest中配置我们的内容提供者
authorities属性必须加。是自定义的一个主机名,用来给外部程序访问时传入的Uri参数
exported=“true”代表是外部程序可以修改我们内容提供者提供的数据,也必须加。
<provider android:name="com.itheima.customcontentprovider.provider.PersonProvider"
            android:authorities="com.itheima.people"
            android:exported="true"
            ></provider>

4、然后我们再重新创建一个工程,来读取我们的内容提供者的数据。

public class MainActivity extends Activity {

     private ContentResolver cResolver;

     @Override
     protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
           cResolver = getContentResolver();
     }

     public void insert(View view) {
          ContentValues values = new ContentValues();
          values.put("name", "lizhenquan");
           cResolver.insert(Uri.parse("content://com.zhenquan.myprovider/teacher"), values);
     }

     public void myquery(View view) {
          Cursor cursor = cResolver.query(Uri.parse("content://com.zhenquan.myprovider"), null, null, null, null);
          while(cursor.moveToNext()){
              String nameString = cursor.getString(1);
              String moneString = cursor.getString(2);
              Log.d("TAG", nameString+","+moneString);
          }

     }
}
这里我们需要注意的就是一个标准的Uri的格式:
一个标准的内容 URI 写法是这样的:
content://com.example.app.provider/table1
这就表示调用方期望访问的是 com.example.app 这个应用的 table1 表中的数据。除此之外,我们还可以在这个内容 URI 的后面加上一个 id
content://com.example.app.provider/table1/1
这就表示调用方期望访问的是 com.example.app 这个应用的 table1 表中 id 为 1 的数据。
内容 URI 的格式主要就只有以上两种,以路径结尾就表示期望访问该表中所有的数据,以 id 结尾就表示期望访问该表中拥有相应 id 的数据。我们可以使用通配符的方式来分别匹配这两种格式的内容 URI,规则如下。
1. *:表示匹配任意长度的任意字符
2. #:表示匹配任意长度的数字
所以,一个能够匹配任意表的内容 URI 格式就可以写成:
content://com.example.app.provider/*
而一个能够匹配 table1 表中任意一行数据的内容 URI 格式就可以写成:
content://com.example.app.provider/table1/#


三、Uri匹配器
上面我们的一个案例是只创建了一个person表,但是在实际开发中,数据库肯定不会只有一个表,那么面对多个 表的时候,内容提供者如何区分要查询的是哪张表,或者要插入的是哪张表呢?
这就引出了我们的Uri匹配器的概念。
先将数据库升级,增加一个teacher表,然后修改我们的PersonProvider如下(因篇幅限制,所以我们只贴出insert和query):
public class PersonProvider extends ContentProvider {

     private MyOpenHelper oh;
     SQLiteDatabase db;

      //创建uri匹配器对象
     static UriMatcher um = new UriMatcher(UriMatcher.NO_MATCH);
     //检测其他用户传入的uri与匹配器定义好的uri中,哪条匹配
     static {
          um.addURI("com.itheima.people", "person", 1);//content://com.itheima.people/person
          um.addURI("com.itheima.people", "teacher", 2);//content://com.itheima.people/teacher
          um.addURI("com.itheima.people", "person/#", 3);//content://com.itheima.people/person/4
     }

     //内容提供者创建时调用
     @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;
     }

借助 UriMatcher这个类就可以轻松地实现匹配内容 URI的功能。 UriMatcher中提供了一个 addURI()方法,这个方法接收三个参数,可以分别把权限、路径和一个自定义
代码传进去。这样,当调用 UriMatcher 的 match()方法时,就可以将一个 Uri 对象传入,返回值是某个能够匹配这个 Uri 对象所对应的自定义代码,利用这个代码,我们就可以判断出
调用方期望访问的是哪张表中的数据了。官方推荐用static,所以我们就用的static对象。

可以发现这里我们还对getType方法增加的逻辑,那么这个方法是用来干嘛的呢?
它是所有的内容提供器都必须提供的一个方法,用于获取 Uri 对象所对应的 MIME 类型。一个内容 URI 所对应的 MIME字符串主要由三部分组分,Android 对这三个部分做了如下格式规定。
1. 必须以 vnd 开头。
2. 如果内容 URI 以路径结尾,则后接 android.cursor.dir/,如果内容 URI 以 id 结尾,则后接 android.cursor.item/。
3. 最后接上 vnd.<authority>.<path>。
所以,对于 content://com.example.app.provider/table1 这个内容 URI,它所对应的 MIME类型就可以写成:
vnd.android.cursor.dir/vnd.com.example.app.provider.table1          也可以简写vnd.android.cursor.dir/table1
对于 content://com.example.app.provider/table1/1 这个内容 URI,它所对应的 MIME 类型就可以写成:
vnd.android.cursor.item/vnd. com.example.app.provider.table1      也可以简写vnd.android.cursor.item /table1


四、获取系统短信案例
其实在实际开发中,一般我们并不会将自己应用的数据暴露给其他应用,内容提供者大多数的时候都是获取系统应用的数据,所以我们接下来做一个获取系统短信的案例
第一步:查看安卓系统源码,找到短信的应用的AndroidManifest,找到内容提供者的主机名,以及权限。实现当我们点击Button,Log打印出短信的“地址,时间,内容,类型(发送的还是接收的)”
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);
        }
    }
第二步:添加权限(因为我们读取系统短信,属于侵犯用户隐私,所以肯定是要添加读取短信的权限的)
  < uses-permission android:name = "android.permission.READ_SMS" />
五、获取系统联系人案例
第一步:实现需求逻辑:点击按钮获取联系人姓名和手机号
public void click1(View v){
          ContentResolver cResolver = getContentResolver();
          Cursor cursor = cResolver.query( ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                   null, null, null, null);
          while(cursor.moveToNext()){
              //获取联系人姓名
              String displayName = cursor.getString(cursor.getColumnIndex(
               ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
              // 获取联系人手机号
              String number = cursor.getString(cursor.getColumnIndex(
               ContactsContract.CommonDataKinds.Phone.NUMBER));
               System.out.println("name="+displayName+"number="+number);
          }
     }
咦,大家可能又要迷糊了,这里为什么Uri那么奇怪。 为 什 么 没 有 调 用 Uri.parse() 方 法 去 解 析 一 个 内 容 URI 字 符 串 呢 ? 这 是 因 为ContactsContract.CommonDataKinds.Phone类已经帮我们做好了封装,提供了一个CONTENT_URI常量,而这个常量就是使用 Uri.parse()方法解析出来的结果。接着我们对 Cursor 对象进行遍历,将联系人姓名和手机号这些数据逐个取出,
联系人姓名这一列对应的常量是 ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
联系人手机号这一列对应的常量是 ContactsContract.CommonDataKinds.Phone.NUMBER
第二步:添加权限
< uses-permission android:name = "android.permission.READ_CONTACTS" />



网站文章