ContentProvider基础操作

ContentProvider是Android四大组件之一,它为存储和获取数据提供统一的接口。即将应用程序的数据提供给外部程序使用的一种手段和方法。最大特点是可以实现不同的应用程序之间共享数据。

对于ContentProvider来说,无论数据的来源是什么,他都认为是一种表,它会把数据组织成表格。

两种使用情景:1.A应用访问B应用,2A应用访问系统应用(通讯录)

1A应用访问B应用:(主要是实现定义一个ContentProvider的操作)

(1)自定义一个类继承于ContentProvider,并且实现它的方法:注意操作步骤

想要建立文件的地方->右键->new->other->Content Provider->修改名称,填写唯一的uri->完成。

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

    @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.
        return false;
    }

    @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");
    }
}

然后看一下manifest.xml:

  <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <provider
            android:name=".OutContentProvider"
            android:authorities="com.stone.mycontentprovider.outcp"
            android:enabled="true"
            android:exported="true"></provider>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

当然也可以先自定义个类,然后继承ContentProvider,实现他的方法,然后配置mainfest.xml。

ok,下一步。

(2)由于这是一个简单的使用步骤,所以就简单的按照sqlite数据库来写,当然数据的来源还可以是其他的任何的一种,这里只是举个例子

由于ContentProvider的创建是在应用第一次打开的时候就创建的,所以为了方便吧数据也在ContentProvider中创建了

 SQLiteDatabase sqLiteDatabase;

  @Override
    public boolean onCreate() {
        // TODO: Implement this to initialize your content provider on startup.
        //创建数据库
        Log.e("###", "onCreate");
        SQLiteOpenHelper helper = new SQLiteOpenHelper(getContext(), "outcp.db", null, 1) {
            @Override
            public void onCreate(SQLiteDatabase db) {
                //创建表格
                String sql = "create table demo_table(_id integer primary key autoincrement," +
                        "name varchar(20)," +
                        "age integer," +
                        "gender varchar(2))";
                db.execSQL(sql);
            }

            @Override
            public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

            }
        };
        sqLiteDatabase = helper.getReadableDatabase();
        //返回true,这样才能会返回数据
        return true;
    }

注意返回值改成true。

(3)insert()方法:增加数据,在OutContentProvider中

  @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");
        Long itemId = 0l;
        try {
            itemId = sqLiteDatabase.insertOrThrow("demo_table", null, values);
        } catch (Exception e) {
            Log.e("###", e.getLocalizedMessage());
        }
        Log.e("###", "调用了out中的insert方法");
        //表名,非空列,值,对于sqlite数据库,添加完成之后会返回一个Long的id
        //uri这个uri就是另外的程序调用contentprovider的时候传过来的uri,如果传回去那么跟调用程序的uri没有区别也没有意义
        // 所以用ContentUris把uri和itemid整合一下,成为一个新的Uri,返回回去。
        return ContentUris.withAppendedId(uri, itemId);
    }

注意:这里的 try {}catch((Exception e){} 可以不写,insertOrThrow可以改成insert。另外ContentUris是处理uri的一个类, ContentUris.withAppendedId(uri, itemId)是吧uri追加一个id作为返回值,为了方便调用者获取到添加成功的状态和id。

(4)insert()方法的调用:这里需要新建一个其他的项目,在调用项目的合适的位置写一个调用方法。这里写了一个textview的点击监听事件:

 Uri mUri = Uri.parse("content://com.stone.mycontentprovider.outcp");
ContentResolver resolver;
  @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
     
        //获取ContentResolver对象
        resolver = getContentResolver();
    }

   public void insertTv(View view) {
        ContentValues values = new ContentValues();
        values.put("name", MyUtils.getChineseName());
        values.put("age", MyUtils.getNum(10, 60));
        values.put("gender", MyUtils.getGender());

        Uri reUri = resolver.insert(mUri, values);
        Long reId = ContentUris.parseId(reUri);
        Toast.makeText(MainActivity.this, reId + "", Toast.LENGTH_LONG).show();
    }

注意:这里的uri就是ContentProvider提供者的insert方法返回的我们添加了id的那个uri,同样的我们使用ContentUris这个类的 ContentUris.parseId(reUri);方法拿到添加成功的数据的id。

