Android的Content Provider知识点总结

前言

  我努力的写完前半段后,正准备开始写后半段,发现了一个痛苦的事实,如果不懂SQLite的内容的话,contentProvider就太过于抽象了,拿不出实例。所以最好是先看过SQLite的内容之后,再来学习有关Content Provider的知识。有关SQLite的些许知识可以看这里

1.定义

Android系统用于让不同的应用程序之间共享数据的接口,就是Content Provider(内容提供者)。

  • 如果一个应用程序有设置这样的外部访问接口,那么任何其他的应用都可以访问到这一部分的内容。比如说Android系统自带的联系人,相册,短信之类,都会带有这样的接口。

2.访问其他应用的程序(ContentResolver)

如果想要访问其他应用的数据,需要使用ContentResolver。

  1. 在当前的Context环境中获取ContentResolver就可以获得其实例。这个是ContextWrapper类中的方法,任何继承ContextWrapper类的类都会自带这个方法(比如Activity)。

    public class MainActivity extends AppCompatActivity {
    
        ContentResolver contentResolver;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            //获取contentResolver的实例
            contentResolver = this.getContentResolver();
        }
    }
    
  2. 对于ContentResolver而言,它能够对访问到的数据进行CRUD操作(Create, Retrieve, Update, Delete,也就是我们常说的增删改查)。这类似对数据库进行操作,但是还是有一些不同的地方。

  3. ContentResolver不同于数据库,它是要访问其他应用的数据,不能单纯的获取数据库名和表名,所以ContentResolver是通过URI来代替从应用名到数据库到表的路径检索。
    uri有两个参数,authority和path,authority对应应用程序,一般是应用程序的包名(比如QQ的包名就是com.tencent.mobileqq)加上’.provider’;path用来区分应用程序内部不同的表名。具体写法:authority/path。

    Uri uri = Uri.parse("content://com.example.contentproviderdemo.provider/table1");
    //如果你想特别希望访问这个表内的第X行时,可以这么写,X是这个表内的id号,一个数字。
    Uri uri = Uri.parse("content://com.example.contentproviderdemo.provider/table1/X");
    
  4. 接下来就可以获取数据了,获取数据需要用到query方法,这个方法类似SQLite的query方法,查询完成之后返回的是一个Cursor对象。

      Cursor cursor = contentResolver.query(
                    uri,
                    projection,
                    selection,
                    selectionArgs,
                    sortOrder);
    
    参数对应SQL部分备注
    urifrom table_1查询某个应用中的某个表
    projectionselect column1, column2查询指定的列名,如果为空就是选择所有列(相当于*)
    selectionwhere column = value约束条件,如果为空就是没有约束条件
    selectionArgs-为where中的占位符提供具体的值,为空就是无具体值,具体内容看8
    sortOrderorder by column1, column2指定结果的排列方式,为空就是默认排列

    SQL:
    select column1, column2 from table_1 where column = value order by column1, column2

  5. 当访问成功之后,就会获得一个Cursor对象,这个对象就包含了我们所查询到的数据。从中取出数据即可。

    if (cursor != null){ //确认是否查询成功
    
        while(cursor.moveToNext()){ //遍历整个查询结果
            
            String column1 = cursor.getString(cursor.getColumnIndex("column1"));  //获取查询结果内的数据
            int column2 = cursor.getInt(cursor.getColumnIndex("column2"));
    
        }
    }
    
  6. 用ContentResolver也可以添加新的数据到其他应用的表内,需要使用到insert方法。

    ContentValues values = new ContentValues();
    values.put("column1", "String");
    values.put("column2", 1);
    contentResolver.insert(uri, values);
    

    SQL:
    insert into table_1 values (‘String’, 1)

  7. 当然也可以修改内容,使用update()方法。

    ContentValues values1 = new ContentValues();
    values1.put("column1", "text");
    values1.put("column2", 2);
    contentResolver.update(uri, values, "column1 = ? and column2 = ?", new String[]{"String", "1"});
    
    //下面是update方法的具体参数
    contentResolver.update(
            uri,
            contentValues,
            where,
            selectionArgs
    );
    

    update方法的几个参数貌似不太好理解,前两个参数还是和之前一样,是表地址和添加值。第三个参数是where的说明语句,但是其中有两个‘?’,这个’?'就是占位符,而第四个参数就是对这两个占位符提供具体值。这样可能还是不太清楚,那么我们看看SQL语句是怎么写的就明白了。
    SQL:
    update table_1 set column1 = ‘text’, column2 = 2 where column1 = ‘String’ and column2 = 1
    顺带一提,query的selectionArgs参数也是和这边一样,为selection中的‘?’提供具体值。

  8. 最后是删除语句,用delete方法实现。

    contentResolver.delete(uri, "column1 = ? and column2 = ?", new String[]{"text", "2"});
    
    //下面是delete方法具体参数
    contentResolver.delete(
            uri,
            where,
            selectionArgs
    );
    

    SQL:
    delete from table_1 where column1 = ‘text’ and column2 = 2

