Android ContentProvider详解

前言

ContentProvide是四大组件之一,说明他是很重要的,虽然项目中不经常使用,但是还是需要学一下的

什么是ContentProvider

ContentProvider为不同的应用之间实现数据共享,提供统一的接口,也就是说ContentProvider可以实现进程间的数据共享,实现跨进程通信

为什么用ContentProvider

  • ContentProvider提供了对底层数据存储方式的抽象,比如下图,底层使用了Sqlit数据库,在用ContentProvider进行封装后,把sqlit换成其他数据库也不会影响工功能
    Android ContentProvider详解_第1张图片
  • ContentProvider为应用间的数据交互提供了一个安全的环境

怎么使用ContentProvider

首先我们需要知道三个类

  • ContentProvider(内容提供者)
  • ContentResolver(内容解析者)
  • ContentObserver(内容观察者)

假如我们现在有个应用A 提供了数据 ,应用B要操作应用A的数据,那么

  • 应用A使用ContentProvider去共享自己数据
  • 应用B使用ContentResolver去操作应用A的数据,通过ContentObserver去监听应用A的数据变化
  • 当应用A的数据发送改变时,通知ContentObserver去告诉应用B数据变化了及时更新

这就是通信的大致流程,在了解更加详细的流程之前,我们还需要知道几个概念

ContentProvider中的URI

ContentProvider中的URI是有固定格式的,例如:
Android ContentProvider详解_第2张图片
Authority:授权信息,以区别不同的ContentProvider
path:表名,以区分ContentProvider中不同的数据表
id:id号,用于区分表中的不同数据

  • getAuthority():获取Uri中Authority部分
  • getPath():获取Uri中path部分

UriMatch

UriMatch主要为了区配URI,比如应用A提供了数据,但是并不是说有的应用都可以操作这些数据,只有提供了满足应用A的URI,才能访问A的数据,而UriMatch就是做这个区配工作的,他有如下方法

  • public UriMatcher(int code) :它的作用就是创建一个UriMatch对象
  • public void addURI(String authority,String path, int code):它的作用是在ContentProvider添加一个用于匹配的Uri,当匹配成功时返回code。Uri可以是精确的字符串,Uri中带有*表示可匹配任意text,#表示只能匹配数字。
  • public int match(Uri uri) :这里的Uri就是传过来的要进行验证,匹配的Uri假如传过来的是:content://com.example.test/student/#,content://com.example.test/student/10可以匹配成功,这里的10可以使任意的数字。

实例

首先我们先完成应用A的工作,首先创建一个MyContentProvider继承ContentProvider

public class MyContentProvider extends ContentProvider {

    //这里的AUTHORITY就是我们在AndroidManifest.xml中配置的authorities,这里的authorities可以随便写
    private static final String AUTHORITY = "com.example.student";
    //匹配成功后的匹配码
    private static final int MATCH_ALL_CODE = 1;
    private static final int MATCH_ONE_CODE = 2;

    private static final Uri NOTIFY_URI = Uri.parse("content://" + AUTHORITY + "/student");


    private static final UriMatcher uriMatcher;

    //在静态代码块中添加要匹配的 Uri
    static {
        //匹配不成功返回NO_MATCH(-1)
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        /**
         * uriMatcher.addURI(authority, path, code); 其中
         * authority:主机名(用于唯一标示一个ContentProvider,这个需要和清单文件中的authorities属性相同)
         * path:路径路径(可以用来表示我们要操作的数据,路径的构建应根据业务而定)
         * code:返回值(用于匹配uri的时候,作为匹配成功的返回值)
         */
        uriMatcher.addURI(AUTHORITY, "student", MATCH_ALL_CODE);// 匹配记录集合
        uriMatcher.addURI(AUTHORITY, "student/#", MATCH_ONE_CODE);// 匹配单条记录
    }

    @Override
    public boolean onCreate() {
        return false;
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        switch (uriMatcher.match(uri)) {
            /**
             * 这里如果匹配是uriMatcher.addURI(AUTHORITY, "student",
             * MATCH_SUCCESS_CODE);中的Uri,则查询全部
             *
             */
            case MATCH_ALL_CODE:
                Log.d("mmm", "queryAll");
                break;
            /**
             * 这里如果匹配是uriMatcher.addURI(AUTHORITY,
             * "student/#",MATCH_ONE_CODE);中的Uri,查询一个
             */
            case MATCH_ONE_CODE:

                break;
            default:
        }
        return null;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }

