【Android ContentProvider】ContentProvider

我们会经常发现app中有这样的功能:

是否允许读取系统联系人?这时你点击是,然后发现你手机通讯录里面存的联系人信息竟然被你新安装的app读取到了。

由于不同的app运行在不同的进程中,这不就是进程间的通信了吗? 没错ContentProvider是安卓提供的用于不同app(进程)之间进行共享数据。

除了系统联系人外、手机信息、日程信息、这些系统的app都提供有对外的ContentProvider。接下来就来总结下ContentProvider。

知识点

ContentReslover和ContentProvider

我们对ContentProvider也有所了解了,知道他可以对外暴露一些想要暴露的数据,供其他进程(app)来共享这些数据。然而ContentReslover又是啥鬼?其实ContentReslover也是安卓提供的一个类,我们要想获得ContentProvider提供的数据就要通过ContentReslover这个类来实现。

在这里插入图片描述
ContentProvider和数据库类似有一套增删除改查。ContentProvider吧一些数据暴露出来提供了增删改查操作,我们通过ContentReslover就可以对通讯录app这些暴露的数据进行增删改查。

ContentReslover

1、相关api
 //对象获取
 ContentResolver resolver = getContentResolver();
//增删改查

 //1 增
final Uri insert(Uri url, 
                 ContentValues values)//Inserts a row into a table at the given URL.
//2 删
final int delete(Uri url, 
                 String where, 
                 String[] selectionArgs) //Deletes row(s) specified by a content URI.
//3 改
final int update(Uri uri, 
                  ContentValues values, 
                  String where, String[] selectionArgs)//Update row(s) in a content URI.

// 4 查
final Cursor query(Uri uri, 
                   String[] projection,
                    Bundle queryArgs, 
                    CancellationSignal cancellationSignal)
                    
final Cursor query(Uri uri, 
                   String[] projection, 
                   String selection, 
                   String[] selectionArgs, 
                   String sortOrder, 
                   CancellationSignal cancellationSignal)
                   
final Cursor query(Uri uri, 
                   String[] projection, 
                   String selection, 
                   String[] selectionArgs, 
                   String sortOrder)
//Query the given URI, returning a Cursor over the result set.

可以看到上面的这些方法是不是似曾相识?没错SQLiteDataBase也是这样来完成curd的。只不过SQLiteDataBase接受的参数为表名,这里变成了URI对象。而且这里的uri为内容URI。

2、内容URI

内容uri给内容提供器中的数据建立了唯一标识。内容uri的写法:

content://authority/path
  • authority:用于对不同的应用程序做区分,一般为app的包名(applicationID)
  • path:一般用于对同一应用程序的不同表做区分。通常添加到authority后面。

其实authority就是ContentProvider注册时的authority字符串:

public class MyProvider extends ContentProvider {
    //authority strings from manifest file
    private static final String authority = "com.example.contentresloverdemo.provider";
    // 暴漏两张表
    public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + authority + "book");
    public static final Uri USER_CONTENT_URI = Uri.parse("content://" + authority + "user");
    ...
}
        <!-- you can also add custom permission for safe here,-->
        <provider
                android:process=":provider"
                android:exported="true"
                android:authorities="com.example.contentresloverdemo.provider"
                android:name=".provider.MyProvider">
        </provider>

比如我们想暴露两张表的数据,表分别为book、user这时,内容uri就可以写为:

content://com.example.contentresloverdemo.provider.book

content://com.example.contentresloverdemo.provider.user

3、为啥这里要使用uri

如果只使用表名,系统无法知道我们要访问哪个app中的表,而通过URI就能精确定位程序。uri对象获得方式:

 Uri uri1 = Uri.parse("content://com.example.administrator.messangerdemo/table1");
4、常用的query方法
final Cursor query(Uri uri, 
                   String[] projection,
                    String selection, 
                    String[] selectionArgs, 
                    String sortOrder)
  • uri:指定查询某应用某一表
  • projection:指定查询列名
  • selection:where 约束
  • sortOrder:查询结果的排序方式

跨进程读取数据栗子

我们通过ContentReslover来读取手机联系人。由于xml文件就一个TextView不在给出,直接上段简单的代码:

public class MainActivity extends AppCompatActivity {

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