(5)query()查询方法:

  @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");
        Cursor cursor = sqLiteDatabase.query("demo_table", projection, selection, selectionArgs, null, null, sortOrder);
        return cursor;
    }

(6)调用查询方法:

SimpleCursorAdapter simpleCursorAdapter;
 ListView mListView;

 public void queryTv(View view) {
// 根据contentprovider中的查询方法:Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder)
        //rui,查询哪一列,查询条件,条件之,排序
        Cursor cursor = resolver.query(mUri, null, null, null, null);
        //这里的listview的adapter用到SimpleCursorAdapter
        simpleCursorAdapter = new SimpleCursorAdapter(this,
                R.layout.layout_adapter_cursor,
                cursor,
                new String[]{"_id", "name", "age", "gender"},
                new int[]{R.id.item_id, R.id.item_name, R.id.item_age, R.id.item_gender},
                CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
        mListView.setAdapter(simpleCursorAdapter);

    }

注意这里直接用的SimpleCursorAdapter其中的参数分别是:SimpleCursorAdapter(context,layout,cursor,键名的字符串数组,对应的控件的id,标志).关于SimpleCursorAdapter有一篇博客可以看一下

(7)delete():删除方法

  @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 result = sqLiteDatabase.delete("demo_table", selection, selectionArgs);
      return result;
    }

(8)调用删除方法:

                    int delResult = resolver.delete(mUri, "_id=?", new String[]{itemId});
                        if (delResult > 0) {
                            Toast.makeText(MainActivity.this, "删除成功", Toast.LENGTH_LONG).show();
                        } else {
                            Toast.makeText(MainActivity.this, "删除灰白", Toast.LENGTH_LONG).show();
                        }

(9)修改updata()方法

  @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 upReslut = sqLiteDatabase.update("demo_table", values, selection, selectionArgs);
        return upReslut;
    }

(10)调用修改方法:

 ContentValues upValues = new ContentValues();
      upValues.put("name", MyUtils.getChineseName());
         upValues.put("age", MyUtils.getNum(10, 60));
        upValues.put("gender", MyUtils.getGender());
          int upResult=resolver.update(mUri,upValues,"_id=?",new String[]{itemId});
          if (upResult>0){
               Toast.makeText(MainActivity.this, "修改成功", Toast.LENGTH_LONG).show();
               } else {
              Toast.makeText(MainActivity.this, "修改灰白", Toast.LENGTH_LONG).show();
           }

ok,以上记录的是设置ContentProvider的方法,和相应的方法的调用然后再看一下关于Uri的解析。

Uri解析有两种方法,一种是使用UriMatcher解析,一种是Uri自带的解析。

(一)UriMatcher解析:事先制定好匹配规则,当调用ContentProvide中的方法时,利用制定好的匹配规则匹配uri,根据不同的uri给出不同的处理:以删除为例在oncreat中制定规则:

UriMatcher matcher;
 @Override
    public boolean onCreate() {
        // TODO: Implement this to initialize your content provider on startup.
        //闯将数据库
        Log.e("###", "onCreate");
        //匹配规则:
        //UriMatcher.NO_MATCH:无法匹配
        matcher = new UriMatcher(UriMatcher.NO_MATCH);
        matcher.addURI("com.stone.mycontentprovider.outcp", "democp", 1000);
        matcher.addURI("com.stone.mycontentprovider.outcp", "democp/child", 1001);
        matcher.addURI("com.stone.mycontentprovider.outcp", "democp/#", 1002);//后面是数字
        matcher.addURI("com.stone.mycontentprovider.outcp", "democp/*", 1003);//任意字符
        //返回true,这样才能会返回数据
        return true;
    }

注意:其中的democp可以使任意的字符串,也可以没有。
删除方法:

  @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 result = 0;
        int code = matcher.match(uri);
        switch (code) {
            case 1000:
                Log.e("###match","匹配到的路径是"+uri.toString());
                break;
            case 1001:
                Log.e("###match","匹配到的路径是"+uri.toString());
                break;
            case 1002:
                Log.d("###match","匹配到路径为"+uri.toString()+"任意数字的内容");
                break;
            case 1003:
                Log.i("###match","匹配到路径为"+uri.toString()+"任意字符的内容");
                break;
            default:
                Log.e("###match","执行删除数据库内容的方法");
                result = sqLiteDatabase.delete("demo_table", selection, selectionArgs);
                break;
        }
        return result;
    }

