内容提供器(Content-Provider)完整使用指南

[TOC]

1. 什么是内容提供器?

​ 内容提供器(Content Provider) 主要用于在不同的应用程序之间实现数据的共享功能,他提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还可以保证被访问的数据的安全性,目前使用内容提供器十Android实现跨程序共享数据的标准方式. 内容提供器可以选择只对哪一部分的数据进行共享,这样就可以保证我们数据的安全性.

2. 如何使用内容提供器获取其他应用的数据

2.1 权限声明

​ 运行时权限:6.0系统中引入的新功能,为了能更好的保护用户的隐私.

​ 通常的权限声明只需要在MainFest中添加要使用的权限就可以了,6.0及以后对于某些权限还需要在运行的时候在代码中检测是否有这个权限否则弹出对话框申请,拒绝的话是无法使用的.

​ **并不是所有的权限都需要用到运行时权限,只有关系到用户隐私的才需要.除了在ManiFest中声明以外,还要在代码中重新请求一 遍,如果没有使用运行时权限则会导致这个应用抛出异常SecurityException() **

public class MainActivity extends AppCompatActivity {
    private String TAG = "TAG";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        checkSelf();//检查权限
    }

    private void checkSelf() {
        //如果检查多个权限的话,可以将要检查的权限放入数组或者集合当中,遍历检查即可
        if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
            /**
             * 通过checkSelfPermission可以检查当前这个应用有没有获取到指定的那个权限,没有的话就调用请求权限的那个方法.
             * 根据返回值来判断状态  0表示权限已给予,-1表示没有获取到权限.
             */
            Log.d(TAG, "checkSelf: 权限允许");
        } else {
            /**
             * 通过ActivityCompat.requestPermissions动态的申请权限.
             * 第一个参数:当前的上下文
             * 第二个参数:需要申请的权限的字符串,保存在数组中.
             * 第三个参数:查询码,请求权限的最终结果会通过回调的方式->onRequestPermissionsResult();在这个方法中,告诉你最后请求成功了没有
             */
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_CALENDAR,Manifest.permission.CAMERA}, 1);

        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        /**
         * 通过请求码来判断具体是申请了什么权限,以及结果;
         * grantResult[]  保存着申请权限后,根据申请权限的先后顺序保存
         */
        switch (requestCode) {
            case 1:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Log.d(TAG, "onRequestPermissionsResult: 已授权");
                    for (int i = 0; i < grantResults.length; i++) {
                        Log.d(TAG, "onRequestPermissionsResult: 授权情况:"+grantResults[i]);
                    }
                } else {
                    Log.d(TAG, "onRequestPermissionsResult: 您拒绝了授权");
                }
                break;
            case 2:
                break;
            default:
                break;
        }
    }
}

复制代码

以上就是运行时权限的基本用法

2.2 内容提供器的基本用法及原理

​ 内容提供器的用法一般有两种,一是使用现有的内容提供器来读取和操作相应程序中的数据,二是自己创建一个内容提供器给我们的程序的数据提供外部访问接口 (就是通过别人(程序)提供的内容提供器来获取别人(程序)想要给我们使用的数据),系统自带的短信,电话簿,媒体库等程序都提供了类似的访问接口,我们就可以利用这个来进行再次开发和使用了.

​ 如果想要获取内容提供器中的数据,那么就需要借助Content-Resolver类,Context中的GetContentResolver方法可以获取到该类的实例. Content-Resolver提供了类似SqLiteDatabase类的方法,可以对共享的数据进行CRUD 操作,只是参数略微有点不同罢了.

​ 内容提供器是通过Uri来寻找数据源的 Uri由authority和path组成;authority用于区分不同的程序,通常使用包名,path是对同一程序不同的表做区分用的.

​ 比如某个包名为 com.example.test 有两张表table1,table2;

