ContentProvider的简单解析

2019-08-20  本文已影响0人  取了个很好听的名字

1.定义

ContentProvider为四大组件之一

2.作用:

实现进程间的数据通信

3.实现原理

Android的binder机制
Binder解析

4.具体使用

在介绍ContentProvider的具体使用时,先介绍一些相关的知识

4.1URI

URI:统一资源标识符,用于唯一标识使用的ContentProvider和数据

外部进程通过URI找到对应的ContentProvider,并调用其中的方法来操作数据
URI分为系统预置URI和自定义URI,这里说明自定义URI

URI:主题://授权信息/表名/记录
主题(Sche):ContentProvider的URI的前缀。
授权信息(Authority):ContentProvider的唯一标识。
表名(Path):ContentProvider指向的数据库表名。
记录(Record):表名中的某一条记录。

  Uri parse = Uri.parse("content://com.zhqy.provider/user/1");
  //uri指向名为com.zhqy.provider的contentProvider中表名为user的id为1的记录
  //URI存在通配符*和#:
  //*:代表任何长度的有效字符串
 //content://com.zhqy.provider/#  匹配该contentProvider的所有内容
 //#:代表任意长度的有效数字字符串
 //content://com.zhqy.provider/user/#   匹配contentProvider中表名为user的所有数据

4.2MIME数据类型

定义:指定文件后缀名以那种方式打开
text/html:指定.html为后缀的文件已text应用程序打开
MIME类型组成
MiME分为父类型和子类型
text/html text为父类型 html为子类型
MIME类型形式
MIME有两种形式:

// 形式1:单条记录  
vnd.android.cursor.item/自定义
// 形式2:多条记录(集合)
vnd.android.cursor.dir/自定义 

// 注:
  // 1. vnd:表示父类型和子类型具有非标准的、特定的形式。
  // 2. 父类型已固定好(即不能更改),只能区别是单条还是多条记录
  // 3. 子类型可自定义

具体实例:
如果URI为content://com.zhqy.provider/user/1
则mime类型可以为:vnd.android.cursor.item/vnd.provider.user
如果URI为content://com.zhqy.provider/user
则mime类型可以为:vnd.android.cursor.dir/vnd.provider.user
获取MIME类型
可以通过ContentProvider.getType(uri)来获取uri对应的MIME类型。

4.3ContentProvider

数据组织方式
ContentProvider的数据组织方式以表格为主,即一个表格可以有多个表,表中可以有多行和多列,行成为记录,列成为字段。

数据库就是表格形式的数据集
当然ContentProvider的数据源也可以是其他形式的数据,如文件,网络数据等

主要方法
一旦继承了ContentProvider就需要实现如下六个方法:

//外部进程查询contentProvider中的数据
 @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        return null;
    }

//外部进程向contentProvider中添加数据
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        return null;
    }
//外部进程向contentProvider中删除数据
    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
//外部进程向contentProvider中更新数据
    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        return 0;
    }
   //获取URI对应的mime类型
    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        return null;
    }
   //当contentProvider创建或外部进程第一次调用contentProvider时会调用该方法,可以在该方法中初始化数据源
   // 注:运行在ContentProvider进程的主线程,故不能做耗时操作 
    @Override
    public boolean onCreate() {
        return false;
    }

如果是自定义ContentProvider则必须重写上述六个方法

4.4 辅助工具类

ContentResolver
统一管理不同ContentProver的操作
ContentResolver的主要方法:

//查询数据
Cursor query(Uri uri,String[] projection, String selection,String[] selectionArgs,String sortOrder)
//添加数据
insert( Uri url, ContentValues values)
//修改数据
update(Uri uri,ContentValues values, String where,String[] selectionArgs) 
//删除数据
delete(Uri url,  String where,String[] selectionArgs)

具体使用:

        //获取contenResolver
        ContentResolver contentResolver = getContentResolver();
       //解析URI
        Uri uri = Uri.parse("content://com.zhqy.provider/user/1");
        //使用contentResolver对uri对应的contentProvider中的数据进行操作
        contentResolver.query(uri,null,null,null,null);

ContenUris
对URI进行操作
有两个核心方法:withAppendId和parseId
withAppendId(Uri uri,long id):向uri中追加一个id
parseId(Uri uri):解析uri中的id值
使用方法如下:

    //解析URI
        Uri uri = Uri.parse("content://com.zhqy.provider/user");
        //向uri中增加id,此时uri为content://com.zhqy.provider/user/1
        ContentUris.withAppendedId(uri,1);
        //解析uri中的id,获取的id值为1
        long id = ContentUris.parseId(uri);

