Android四大组件之一 ContentProvider

一、什么是ContentProvider
ContentProvider是Android的四大组件之一,主要用于给不同应用程序提供接口,实现数据共享,并且可以保证数据的安全性。在手机的联系人、短信等应用都会创建ContentProvider提供接口将应用内数据提供给其他应用使用。ContentProvider的底层实现还是使用Binder,主要是以表格的形式操作存储数据,并且可以包含多张表格;也支持文件类型的数据储存操作,如图片、视频等。

二、ContentProvider的基础知识
BroadCast需要使用BroadCast Receive接收广播,而Content Provider数据存储需要借助ContentResolver类提供的一系列方法进行增删改查操作。ContentResolver可以通过Context的getContentResolver()方法获取实例。ContentResolver实例访问对应的数据,使用CRUD方法对数据进行增删改查。
1.内容 URI
内容 URI 给内容提供器中的数据建立了唯一标识符,内容URI的定义和Intentfilter中的data标签下的Uri基本是一致的,它由三部分组成,协议声明(content)、权限(authority)和路径(path)。权限(authority)是对不同应用的区分主要是“应用的包名”,路径(path)是对同一个应用“不同表格”的区分,总结起来就是访问“哪个应用/哪张表”,例如:

content://com.example.app.provider/table1
content://com.example.app.provider/table2/1

内容 URI有两种格式,以路径结尾的格式代表访问“整个表”;以数字id结束的表示访问表中的“某一列”,可以使用通配符的方式区分。例如:

//匹配所有的表
content://com.example.app.provider/*
//匹配table2表中的某一列
content://com.example.app.provider/table2/#

将字符串解析为Uri对象

Uri uri = Uri.parse("content://com.example.app.provider/table1);

2.getType()方法
ContentProvider必须实现的一个方法,是为了获取Uri对象对应的MIME类型。一个内容Uri对应的MIME类型主要分为3个部分组成与它的格式有关系。格式为:

//路径对应dir,匹配表,标准:"vnd."+"android.cursor.dir/"+"vnd."+"authority.path"
vnd.android.cursor.dir/vnd.com.example.app.provider/table1
//id对应item,匹配列,标准:"vnd."+"android.cursor.item/"+"vnd."+"authority.path",注意路径后面未带id
vnd.android.cursor.item/vnd.com.example.app.provider/table1

3.创建SQLite数据库
创建数据库是供ContentProvider进行增删改查的操作的,创建SQLite需要注意建表语句的书写注意空格;必须要有一个构造方法;必须要实现onCreat、update方法。先创建数据库,在调用onCreat方法时创建表,后通过数据库的实例对表进行操作。

package com.mrdouya.mycontenprovidertest;

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


public class MyDatabaseHelper extends SQLiteOpenHelper {

    private Context mContext;
    private final String TAG = "MrDouYa-DatabaseHelper";

    //建表语句,注意空格问题
    public static final String CREAT_TABLE = "create table Category ("
            + " id integer primary key autoincrement"
            + ", category_name text"
            + ", category_code integer)";


    public static final String CREAT_CATTEGORY = "create table Book ("
            + " id integer primary key autoincrement"
            + ", author text"
            + ", prices real"
            + ", pages integer"
            + ", name text)";


    /**
     * 必须实现父类的构造方法
     * @param context
     * @param name
     * @param factory
     * @param version
     */
    public MyDatabaseHelper(Context context,String name,SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        mContext = context;
    }

    /**
     * 必须重写父类的onCreat方法
     * @param sqLiteDatabase
     */
    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        //创建两张表
        sqLiteDatabase.execSQL(CREAT_TABLE);
        sqLiteDatabase.execSQL(CREAT_CATTEGORY);
        Log.d(TAG,"table book have create!");
    }

    /**
     * 必须重写父类的onUpgrade方法
     * @param sqLiteDatabase
     * @param i
     * @param i1
     */
    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
        //比较强盗,查询如果存在表就删除
        sqLiteDatabase.execSQL("drop table if exists Book");
        sqLiteDatabase.execSQL("drop table if exists Category");
        //onCreate创建表
        onCreate(sqLiteDatabase);
        Log.d(TAG,"onUpgrade Book have update!");
    }
}

4.六个必须实现的父类方法

package com.mrdouya.mycontenprovidertest;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.util.Log;


public class MyProvider extends ContentProvider {

    private final String TAG = "MrDouYa";

    /**
     * 初始化内容提供器使用
     * 完成对数据库的创建和升级等操作
     */
    @Override
    public boolean onCreate() {
        Log.d(TAG,"MyProvider--->onCreate");
        return false;
    }

    /**
     * 从内容提供器中查询数据,返回Cursor对象,uri为查询哪张表
     * @param uri
     * @param strings
     * @param s
     * @param strings1
     * @param s1
     * @return
     */
    @Override
    public Cursor query(Uri uri,String[] strings,String s,String[] strings1,String s1) {
        return null;
    }