​ 则标准的Uri格式为(://前面的内容为协议):

​ content://com.example.test/table1

​ content://com.example.test/table2

​ 这样就可以明确的表达出我们想要访问哪个程序的哪个表里面的数据了. 所以内容提供器的CRUD都只接受Uri参数来确定位置.

查找:

Uri uri=Uri.parse("content://com.example.test/table2");

Cursor cursor=getContentResolver().quert(uri,projection,selection,selectionArgs,sortOrder);

quert()方法的参数 对应SQL部分 描述
uri from table_name 指定查询某个应用程序下的某一张表
projection select column1,column2 指定查询的列名
selection where column =value 指定where的约束条件
selectionArgs - 为where中的占位符提供具体的值
sortOrder order by column1,column2 指定查询结果的排序方式

从cursor中取出值也和数据库的操作一样,选择想要获取的数据类型,再选择列名即可得到

	...

	String data=cursor.getString(cursor.getColumnIndex("columnName"));

	...

复制代码

插入:

​ 和数据库的操作差不多,都是讲数据组装到ContentValues中

ContentValues values=new ContentValues();
 values.put("columnName1","value1");
 values.put("columnName2","value2");
  getContentResolver().insert(uri,values);
复制代码

更新:

ContentValues values=new ContentValues();
values.put("columnName1","value1");
getContentResolver().update(Uri.parse(""),contentValues,"where column1=?",new String[]{"1"});
复制代码

update()中的参数解释:第一个参数指定数据的位置;第二个参数指定要更新成什么值;第三个参数指定条件;第四个参数指定where条件语句中的缺省值;

删除:

getContentResolver().delete(Uri.parse(""),"column=?",new String[]{"1"});
复制代码

​ 参数的意思和上面几个差不多.就不过多的解释了.

2.3 使用内容提供器获取数据

​ 下面使用系统已经给我们提供好了的内容提供器来获取通讯簿中的姓名的电话号码吧,先确保的确保存了几个电话号码在通讯簿中.

1.首先需要获取READ_CANTACTS权限否则是不能读取数据的 并且会报错.

2.查询语句和数据库的用法相似

public class MainActivity extends AppCompatActivity {
    private String TAG = "TAG";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
        } else {
            
            getData();
        }
    }

    private void getData() {
        Cursor cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null);
        	//传入的Uri是ContactsContract.CommonData-Kinds.Phone类已经帮我们封装好了的一个常量
        //点开源代码可以看到 : 
        //public static final Uri CONTENT_URI = Uri.withAppendedPath(Data.CONTENT_URI,
                    "phones");
        if (cursor != null) {
            while (cursor.moveToNext()) {
                String name = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
        //ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME  也是一个常量,为保存该数据的类名.下同
                String phone = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                Log.d(TAG, "getData: " + name + "-" + phone);
            }
        }
    }

    @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) {
                    getData();
                } else {
                    Log.d(TAG, "onRequestPermissionsResult: 你点击了拒绝!");
                }
                break;
            default:
                break;
        }
    }
}

复制代码

运行程序就可以看到姓名和电话一起打印出来了.

主要的内容都在getData();方法中,具体的解释都写在注释中,只要知道原理 那么使用起来就变得十分的方便了.

3. 创建自己的内容提供器

基础知识:

通过新建一个类去继承ContentProvider的方式来创建一个内容提供器,但是必须重写里面的六个方法;

重写的方法: 返回值 用途
onCreate(); boolean 初始化内容提供器的时候调用.通常会在这里完成对数据库的创建和升级操作.返回true表明内容提供器初始化成功,false失败.
query(uri,projection, selection, selectionArgs, sortOrder); Cursor 从内容提供器中查找数据,使用uri参数来确定要查找哪一张表格,projection用于确定查找哪些列,selection用于确定查找的条件,selectionArgs用于填充selection中的条件的缺省值,sortOrder用于对查询结果排序 ; 查询结果放入Cursor中返回;
insert(uri, values); Uri 向内容提供器中添加一条数据,使用uri来确定要添加到的表,待添加的数据放在values参数中; 添加成功以后 返回一个用于表示这条新纪录的Cursor;
update(uri, values, selection, selectionArgs); int 更新内容提供器中已有的数据,使用Uri参数来确定更新哪一张表中的数据,新的数据保存在values中(只更新这里有写出来的值),selection和selectIonArgs参数用于约束更新哪些行;受影响的行数将作为返回值 返回
delete(uri, selection, selectionArgs) int 删除内容提供器中的数据,uri参数用来确定删除哪张表中的数据,selection和selectionArgs参数用于约束删除哪些行;被删除的行数将作为返回值返回;
getType(uri)(); String 根据传入的Uri来返回相对应的MIME类型字符串

Uri两种格式的说明:

​ content://com.example.test/table1/1;

表示调用方期望访问的是com.exampke.test应用里面表table1中id为1的数据

​ content://com.example.test/table1;

表示调用方期望访问的是com.exampke.test应用里面表table1的所有数据

不过通常都使用通配符的方式来匹配这两种格式:

:表示匹配任意长度的数字

#* :表示匹配任意长度的字符

上面的内容就可以写成: content://com.example.test/* 或 content://com.example.test/table1/#

getType();返回数据说明

​ content://com.example.test/table1/1;

​ content://com.example.test/table1;

还是以上面这两个为例子.

MIME字符串: 以vnd开头 + . + android.cursor.dir/(或android.cursor.item/) + vnd. + AUTHORITY + . + PATH