UriMatcher
在ContentProvider 中注册URI
根据 URI 匹配 ContentProvider 中对应的数据表
具体使用:

  //权限
    private static final String AUTHPORITY="com.zhqy.provider";
    //表名
    private static final String TABLE_USER="user";
    private static final String TABLE_JOB="job";
    //注册码
    private static final int CODE_USER=1;
    private static final int CODE_JOB=2;
    private static UriMatcher uriMatcher;
    static {
        // 即初始化时不匹配任何东西
        uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
        //注册uri
        uriMatcher.addURI(AUTHPORITY,TABLE_USER,CODE_USER);
        uriMatcher.addURI(AUTHPORITY,TABLE_JOB,CODE_JOB);
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        //根据uri对应的注册码返回MIME类型
        switch (uriMatcher.match(uri)){
            case CODE_USER:
                return  "vnd.android.dir/vnd.provider.user";
            case CODE_JOB:
                return "vnd.android.dir/vnd.provider.job";

        }
        return  null;
    }

ContentObserver
内容观察者,当ContentProvider发生改变时(增删改)通知观察者ContentProvider的数据发生改变
具体使用:

public class MyContentObserver extends ContentObserver {

    /**
     * Creates a content observer.
     *
     * @param handler The handler to run {@link #onChange} on, or null if none.
     */
    public MyContentObserver(Handler handler) {
        super(handler);
    }
    //当ContentProvider数据发生改变时会触发该方法
    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);
    }
}



// 步骤1:注册内容观察者ContentObserver
    getContentResolver().registerContentObserver(uri);
    // 通过ContentResolver类进行注册,并指定需要观察的URI

// 步骤2:当该URI的ContentProvider数据发生变化时,通知外界(即访问该ContentProvider数据的访问者)
    public class UserContentProvider extends ContentProvider { 
      public Uri insert(Uri uri, ContentValues values) { 
      db.insert("user", "userid", values); 
      getContext().getContentResolver().notifyChange(uri, null); 
      // 通知访问者
   } 
}

// 步骤3:解除观察者
 getContentResolver().unregisterContentObserver(uri);
    // 同样需要通过ContentResolver类进行解除

5.ContentProvider的实际使用

ContentProvider的使用有以下两种情况:1.同一进程下的使用ContentProvider操作数据2.不同进程间使用ContentProvider操作数据.
同一进程(数据源为数据库)
1.创建数据库操作类

package com.zhqy.contentproviderdemo;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

/**
 * Created by jackal on 2019/8/20.
 */

public class MySqlliteOpenHelper extends SQLiteOpenHelper {

    private  static final String TABLE_USER="user";
    private  static final String TABALE_JOB="job";
    private static final String DATABASE_NAME="databse";
    private static int VERSION=1;

    public MySqlliteOpenHelper(Context context) {
        super(context, DATABASE_NAME, null, VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        //创建表
         db.execSQL("CREATE TABLE IF NOT EXISTS "+TABLE_USER+"(id INTEGER PRIMARY KEY AUTOINCREMENT ,name text)");
         db.execSQL("CREATE TABLE IF NOT EXISTS "+TABALE_JOB+"(id INTEGER PRIMARY KEY AUTOINCREMENT ,jobname text)");
    }

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

    }
}

2.自定义ContentProvider

package com.zhqy.contentproviderdemo;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;

/**
 * Created by jackal on 2019/8/20.
 */

public class MyContentProvider extends ContentProvider {
    //权限
    private static final String AUTHPORITY = "com.zhqy.provider";
    //表名
    private static final String TABLE_USER = "user";
    private static final String TABLE_JOB = "job";
    //注册码
    private static final int CODE_USER = 1;
    private static final int CODE_JOB = 2;
    private static UriMatcher uriMatcher;
    private SQLiteDatabase database;

    static {
        // 即初始化时不匹配任何东西
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        //注册uri
        uriMatcher.addURI(AUTHPORITY, TABLE_USER, CODE_USER);
        uriMatcher.addURI(AUTHPORITY, TABLE_JOB, CODE_JOB);
    }


    @Override
    public boolean onCreate() {

        Context context = getContext();
        //创建数据库操作类
        MySqlliteOpenHelper mySqlliteOpenHelper = new MySqlliteOpenHelper(context);
        database = mySqlliteOpenHelper.getWritableDatabase();
        //添加记录
        ContentValues values1 = new ContentValues();
        values1.put("name", "小鸡");
        database.insert(TABLE_USER, null, values1);

        ContentValues values2 = new ContentValues();
        values2.put("name", "小鸭");
        database.insert(TABLE_USER, null, values2);


        ContentValues values3 = new ContentValues();
        values3.put("jobname", "码农");
        database.insert(TABLE_JOB, null, values3);


        ContentValues values4 = new ContentValues();
        values4.put("jobname", "菜农");
        database.insert(TABLE_JOB, null, values4);


        return true;
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        //根据uri对应的注册码返回MIME类型
        switch (uriMatcher.match(uri)) {
            case CODE_USER:
                return "vnd.android.dir/vnd.provider.user";
            case CODE_JOB:
                return "vnd.android.dir/vnd.provider.job";

        }
        return null;
    }