    /**
     * 更新已有的数据,更据参数约束,返回值为受影响的行数
     * @param uri
     * @param contentValues
     * @param s
     * @param strings
     * @return
     */
    @Override
    public int update(Uri uri,ContentValues contentValues,String s,String[] strings) {
        return 0;
    }

    /**
     * 向内容提供器添加一条数据,返回一个用于表示这条新记录的URI
     * @param uri
     * @param contentValues
     * @return
     */
    @Override
    public Uri insert(Uri uri,ContentValues contentValues) {
        return null;
    }

    /**
     * 删除一条数据,返回值为被删除的行数
     * @param uri
     * @param s
     * @param strings
     * @return
     */
    @Override
    public int delete(Uri uri,String s,String[] strings) {
        return 0;
    }

    /**
     * 更具传入的Uri的返回对应的MIME类型
     * @param uri
     * @return
     */
    @Override
    public String getType(Uri uri) {
        return null;
    }
}

三、实际使用ContentProvider
1.访问其他程序的ContentProvider
非常简单可以分为3个步骤:

  • 1).明确Uri(第三方程序提供的数据共享的Uri)
    2).根据需要通过getContentRelsove()实例调用对应的方法进行数据操作
    3).遍历提取cursor对象中封装的数据

注意:访问权限声明如获取联系人权限、ContentProvider重要方法的“返回值”

package com.mrdouya.mycontenprovidertest;

import android.app.Activity;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

public class MyProviderTest extends Activity implements View.OnClickListener {

    public static final String TAG = "MrDouYa-MyProviderTest";
    private String newId;

    private Button queryButton;
    private Button updateButton;
    private Button deleteButton;
    private Button insertButton;
    private Uri uri;

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

        queryButton = findViewById(R.id.bt_provider_query_book);
        updateButton = findViewById(R.id.bt_provider_update_book);
        deleteButton = findViewById(R.id.bt_provider_delete_book);
        insertButton = findViewById(R.id.bt_provider_add_book);

        queryButton.setOnClickListener(this);
        updateButton.setOnClickListener(this);
        deleteButton.setOnClickListener(this);
        insertButton.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {

        switch (v.getId()){
            case R.id.bt_provider_add_book:
                //Uri唯一标志,访问com.mrdouya.mycontenprovidertest应用下的Book表
                uri = Uri.parse("content://com.mrdouya.mycontenprovidertest/Book");
                //一般使用ContentValues将数据封装
                ContentValues contentValues = new ContentValues();
                contentValues.put("name","A Clash of Kings");
                contentValues.put("author","Geoge Martin");
                contentValues.put("pages",1040);
                contentValues.put("prices",22.85);
                //获取getContentResolver()实例调用insert方法将uri和contentValues传入,返回一个“被改变的Uri地址”
                Uri newUri = getContentResolver().insert(uri,contentValues);
                //获取newUri地址中被改变的id,用于后续删除、更新Book表中现在插入的这条数据
                newId = newUri.getPathSegments().get(1);
                Log.d(TAG,"insert book over!,newId = "+newId);
                break;

            case R.id.bt_provider_query_book:
                //Uri唯一标志
                uri = Uri.parse("content://com.mrdouya.mycontenprovidertest/Book");
                //查询,返回cursor对象
                Cursor cursor = getContentResolver().query(uri,null,null,null,null);
                if(cursor != null){
                    while (cursor.moveToNext()){
                        String name = cursor.getString(cursor.getColumnIndex("name"));
                        String author = cursor.getString(cursor.getColumnIndex("author"));
                        int pages = cursor.getInt(cursor.getColumnIndex("pages"));
                        double prices = cursor.getDouble(cursor.getColumnIndex("prices"));
                        Log.d(TAG,"query book name:"+name+" ,author : "+author+" ,pages :"+pages+" ,prices :"+prices);
                    }
                    cursor.close();
                }
                Log.d(TAG,"query book over!");
                break;

            case R.id.bt_provider_update_book:
                //Uri唯一标志+newId上面插入的一条记录的id
                uri = Uri.parse("content://com.mrdouya.mycontenprovidertest/Book/"+newId);
                //添加数据
                ContentValues values = new ContentValues();
                values.put("name","A Storm of Swords");
                values.put("pages",1216);
                values.put("prices",24.05);
                //更新
                getContentResolver().update(uri,values,null,null);
                Log.d(TAG,"update book over!");
                break;

            case R.id.bt_provider_delete_book:
                //删除
                uri= Uri.parse("content://com.mrdouya.mycontenprovidertest/Book/"+newId);
                getContentResolver().delete(uri,null,null);
                Log.d(TAG,"delete book over!");
                break;
        }

    }
}