​ vnd.android.cursor.dir/vnd.com.example.test.table1;

​ vnd.android.cursor.item/vnd.com.example.test.table1

3.1 创建的基本步骤

创建一个继承自ContentProvider的类,并重写里面的六个方法

其中内容提供器必须在ManiFest.xml中进行注册,否则无法使用

<provider
            android:name=".MyProvider" 
            android:authorities="com.example.h.content_demo_provider"
            android:enabled="true"
            android:exported="true" />
复制代码

第一个参数:类名

第二个参数:通常使用包名来使用, 可以区分 不同的程序之间的内容提供器

第三个参数:启用

第四个参数:表示允许被其他的应用程序进行访问

新建一个内容提供器:


public class MyProvider extends ContentProvider {
    public static final int BOOK_DIT = 0;
    public static final int BOOK_ITEM = 1;
    public static final int CATEGORY_DIR = 2;
    public static final int CATEGORT_ITEM = 3;
    public static UriMatcher sUriMatcher;
    public static final String AUTHORITY = "com.example.h.content_demo_provider";
    private MyDatabaseHelper mMyDatabaseHelper;
    private SQLiteDatabase db;

    {
        sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        sUriMatcher.addURI(AUTHORITY, "book", BOOK_DIT);
        sUriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);
        sUriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);
        sUriMatcher.addURI(AUTHORITY, "category/#", CATEGORT_ITEM);
        //UriMatcher 可以匹配uri  通过调用他的match()方法 匹配到就会返回我们在上面添加uri时填入的第三个参数


    }

    @Override
    /**
     * 初始化内容提供器的时候调用,通常会在这里完成对数据库的创建和升级等操作
     * 返回true表示内容提供器初始化成功,返回false则表示失败.
     */
    public boolean onCreate() {
        //对当前内容提供器需要的资源进行初始化
        mMyDatabaseHelper = new MyDatabaseHelper(getContext(), "info.db", null, 1);
        db = mMyDatabaseHelper.getWritableDatabase();
        Log.d(TAG, "onCreate: 内容提供器初始化完成");
        return true;
        
    }

    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String
            selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
        //查询方法,通过解析uri来判断想要查询哪个程序的哪个表.通过UriMatchder进行匹配 如果有就返回前面addUri()中填入的code
        
        Cursor cursor = null;
        switch (sUriMatcher.match(uri)) {
            case BOOK_DIT:
                cursor = db.query("book", projection, selection, selectionArgs, null, null,
                        sortOrder);
                Log.d(TAG, "query: 查询整个表" + cursor);
                return cursor;
            case BOOK_ITEM:
                String itemId = uri.getPathSegments().get(1);
                cursor = db.query("book", projection, "id=?", new String[]{itemId}, null, null,
                        sortOrder);
                /**
                 * .getPathSegments()它会将内容URI权限之后的部分以 / 进行分割,并把分割后的结果放入到一个字符串列表中,
                 * 返回的列表[0]存放的就是路径,[1]存放的就是id
                 */
                return cursor;
            case CATEGORT_ITEM:
                String itemId2 = uri.getPathSegments().get(1);
                cursor = db.query("category", projection, "id=?", new String[]{itemId2}, null, null,
                        sortOrder);
                return cursor;
            case CATEGORY_DIR:
                cursor = db.query("category", projection, selection, selectionArgs, null, null,
                        sortOrder);
                return cursor;
            default:
                return cursor;
        }

    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        switch (sUriMatcher.match(uri)) {
            case BOOK_DIT:
                return "vnd.android.cursor.dir/vnd.com.example.h.content_demo_provider.book";

            case BOOK_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.h.content_demo_provider.book";
            case CATEGORT_ITEM:
                return "vnd.android.cursor.item/vnd.com.example.h.content_demo_provider.category";
            case CATEGORY_DIR:
                return "vnd.android.cursor.dir/vnd.com.example.h.content_demo_provider.category";
            default:
                return null;
        }
    }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
        Uri uriReturn = null;
        switch (sUriMatcher.match(uri)) {
            case BOOK_DIT:
            case BOOK_ITEM:
                long value = db.insert("book", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" + value);
                return uriReturn;
            //返回新插入行的行id,如果发生错误则返回-1
            case CATEGORT_ITEM:
            case CATEGORY_DIR:
                long value2 = db.insert("category", null, values);
                uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" + value2);
                return uriReturn;
            default:
                return uriReturn;

        }
    }

    @Override
    public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[]
            selectionArgs) {
        int deleteRows = 0;
        switch (sUriMatcher.match(uri)) {
            case BOOK_DIT:
                deleteRows = db.delete("book", selection, selectionArgs);
                return deleteRows;
            case BOOK_ITEM:
                String itemId1 = uri.getPathSegments().get(1);
                deleteRows = db.delete("book", "id=?", new String[]{itemId1});
                return deleteRows;
            case CATEGORT_ITEM:
                String itemId2 = uri.getPathSegments().get(1);
                deleteRows = db.delete("category", "id=?", new String[]{itemId2});
                return deleteRows;
            case CATEGORY_DIR:
                deleteRows = db.delete("category", selection, selectionArgs);
                return deleteRows;
            default:
                return deleteRows;
        }

    }

    @Override
    public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String
            selection, @Nullable String[] selectionArgs) {
        int updateRows = 0;
        switch (sUriMatcher.match(uri)) {
            case BOOK_DIT:
                updateRows = db.update("book", values, selection, selectionArgs);
                return updateRows;
            case BOOK_ITEM:
                String itemId1 = uri.getPathSegments().get(1);
                updateRows = db.update("book", values, "id=?", new String[]{itemId1});
                return updateRows;
            case CATEGORT_ITEM:
                String itemId2 = uri.getPathSegments().get(1);
                updateRows = db.update("category", values, "id=?", new String[]{itemId2});
                return updateRows;
            case CATEGORY_DIR:
                updateRows = db.update("category", values, selection, selectionArgs);
                return updateRows;
            default:
                return updateRows;
        }
    }
}