    /**
     * 插入 使用UriMatch的实例中的match方法对传过来的 Uri进行匹配。 这里通过ContentResolver传过来一个Uri,
     * 用这个传过来的Uri跟在ContentProvider中静态代码块中uriMatcher.addURI加入的Uri进行匹配
     * 根据匹配的是否成功会返回相应的值,在上述静态代码块中调用uriMatcher.addURI(AUTHORITY,
     * "student",MATCH_CODE)这里的MATCH_CODE
     * 就是匹配成功的返回值,也就是说假如返回了MATCH_CODE就表示这个Uri匹配成功了
     * ,我们就可以按照我们的需求就行操作了,这里uriMatcher.addURI(AUTHORITY,
     * "person/data",MATCH_CODE)加入的Uri为:
     * content://com.example.studentProvider/student
     * ,如果传过来的Uri跟这个Uri能够匹配成功,就会按照我们设定的步骤去执行相应的操作
     */
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        switch (uriMatcher.match(uri)) {
            case MATCH_ALL_CODE:
                String aaa = (String) values.get("aaa");
                Log.d("mmm", "insertAll" + aaa);
                //通知ContentObserver数据发生变化了
                notifyDataChanged();
                break;
            case MATCH_ONE_CODE:
                break;
            default:
        }
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        switch (uriMatcher.match(uri)) {
            case MATCH_ALL_CODE:
                Log.d("mmm", "deleteAll");
                break;
            case MATCH_ONE_CODE:
                break;
            default:
        }
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        switch (uriMatcher.match(uri)) {
            case MATCH_ALL_CODE:
                Log.d("mmm", "updateAll");
                break;
            case MATCH_ONE_CODE:
                break;
            default:
        }
        return 0;
    }

    //通知指定URI数据已改变
    private void notifyDataChanged() {
        getContext().getContentResolver().notifyChange(NOTIFY_URI, null);
    }
}

因为ContentProvider作为四大组件之一,所以还需要在AndroidManifest.xml中注册一下。

 

这里的authorities就是它是唯一标识内容提供者的,为内容提供者指定一个唯一的标识,这样别的应用才可以唯一获取此provider,exported的值为[flase|true]当为true时:当前提供者可以被其它应用使用。

现在应用A已经完成了,下面我们看下应用B怎么写

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    //这里的AUTHORITY就是我们在AndroidManifest.xml中配置的authorities,这里的authorities可以随便写
    private static final String AUTHORITY = "com.example.student";

    private static final Uri URI = Uri.parse("content://" + AUTHORITY + "/student");
    private ContentResolver contentResolver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        initData();
    }

    private void initData() {
        contentResolver = getContentResolver();
        //注册内容观察者
        contentResolver.registerContentObserver(URI, true, new MyContentObserver(null));
    }

    private void initView() {
        findViewById(R.id.zeng).setOnClickListener(this);
        findViewById(R.id.shan).setOnClickListener(this);
        findViewById(R.id.gai).setOnClickListener(this);
        findViewById(R.id.cha).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        ContentValues contentValues = new ContentValues();
        contentValues.put("aaa", "aaaa");
        //通过内容解析者操作内容观察者
        switch (v.getId()) {
            case R.id.zeng:
                contentResolver.insert(URI, contentValues);
                break;
            case R.id.shan:
                contentResolver.delete(URI, null, null);
                break;
            case R.id.gai:
                contentResolver.update(URI, contentValues, null, null);
                break;
            case R.id.cha:
                contentResolver.query(URI, null, null, null, null);
                break;
        }

    }


    //定义内容观察者
    class MyContentObserver extends ContentObserver {


        public MyContentObserver(Handler handler) {
            super(handler);
        }

        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);
            Log.d("mmm", "改变了");
        }
    }
}

然后操作增删改查,看下log

01-18 18:30:44.550 1987-2001/? D/mmm: insertAllaaaa
01-18 18:30:44.552 1955-1975/com.baidu.bpit.aibaidu.myapplication D/mmm: 改变了
01-18 18:30:46.194 1987-2001/com.baidu.bpit.aibaidu.service D/mmm: deleteAll
01-18 18:30:48.016 1987-2001/com.baidu.bpit.aibaidu.service D/mmm: updateAll
01-18 18:30:48.978 1987-2001/com.baidu.bpit.aibaidu.service D/mmm: queryAll

可以看到都通知到了应用A中,因为应用A的insert方法,通知了ContentObserver,所以应用B可以收到数据改变的通知

参考 https://www.jianshu.com/p/f5ec75a9cfea
https://blog.csdn.net/dmk877/article/details/50387741

你可能感兴趣的:(android)