执行插入、查询、更新、删除、更新的Log如下:

2019-09-04 13:58:56.990 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: insert book over!,newId = 1
2019-09-04 13:59:04.050 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: query book name:A Clash of Kings ,author : Geoge Martin ,pages :1040 ,prices :22.85
2019-09-04 13:59:04.055 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: query book over!
2019-09-04 13:59:15.941 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: update book over!
2019-09-04 13:59:19.158 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: query book name:A Storm of Swords ,author : Geoge Martin ,pages :1216 ,prices :24.05
2019-09-04 13:59:19.162 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: query book over!
2019-09-04 13:59:31.015 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: delete book over!
2019-09-04 13:59:34.697 24148-24148/com.mrdouya.mycontenprovidertest:providertest D/MrDouYa-MyProviderTest: query book over!

2.创建ContentProvider提供接口
1)需要创建数据库,提供可操作的表或数据,如第二节第4点
2)创建ContentProvider,提供可访问的Uri,如下:
在Androidmanifest.xml中声明,ContentProvider是四大组件之一,需要注意android:authorities属性表明该内容提供器的权限

<provider
    android:name="com.mrdouya.mycontenprovidertest.MyProvider"
    android:authorities="com.mrdouya.mycontenprovidertest"
    />

自定义的ContentProvider:

package com.mrdouya.mycontenprovidertest;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;


public class MyProvider extends ContentProvider {

    private final String TAG = "MrDouYa-MyProvider";

    //定义int用于区分是访问哪张表的路径或者item
    public static final int BOOK_DIR = 0;
    public static final int BOOK_ITEM = 1;
    public static final int CATEGORY_DIR = 2;
    public static final int CATEGORY_ITEM = 3;
    //与android:authorities一致,Uri唯一标志中的author
    public static final String AUTHOR = "com.mrdouya.mycontenprovidertest";
    //数据库实例
    private MyDatabaseHelper myDatabaseHelper;
    //UriMatcher可以实现Uri的匹配,先定义
    private static UriMatcher uriMatcher;