复制代码

新出现的方法:

uri.getPathSegments().get(1); //getPathSegments()返回一个集合,它将uri中authority后面的内容进行分割 即get(0)表名和id的值get(1) 这样取出来的id就可以当作条件对数据库进行条件查询了 .

3.2 跨程序获取数据

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button btn1, btn2, btn3, btn4;
    private Uri mUriBook = Uri.parse("content://com.example.h.content_demo_provider/book");
    private Uri mUriCategory = Uri.parse("content://com.example.h.content_demo_provider/category");

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        //randomInsert();
        iQuery();

    }

    private void initView() {
        btn1 = findViewById(R.id.button);
        btn2 = findViewById(R.id.button2);
        btn3 = findViewById(R.id.button4);
        btn4 = findViewById(R.id.button5);
        btn4.setOnClickListener(this);
        btn3.setOnClickListener(this);
        btn2.setOnClickListener(this);
        btn1.setOnClickListener(this);
    }

    private void iQuery() {
        Cursor cursor = getContentResolver().query(mUriBook, null, null, null, null);
        if (cursor != null) {
            while (cursor.moveToNext()) {
                String id = cursor.getString(cursor.getColumnIndex("id"));
                String name = cursor.getString(cursor.getColumnIndex("name"));
                String author = cursor.getString(cursor.getColumnIndex("author"));
                System.out.println(id + name + author);
            }
        } else {
            System.out.println("为空");
        }
    }

    private void randomInsert() {
        ContentValues contentValues1 = new ContentValues();
        ContentValues contentValues2 = new ContentValues();
        for (int i = 0; i < 5; i++) {
            contentValues1.put("name", "name" + i);
            contentValues1.put("author", "author" + i);
            System.out.println(getContentResolver().insert(mUriBook, contentValues1));
            contentValues2.put("type", "type" + i);
            contentValues2.put("code", "code" + i);
            System.out.println(getContentResolver().insert(mUriCategory, contentValues2)); ;
        }
        //这个方法只是通过内容提供器对另一个程序中的数据库写一点数据进去方便我们进行后续的CRUD.
    }

    private void iDelete(String value) {
        ContentValues contentValues = new ContentValues();
        int x = 0;
        Uri uri = Uri.parse("content://com.example.h.content_demo_provider/book/" + value);
        x = getContentResolver().delete(uri, null, null);
        Log.d("TAG", "iDelete: 删除后的返回值:" + x);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button:
                iQuery();
                break;
            case R.id.button2:
                break;
            case R.id.button4:
                iDelete("1");
                break;
            case R.id.button5:
                break;
            default:
                break;
        }
    }
}
复制代码

主要还是通过定义好Uri 然后传递给内容提供器,告诉它你想做什么,最后他会将执行结果通过返回值告诉你,更新功能和其他几个差不多就不展开解析了,

3.3 总结

经过上面的实践,给我的感觉就是, 某个程序A提供了一个内容提供器(这个内容提供器本质上就是对本程序内的数据库进行CRUD 不过可以对他进行限制一些权限 只给想给的数据), 然后程序B想要访问程序A中的某些数据, 程序B可以通过Uri对程序A允许的范围内进行CRUD

你可能感兴趣的:(内容提供器(Content-Provider)完整使用指南)