那么调用删除方法:

                 resolver.delete(Uri.parse("content://com.stone.mycontentprovider.outcp/democp"),null,null);
                   resolver.delete(Uri.parse("content://com.stone.mycontentprovider.outcp/democp/child"),null,null);
                        resolver.delete(Uri.parse("content://com.stone.mycontentprovider.outcp/democp/123"),null,null);
                        resolver.delete(Uri.parse("content://com.stone.mycontentprovider.outcp/democp/090"),null,null);
                        resolver.delete(Uri.parse("content://com.stone.mycontentprovider.outcp/democp/89ii"),null,null);
                        resolver.delete(Uri.parse("content://com.stone.mycontentprovider.outcp/democp/ab90"),null,null);
                        resolver.delete(Uri.parse("content://com.stone.mycontentprovider.outcp/123/ab90"),null,null);

运行结果:

E/###match: 匹配到的路径是content://com.stone.mycontentprovider.outcp/democp
E/###match: 匹配到的路径是content://com.stone.mycontentprovider.outcp/democp/child
D/###match: 匹配到路径为content://com.stone.mycontentprovider.outcp/democp/123任意数字的内容
D/###match: 匹配到路径为content://com.stone.mycontentprovider.outcp/democp/090任意数字的内容
I/###match: 匹配到路径为content://com.stone.mycontentprovider.outcp/democp/89ii任意字符的内容
I/###match: 匹配到路径为content://com.stone.mycontentprovider.outcp/democp/ab90任意字符的内容
E/###match: 执行删除数据库内容的方法

(二)Uri自带的解析方法:以添加为例:

调用添加方法:

  Uri othUri=resolver.insert(Uri.parse("content://com.stone.mycontentprovider.outcp/anypath?name="+MyUtils.getChineseName()
        +"&age="+MyUtils.getNum(10, 60)
        +"&gender="+ MyUtils.getGender()),new ContentValues());

注意其中的anypath可以使任意的字符串,也可以不要。
MyContentProvider中的添加方法:

  @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");

        Long itemId = 0l;
        if (values.size()>0){
            try {
                itemId = sqLiteDatabase.insertOrThrow("demo_table", null, values);
            } catch (Exception e) {
                Log.e("###", e.getLocalizedMessage());
            }
        }else {
            String authory=uri.getAuthority();
            Log.e("###authory",authory);
            String path=uri.getPath();
            Log.e("###path",path);
            String query=uri.getQuery();
            Log.e("###query",query);
            String name=uri.getQueryParameter("name");
            Log.e("###name",name);
            String age=uri.getQueryParameter("age");
            Log.e("###age",age);
            String gender=uri.getQueryParameter("gender");
            Log.e("###gender",gender);
            values.put("name", name);
            values.put("age",age);
            values.put("gender",gender);
            itemId = sqLiteDatabase.insertOrThrow("demo_table", null, values);
        }

        Log.e("###", "调用了out中的insert方法");
        //表名,非空列,值,对于sqlite数据库,添加完成之后会返回一个Long的id
        //uri这个uri就是另外的程序调用contentprovider的时候传过来的uri,如果传回去那么跟调用程序的uri没有区别也没有意义
        // 所以用ContentUris把uri和itemid整合一下,成为一个新的Uri,返回回去。
        return ContentUris.withAppendedId(uri, itemId);
    }

运行结果:

2020-07-23 20:07:33.190 15996-16123/com.stone.mycontentprovider E/###: 调用了out中的insert方法
2020-07-23 20:07:33.246 15996-16123/com.stone.mycontentprovider E/###authory: com.stone.mycontentprovider.outcp
2020-07-23 20:07:33.246 15996-16123/com.stone.mycontentprovider E/###path: /anypath
2020-07-23 20:07:33.247 15996-16123/com.stone.mycontentprovider E/###query: name=咸桂&age=45&gender=男
2020-07-23 20:07:33.247 15996-16123/com.stone.mycontentprovider E/###name: 咸桂
2020-07-23 20:07:33.248 15996-16123/com.stone.mycontentprovider E/###age: 45
2020-07-23 20:07:33.248 15996-16123/com.stone.mycontentprovider E/###gender: 男
2020-07-23 20:07:33.256 15996-16123/com.stone.mycontentprovider E/###: 调用了out中的insert方法