    /**
    *初始化uriMatcher,并添加能访问这个ContentProvider的Uri唯一标志
    *addURI()传入的是AUTHOR、path、自定义代码
    */
    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        uriMatcher.addURI(AUTHOR,"Book",BOOK_DIR);//表
        uriMatcher.addURI(AUTHOR,"Book/#",BOOK_ITEM);//item
        uriMatcher.addURI(AUTHOR,"Category",CATEGORY_DIR);
        uriMatcher.addURI(AUTHOR,"Category/#",CATEGORY_ITEM);
    }

    /**
     * 初始化内容提供器使用
     * 完成对数据库的创建和升级等操作
     */
    @Override
    public boolean onCreate() {
        myDatabaseHelper = new MyDatabaseHelper(getContext(),"BookStore.db",null,3);
        Log.d(TAG,"onCreate");
        return false;
    }

    /**
     * 从内容提供器中查询数据,返回Cursor对象,uri为查询哪张表
     * @param uri
     * @param strings
     * @param s
     * @param strings1
     * @param s1
     * @return
     */
    @Override
    public Cursor query(Uri uri,String[] strings,String s,String[] strings1,String s1) {
        Log.d(TAG,"query");
        //getReadableDatabase()方法获取“读取SQLite”的实例,实际通过SQLiteDatabase的query方法去查询数据
        SQLiteDatabase db = myDatabaseHelper.getReadableDatabase();
        Cursor cursor = null;
        //match()方法返回值用于匹配当前ContentProvider的Uri唯一标识
        switch (uriMatcher.match(uri)){
            case BOOK_DIR :
                cursor = db.query("Book",strings,s,strings1,null,null,s1);
                break;

            case BOOK_ITEM :
                //如果访问item,就获取对应item的id
                //getPathSegments()会获取Uri中author之后的部分,并以“/”分割字符放入字符串列表,列表第一列“0”为路径,第二列“1”为id
                String bookId = uri.getPathSegments().get(1);
                cursor = db.query("Book",strings,"id = ?",new String[]{ bookId },null,null,s1);
                break;

            case CATEGORY_DIR :
                cursor = db.query("Category",strings,s,strings1,null,null,s1);
                break;

            case CATEGORY_ITEM :
                String categoryId = uri.getPathSegments().get(1);
                cursor = db.query("Category",strings,"id = ?",new String[]{ categoryId },null,null,s1);
                break;

                default:
                    break;
        }
        return cursor;
    }

    /**
     * 更新已有的数据,更据参数约束,返回值为受影响的行数
     * @param uri
     * @param contentValues
     * @param s
     * @param strings
     * @return
     */
    @Override
    public int update(Uri uri,ContentValues contentValues,String s,String[] strings) {
        Log.d(TAG,"update");
        SQLiteDatabase db = myDatabaseHelper.getWritableDatabase();
        int updatedRows = 0;
        switch (uriMatcher.match(uri)){
            case BOOK_DIR :
                updatedRows = db.update("Book",contentValues,s,strings);
                break;

            case BOOK_ITEM :
                String bookId = uri.getPathSegments().get(1);
                updatedRows = db.update("Book",contentValues,"id = ?",new String[]{ bookId });
                break;

            case CATEGORY_DIR :
                updatedRows = db.update("Category",contentValues,s,strings);
                break;

            case CATEGORY_ITEM :
                String categoryId = uri.getPathSegments().get(1);
                updatedRows = db.update("Category",contentValues,"id = ?",new String[]{ categoryId });
                break;

            default:
                break;
        }
        return updatedRows;
    }

    /**
     * 向内容提供器添加一条数据,返回一个用于表示这条新记录的URI
     * @param uri
     * @param contentValues
     * @return
     */
    @Override
    public Uri insert(Uri uri,ContentValues contentValues) {
        Log.d(TAG,"insert");
        SQLiteDatabase db = myDatabaseHelper.getWritableDatabase();
        Uri uriReturn = null;
        switch (uriMatcher.match(uri)){
            case BOOK_DIR:
            case BOOK_ITEM:
                //获取SQLite增加数据后返回item的id
                long newBookId = db.insert("Book",null,contentValues);
                //将id添加到Uri唯一标志上,返回一个新Uri唯一标志
                uriReturn = Uri.parse("content://"+AUTHOR+"/Book/"+newBookId);
                break;
            case CATEGORY_DIR:
            case CATEGORY_ITEM:
                long newCategoryId = db.insert("Category",null,contentValues);
                uriReturn = Uri.parse("content://"+AUTHOR+"/Category/"+newCategoryId);
                break;
        }
        return uriReturn;
    }

    /**
     * 删除一条数据,返回值为被删除的行数
     * @param uri
     * @param s
     * @param strings
     * @return
     */
    @Override
    public int delete(Uri uri,String s,String[] strings) {
        Log.d(TAG,"delete");
        SQLiteDatabase db = myDatabaseHelper.getWritableDatabase();
        int deleteRows = 0;
        switch (uriMatcher.match(uri)){
            case BOOK_DIR :
                deleteRows = db.delete("Book",s,strings);
                break;

            case BOOK_ITEM :
                String bookId = uri.getPathSegments().get(1);
                deleteRows = db.delete("Book","id = ?",new String[]{ bookId });
                break;

            case CATEGORY_DIR :
                deleteRows = db.delete("Category",s,strings);
                break;

            case CATEGORY_ITEM :
                String categoryId = uri.getPathSegments().get(1);
                deleteRows = db.delete("Category","id = ?",new String[]{ categoryId });
                break;

            default:
                break;
        }
        return deleteRows;
    }

    /**
     * 更具传入的Uri的返回对应的MIME类型
     * @param uri
     * @return
     */
    @Override
    public String getType(Uri uri) {
        switch (uriMatcher.match(uri)){
            case BOOK_DIR :
                return "vnd.android.cursor.dir/vnd.com.mrdouya.mycontenprovidertest.Book";

            case BOOK_ITEM :
                return "vnd.android.cursor.item/vnd.com.mrdouya.mycontenprovidertest.Book";

            case CATEGORY_DIR :
                return "vnd.android.cursor.dir/vnd.com.mrdouya.mycontenprovidertest.Category";

            case CATEGORY_ITEM :
                return "vnd.android.cursor.item/vnd.com.mrdouya.mycontenprovidertest.Category";
        }
        return null;
    }
}

3)访问验证
其实在本节第一小点访问的就是自定义的contentProvider,我将测试类运行在单独的线程中,去访问自定义的contentProvider这与两个应用资源共享效果是一致的,声明如下:

<activity android:name=".MyProviderTest"
    android:process=":providertest">
</activity>

注意:四大组件均支持process属性,但不能轻易使用的时候,主要有一下几点权衡
优点:

  • 1)分散应用内存,Android应用的内存大小是有规定的
  • 2)应用的模块化,大型应用
  • 3)守护线程
  • 4)子线程、主线程的异步

缺点:

  • 1)静态属性、单列模式失效
  • 2)SheredPerference安全性降低
  • 3)锁失效
  • 4)appliction多次创建

应用私有进程:

<activity android:name=".MyProviderTest"
    android:process=":providertest">
</activity>

应用公共进程:

<!--process的属性字段必须包含符号“.”-->
<activity android:name=".MyProviderTest"
    android:process="com.providertest">
</activity>

参考:
书籍:《第一行代码》、《Android开发与艺术探索》
Process属性:https://blog.csdn.net/lixpjita39/article/details/77435156

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值