    /**
     * 权限检测
     */
    @TargetApi(Build.VERSION_CODES.O)
    private void checkPermision() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
        } else {
            readContacts();
        }
    }

    /**
     * 读取联系人
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    private void readContacts() {
        StringBuffer sb = new StringBuffer(); //sb
        String name = null;  //联系人姓名
        String number = null;// 手机号
        Cursor cursor = null;
        cursor = getContentResolver().query( //简单模拟 实际放线程中操作(查询也是耗时)
                ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                null,
                null,
                null);
        if (cursor != null) {
            while (cursor.moveToNext()) {
                name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
                number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));

                sb.append("联系人:")
                        .append(name)
                        .append("手机号:")
                        .append(number)
                        .append("\n");
            }
        }
        ((TextView) findViewById(R.id.tv_text)).setText(sb.toString());
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    readContacts();
                } else {
                    Toast.makeText(this, "权限否定", Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }
}


把上面的代码简单的copy运行下就完成了简单的IPC,读取手机联系人成功,是不是感觉特别easy。哈哈接下来我们就分析下涉及的知识点:

1、动态权限申请

其实就是个安卓6.0新增的特性,需要时申请权限。

2、Cursor

安卓数据库查询返回的结果集(query方法返回值类型)常见方法:

(1)行列相关:

  • int getCount()返回Cursor 中的行数
  • int getColumnCount()返回所有列的总数

(2)遍历Cursou数据常用:

  • boolean moveToFirst()移动光标到第一行
  • boolean moveToLast()移动光标到最后一行
  • boolean moveToNext()移动光标到下一行(使用较多)
  • boolean moveToPrevious()移动光标到上一行
  • boolean moveToPosition(int position)——移动光标到指定行

返回值代表是否移动成功,有指定行就返回true,没有就返回false

(3)取数据常用

  • int getColumnIndex(String columnName) 根据列名返回列所在索引,列不存在返回-1
  • String getString(int columnIndex) 返回指定列的值

在这里插入图片描述
根据列名获得列所在的索引这点我们明白,但是根据列的索引获得数据我们就有点迷惑了,这里不是应该返回数据集合吗?一列数据的。其实Cursor就是个结果集。我们只需要不断的移动指针让他指向不同的行,这时指针指的行与我们指定的列综合起来就确定了某行某列的数据。

3、ContactsContract.CommonDataKinds.Phone相关介绍

上文中的Uri直接使用了这个系统字符串常量,嗯???其实这个是google规定好的系统电话的内容uri类似的还有Email,photo(联系人照片)这些系统都有封装,我们使用时查下就好啦。

自定义ContentProvider

系统app的ContentProvider提供的数据我们可以查询到,下面就总结下自己app的ContentProvider提供共享数据。

1、总体思路

(1)自定义类继承ContentProvider,实现要实现的方法。
(2)提供数据库,完成要暴露表的初始化工作。

2、栗子

使用SQLiteOpenHelper操作原生数据库暴露数据

(1)数据库

/**
 * Created by sunnyDay on 2019/6/28 12:00
 */
public class DbOpenHelper extends SQLiteOpenHelper {

    private static final String DB_NAME = "book_provider_db";
    public static final String BOOK_TABLE_NAME = "book";
    public static final String USER_TABLE_NAME = "user";
    private static final int DB_VERSION = 1;

    // book table
    private static final String CREATE_BOOK_TABLE = "create table if not exists " + BOOK_TABLE_NAME
            + "(id integer primary key,name text)";
    // user table
    private static final String CREATE_USER_TABLE = "create table if not exists " + USER_TABLE_NAME
            + "(id integer primary key,name text,sex integer)";


    public DbOpenHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
         db.execSQL(CREATE_USER_TABLE);
         db.execSQL(CREATE_BOOK_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
             // TODO nothing
            //  call this method when db version to upgrade
    }
}

(2)ContentProvider

/**
 * Created by sunnyDay on 2019/6/28 11:39
 */
public class MyProvider extends ContentProvider {

    private static final String authority = "com.example.contentresloverdemo.provider"; //authority strings from manifest file

    public static final Uri BOOK_CONTENT_URI = Uri.parse("content://" + authority + "book");
    public static final Uri USER_CONTENT_URI = Uri.parse("content://" + authority + "user");

    private static final int BOOK_URI_CODE = 0;
    private static final int USER_URI_CODE = 1;