两种添加方法都执行了,所以打印了两边“” 调用了out中的insert方法“”。

ok关于解析就简单记录这些东西。下面简单说一下调用系统ContentProvider的问题:

(一)读取短信息:基本分三个步骤:

(1)获取ContentResolver

 ContentResolver contentResolver = getContentResolver();

(2)查询短信息

 Uri smsUri = Uri.parse("content://sms/");
 Cursor smsCoursor = contentResolver.query(smsUri, null, null, null, null);

(3)遍历smsCoursor ,得到每一条的数据

//遍历所有的行
   while (smsCoursor.moveToNext()) {
          //参数是列索引,遍历所有的列
          for (int i = 0; i < smsCoursor.getColumnCount(); i++) {
             Log.e("###", smsCoursor.getString(i));//此时获取到的是每一条的所有内容
            String address=cursor.getString(cursor.getColumnIndex("address"));//地址
            String body=cursor.getString(cursor.getColumnIndex("body"));//短信内容
            String dateString=cursor.getString(cursor.getColumnIndex("date"));//日期
          }
      }

(4)注意点:

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

(5)权限不要忘了动态申请,manifest也要注册。

 <uses-permission android:name="android.permission.READ_SMS"/>

(6)还有我的红米手机获取不到短信息,查了资料说是没有权限,现在还没有解决,希望各位看官能给个指导意见和方法。华为的可以。

(二)读取联系人:通讯录中的姓名和其他内容是由不同的contentprovider提供的。也就是说姓名和其他内容是不同的表,姓名是主表,其他内容是从表,主表中的主键会在从表中作为外键使用

(1)权限:动态申请

 <uses-permission android:name="android.permission.READ_CONTACTS"/>

(2)读取数据

 ContentResolver resolver;
 resolver = getContentResolver();
//先读取联系人信息
 Cursor cCursor = resolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
        while (cCursor.moveToNext()) {
//            ContactsContract.Contacts.DISPLAY_NAME//姓名
//            ContactsContract.Contacts._ID//id
//根据读取到的联系人信息遍历Cursor获取每一个联系人的姓名和id
            String name = cCursor.getString(cCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
            String mId = cCursor.getString(cCursor.getColumnIndex(ContactsContract.Contacts._ID));
            Log.e("###name", name + " ");
            Log.e("###mId", mId + " ");
//根据id获取对应的联系人的其他信息
            String selections = ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=?";//查询条件
            Cursor phoneCursor = resolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, selections, new String[]{mId}, null);
//变美丽其他信息
            while (phoneCursor.moveToNext()) {
//获取电话号码
                String phoneNum = phoneCursor.getString(phoneCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                name += "  " + phoneNum;
            }
//这个时候的log打印的就是姓名和联系电话
            Log.e("###name", name + " ");

        }

(三)添加联系人:上面我们知道了联系人姓名和电话是在两个表中,所以写入联系人也需要添加两次:

(1)权限:WRITE_CONTACTS,动态的你懂的

 <uses-permission android:name="android.permission.WRITE_CONTACTS"/>

(2)写入联系人

 //1.往一个contentprovider中插入一条空数据,获取新生成的id
        ContentValues values = new ContentValues();
        Uri uri = resolver.insert(ContactsContract.RawContacts.CONTENT_URI, values);
        long id = ContentUris.parseId(uri);//获取到刚刚生成的id
   //2.利用刚刚获取到的id分别组合姓名和电话号码往另外一个contentprovider中插入数据
        //插入姓名
        values.put(ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME,MyUtils.getChineseName());//插入的姓名
        values.put(ContactsContract.Data.RAW_CONTACT_ID,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.Data.RAW_CONTACT_ID,id);//指定和电话关联的id
        values.put(ContactsContract.CommonDataKinds.Phone.NUMBER,"13788883333");//插入的电话
        values.put(ContactsContract.CommonDataKinds.Phone.TYPE,ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);//插入的电话的类型
        values.put(ContactsContract.Data.MIMETYPE,ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE);//设置该行数据的类型
        resolver.insert(ContactsContract.Data.CONTENT_URI,values);//插入操作

ok,到此关于ContentProvider基础操作已经差不多了,其中还有其他的遗漏或者不足的地方,希望看官指正。后续还会不断的改善。祝大家工作愉快。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342