    //查询数据
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {

        String tableName = getTableName(uri);

        return database.query(tableName, projection, selection, selectionArgs, null, null, sortOrder);
    }

    //添加数据
    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        String tableName = getTableName(uri);
        database.insert(tableName, null, values);
        return uri;
    }
    //删除数据
    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
        String tableName = getTableName(uri);
        return database.delete(tableName, selection, selectionArgs);

    }
    //更新数据
    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
        String tableName = getTableName(uri);
        return database.update(tableName,values,selection,selectionArgs);

    }

    //获取表名
    public String getTableName(Uri uri) {
        String tableName = null;
        switch (uriMatcher.match(uri)) {
            case CODE_USER:
                tableName = "user";
                break;
            case CODE_JOB:
                tableName = "job";
                break;
        }

        return tableName;
    }


}

3.在Manifest.xml注册该ContentProvider

<provider
            android:authorities="com.zhqy.provider"
            android:name=".MyContentProvider"></provider>

4.访问ContentProvider中的数据

package com.zhqy.contentproviderdemo;

import android.content.ContentResolver;
import android.content.ContentUris;
import android.database.Cursor;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
       //解析URI
        Uri uri = Uri.parse("content://com.zhqy.provider/user");
       //获取contentResolver
        ContentResolver contentResolver = getContentResolver();
        //查询表中数据
        Cursor query = contentResolver.query(uri, null, null, null, null);
        while (query.moveToNext()){
            Log.e("user",query.getInt(0)+","+query.getString(1));
        }


    }
}

测试结果:

08-20 17:19:36.267 21006-21006/com.zhqy.contentproviderdemo E/user: 1,小鸡
08-20 17:19:36.267 21006-21006/com.zhqy.contentproviderdemo E/user: 2,小鸭

进程间访问ContentProvider(数据源为数据库)
1.创建数据库操作类(同上)
2.自定义ContentProvider(同上)
3.将contentProvider注册到Manifest.xml上

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.zhqy.contentproviderdemo">
    <permission android:name="com.zhqy.PROVIDER" android:protectionLevel="normal"></permission>
    <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">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <provider
            android:authorities="com.zhqy.provider"
            android:name=".MyContentProvider"
            android:exported="true"
            android:permission="com.zhqy.PROVIDER"
            ></provider>
    </application>

</manifest>

其中
android:exported="true" 允许其他进程访问
android:permission="com.zhqy.PROVIDER" 访问该provider所需要的权限
android:authorities="com.zhqy.provider" 授权信息
<permission android:name="com.zhqy.PROVIDER" android:protectionLevel="normal"></permission> 声明权限

操作进程:
1.使用contenProvider所需要的权限
<uses-permission android:name="com.zhqy.PROVIDER"></uses-permission>

2.访问ContentProvider的类

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //解析URI
        Uri uri = Uri.parse("content://com.zhqy.provider/user");
        ContentResolver contentResolver = getContentResolver();
        //通过contenResolver操作contentProvider进行数据查询
        Cursor query = contentResolver.query(uri, null, null, null,null);
        while (query.moveToNext()){
            Log.e("databse",query.getInt(0)+","+query.getString(1));
        }
    }
}

测试结果

08-20 17:35:38.958 22250-22250/com.zhqy.contentproviderclientdemo E/databse: 1,小鸡
08-20 17:35:38.958 22250-22250/com.zhqy.contentproviderclientdemo E/databse: 2,小鸭

优点

6.1 安全

ContentProvider为应用间的数据交互提供了一个安全的环境:允许把自己的应用数据根据需求开放给 其他应用 进行 增、删、改、查,而不用担心因为直接开放数据库权限而带来的安全问题

6.2 访问简单 & 高效
对比于其他对外共享数据的方式,数据访问方式会因数据存储的方式而不同:

采用 文件方式 对外共享数据,需要进行文件操作读写数据;
采用 Sharedpreferences 共享数据,需要使用 sharedpreferences API读写数据

这使得访问数据变得复杂 & 难度大。

而采用ContentProvider方式,其 解耦了 底层数据的存储方式,使得无论底层数据存储采用何种方式,外界对数据的访问方式都是统一的,这使得访问简单 & 高效

如一开始数据存储方式 采用 SQLite 数据库,后来把数据库换成 MongoDB,也不会对上层数据ContentProvider使用代码产生影响

示意图.png
上一篇 下一篇

猜你喜欢

热点阅读