3.让其他程序访问数据(ContentProvider)

接下来,如果想要让其他程序访问本程序的数据,就需要ContentProvider,这里我会拿一个简单的带有两个表的小项目来实际操作。地址:https://github.com/wodongx123/SQLiteDemo
这个项目就是单纯的内部一个数据库BookStore.db,其中带有两个表Book和People,没有任何其他功能。

  1. 想要让其他程序访问的话,要自建一个继承ContentProvider抽象类的类,然后为了消除错误添加其中必加的方法。

    public class MyContentProvider extends ContentProvider {
        
        @Override
        public boolean onCreate() {
            return false;
        }
    
        @Nullable
        @Override
        public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
            return null;
        }
    
        @Nullable
        @Override
        public String getType(@NonNull Uri uri) {
            return null;
        }
    
        @Nullable
        @Override
        public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
            return null;
        }
    
        @Override
        public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
            return 0;
        }
    
        @Override
        public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
            return 0;
        }
    }
    

      看上去很多,其实现阶段还没有内容,而且其中的增删改查四个方法还似曾相识,唯一不同的就是多了一个onCreate()和getType()。
      onCreate() 初始化ContentProvider的时候调用,一般在这里初始化我们要用的数据库,如果返回true就表示初始化成功,返回false就是初始化失败了。
      query(),insert(),delete(),update()几个参数发现和ContentResolver的方法参数一样,这个事实上就是从其他程序ContentResolver原原本本传过来的值,从其他程序传过来的值我们这边经过处理后,再把得到的数据返回回去,这样跨应用程序的数据交换就完成了。
      getType() 这个方法是对其他方法中传入的Uri的参数返回对应的MIME类型。

  2. 每个Provider都要在AndroidManifest.xml中配置了才能生效,如果你是new -> other -> Content Provider创建的类的话,Android Studio会自动帮你创建配置,如果是像我一样new class创建的话,就要手动配置了。

    <application
    
    	……………………
    
    
         <provider
             android:authorities="com.example.sqlitedemo.provider"
             android:name=".MyContentProvider"
             android:exported="true"
             android:enabled="true"/>
    
    	……………………
    
     </application>
    

    authorities就是之前说过的应用地址,一般是包名加上’.provider’。
    exported = true表示可以被其他应用发现。
    enabled = true表示可以使用。

  3. 在onCreate()中初始化我们的数据库,我这边就初始化BookStore.db。

    public class MyContentProvider extends ContentProvider {
        
        private MyDataBaseHelper myDataBaseHelper;
        private SQLiteDatabase sqLiteDatabase;
    
        @Override
        public boolean onCreate() {
            myDataBaseHelper = new MyDataBaseHelper(getContext(), "BookStore.db", null, 2);
            sqLiteDatabase = myDataBaseHelper.getWritableDatabase();
            return true;
        }
    }
    
  4. 接下来对Uri进行解析,先再看看Uri的写法:
    content://com.example.contentproviderdemo.provider/table1
    content://com.example.contentproviderdemo.provider/table1/X
    那么,前面的authority部分不会变动,因为已经限定是这个应用了,唯一变动的就是后面的path已经指定的行号。
    这个时候就需要使用UriMatcher这个类,它会自动的根据不同的Uri来匹配不同的值。

    public class MyContentProvider extends ContentProvider {
        public static final int TABLE_BOOK = 0;  //使用静态变量来代替两个表和表内数据
        public static final int ITEM_BOOK = 1;
        public static final int TABLE_PEOPLE = 2;
        public static final int ITEM_PEOPLE = 3;
        private static UriMatcher uriMatcher; //声明一个uriMathcher
    
        static {
            uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
            //addURI有三个参数,authority和path已经对应的值
            uriMatcher.addURI("com.example.sqlitedemo.provider", "book", TABLE_BOOK);
            uriMatcher.addURI("com.example.sqlitedemo.provider", "book/#", ITEM_BOOK);
            uriMatcher.addURI("com.example.sqlitedemo.provider", "people", TABLE_PEOPLE);
            uriMatcher.addURI("com.example.sqlitedemo.provider", "people/#", ITEM_PEOPLE);
        }
    
    	……………………
    
    }
    

    Uri中有一个叫做通配符的东西,简单来说是这样。
    #:匹配任意长度的数字。
    *:匹配任意长度的任意字符。
    content://com.example.contentproviderdemo.provider/table1/#表示table1内的任意一条数据。

  5. 接下来实现具体的增删改查方法,根据urimatcher匹配出不同的内容进行不同的操作即可,这里就以query和insert为例,代码看上去很多,其实非常简单。

    public class MyContentProvider extends ContentProvider {
    
        private MyDataBaseHelper myDataBaseHelper;
        private SQLiteDatabase sqLiteDatabase;
    
        public static final int TABLE_BOOK = 0;  //使用静态变量来代替两个表和表内数据
        public static final int ITEM_BOOK = 1;
        public static final int TABLE_PEOPLE = 2;
        public static final int ITEM_PEOPLE = 3;
        private static UriMatcher uriMatcher; //声明一个uriMathcher
    
    
    	……………………
    	
        @Nullable
        @Override
        public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
            //虽然加密过了,但是参数就是Resolver的那几个参数
            switch (uriMatcher.match(uri)){
                case TABLE_BOOK:
                case ITEM_BOOK: //为了方便就把查询某一条的数据合并了
                    return sqLiteDatabase.query("book", strings, s, strings1, null, null, s1);
                case TABLE_PEOPLE:
                case ITEM_PEOPLE:
                    return sqLiteDatabase.query("People", strings, s, strings1, null, null, s1);
            }
            return null;
        }
    
    
        @Nullable
        @Override
        public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
            //返回的是一个uri,通过这个uri告诉对方插入数据是否成功,返回为null就是失败了
            Uri uriReturn = null;
            switch (uriMatcher.match(uri)){
                case TABLE_BOOK:
                    long bookId = sqLiteDatabase.insert("book", null, contentValues);
                    uriReturn = Uri.parse("content://com.example.sqlitedemo.provider/book/" + bookId);
                    break;
                case TABLE_PEOPLE:
                    long peopleId = sqLiteDatabase.insert("People", null, contentValues);
                    uriReturn = Uri.parse("content://com.example.sqlitedemo.provider/book/" + peopleId);
                    break;
            }
            return uriReturn;
        }
    
    	……………………
        
    }
    
  6. 最后是关于getType()这个方法,会根据传入的URI值,返回对应的MIME,MIME的话Android对此有做规定,我们按照规定来写就好。

    • vnd开头。
    • 如果url以路径结尾,vnd接上android.cursor.dir/;如果以id结尾,接android.cursor.item/。
    • 最后加上vnd.authority.path。
        public String getType(@NonNull Uri uri) {
            switch (uriMatcher.match(uri)){
                case TABLE_BOOK:
                    return "vnd.android.cursor.dir/vnd.com.example.sqlitedemo.provider.book";
                case ITEM_BOOK:
                    return "vnd.android.cursor.item/vnd.com.example.sqlitedemo.provider.book";
                case TABLE_PEOPLE:
                    return "vnd.android.cursor.dir/vnd.com.example.sqlitedemo.provider.people";
                case ITEM_PEOPLE:
                    return "vnd.android.cursor.item/vnd.com.example.sqlitedemo.provider.people";
            }
            return null;
        }
    

    至于gettype这个方法到底有什么用,可以参考这篇,我一时间确实没看懂。
    ContentProvider数据库共享之——MIME类型与getType()
    https://blog.csdn.net/harvic880925/article/details/44620851

4.内容观察者(ContentObserver)

  ContentObserver是输出数据的一方(也就是ContentProvider的一方)用于监控数据变动所设置的监视机制。目的是观察特定Uri引起的数据库变化,进而做出相应的措施。

参考材料

第一行代码——Android(第2版)
p254 - p265

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值