    // uri 和code 建立联系 。建立联系后我们可以根据uri的到code值,根据code值 的到数据表名称
    private static UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); //uriMatcher 对象 参数为UriMatcher 内部提供的常量

    static {
        uriMatcher.addURI(authority, "book", BOOK_URI_CODE);// 代表 BOOK_CONTENT_URI
        uriMatcher.addURI(authority, "user", USER_URI_CODE); // 代表 USER_CONTENT_URI
    }

    private SQLiteDatabase mDB;

    /**
     * 根据 uri 获得表名
     */
    private String getTableName(Uri uri) {
        String tableName = null;
        switch (uriMatcher.match(uri)) { //int match(uri)
            case BOOK_URI_CODE:
                tableName = DbOpenHelper.BOOK_TABLE_NAME;
                break;
            case USER_URI_CODE:
                tableName = DbOpenHelper.USER_TABLE_NAME;
                break;
            default:
                break;
        }
        return tableName;
    }

    @Override
    public boolean onCreate() {
        mDB = new DbOpenHelper(getContext()).getReadableDatabase();

        mDB.execSQL("insert into book values (3,'android')");
        mDB.execSQL("insert into book values (4,'java')");

        mDB.execSQL("insert into user values (1,'tom',20)");
        mDB.execSQL("insert into user values (2,'kate',20)");
        return true;
    }


    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {

        String tableName = getTableName(uri);//获得用户想要访问的表名
        if (tableName == null) {
            throw new ArithmeticException("un support uri:" + uri);
        }
        return mDB.query(tableName, projection, selection, selectionArgs, null, null, sortOrder, null);
    }


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


    @Override
    public Uri insert(Uri uri, ContentValues values) {
        String tableName = getTableName(uri);//获得用户想要访问的表名
        if (tableName == null) {
            throw new ArithmeticException("un support uri:" + uri);
        }
        mDB.insert(tableName,null,values);
        getContext().getContentResolver().notifyChange(uri,null);
        return uri;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        String tableName = getTableName(uri);//获得用户想要访问的表名
        if (tableName == null) {
            throw new ArithmeticException("un support uri:" + uri);
        }
        int count = mDB.delete(tableName,selection,selectionArgs);
        if (count>0){
            getContext().getContentResolver().notifyChange(uri,null);
        }
        return count;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        String tableName = getTableName(uri);//获得用户想要访问的表名
        if (tableName == null) {
            throw new ArithmeticException("un support uri:" + uri);
        }
        int row = mDB.update(tableName,values,selection,selectionArgs);
        if (row>0){
            getContext().getContentResolver().notifyChange(uri,null);
        }
        return row;
    }
}

(3)MainActivity测试

   /**
     * 自定义 内容提供者逻辑处理
     */
    @RequiresApi(api = Build.VERSION_CODES.O)
    private void customProvider() {
        queryBook(); // 查询book表
        queryUser();// 查询user 表
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    private void queryUser() {
        Uri uri = Uri.parse("content://com.example.contentresloverdemo.provider/user");
        Cursor cursor = getContentResolver().query(uri,
                null,
                null,
                null);

        if (null != cursor){
            while(cursor.moveToNext()){
                String userName =   cursor.getString(cursor.getColumnIndex("name"));
                String userid = cursor.getString(cursor.getColumnIndex("id"));
                Log.i("aaa", "customProvider: "+userid+userName);
            }
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.O)
    private void queryBook() {
        Uri uri = Uri.parse("content://com.example.contentresloverdemo.provider/book");
        Cursor cursor = getContentResolver().query(uri,
                null,
                null,
                null);

        if (null != cursor){
            while(cursor.moveToNext()){
                String bookName =   cursor.getString(cursor.getColumnIndex("name"));
                String bookid = cursor.getString(cursor.getColumnIndex("id"));
                Log.i("aaa", "customProvider: "+bookid+bookName);
            }
        }
    }

在这里插入图片描述

3、总结
  • onCreate 做一些初始化工作,由系统回调,运行在主线程。contentResolver请求时创建。
  • curd 由外界回调,运行在Binder池中。
  • getType 由外界回调,运行在Binder池中,返回一个uri请求的对应的MIME,我们不关心可以直接null。
  • uriMatcher:吧uri 和我们定义的code 常量建立联系 。建立联系后我们可以根据uri的到code值,根据code值得到数据表名称。

End

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值