ContentProvider基本使用

ContentProvider基本用法:

什么叫ContentProvider?使用场景?

ContentProvider(内容提供器),用于在不同应用程序之间实现数据共享的功能,它提供了一套完整的机制,允许一个程序访问另一个程序中的数据,同时还能保证被访数据的安全性。

如何实现ContentProvider?如何访问?

1、创建自己的数据列表;
2、自定义ContentProvider实现相关的抽象方法;
3、在AndroidManifest中声明provider以及定义相关访问权限;
4 、通过ContentResolver根据URI进行增删改查。

------------------------ContentProvider是一个抽象类,如果开发自己的ContentProvider就需要继承这个类并复写其方法,需要实现的方法有:

public boolean onCreate()        //在创建ContentProvider时使用
public Cursor query()         //用于查询指定uri的数据返回一个Cursor
public Uri insert()           //用于向指定uri的ContentProvider中添加数据
public int delete()           //用于删除指定uri的数据
public int update()         //用户更新指定uri的数据
public String getType()        //用于返回指定的Uri中的数据MIME类型

数据访问的方法insert,delete和update可能被多个线程同时调用,此时必须是线程安全的(是否需要注意加入类似synchronized关键字实现线程安全?)

----------------------------URI(Universal Resource Identifier)统一资源定位符,如果您使用过安卓的隐式启动就会发现,在隐式启动的过程中我们也是通过uri来定位我们需要打开的Activity,并且可以在uri中传递参数。URI的格式如下:
在这里插入图片描述
schema: Android中固定为content://;
authority: 用于唯一标识一个ContentProvider,一般是com.example.app.provider这种形式;
path: ContentProvider中数据表的表名;
id: 数据表中数据的标识,可选字段

------------------------------自定义一个PeopleDataBaseProvider 的类用来共享该应用的数据:

package com.example.demodatabase;

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;

import java.util.concurrent.ThreadPoolExecutor;

public class PeopleDataBaseProvider extends ContentProvider {

    public static final String TAG = "PeopleDataBaseProvider";
    public static final String TABLE_PEOPLE_PATH_ALL_LINE = "People";
    public static final String TABLE_PEOPLE_PATH_SINGLE_LINE = "People/#";
    public static final int PEOPLE_DIR = 0;
    public static final int PEOPLE_ITEM = 1;
    public static final String AUTHORITY = "com.example.demodatabase.provider";
    private static UriMatcher uriMatcher;
    private MyDatabaseHelper mDbHelper;

    static {
        uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        /**
         * 通过uriMatcher.addURI()将authority(包名.provider)、表名/可选的数据表中的标识与匹配数字(MATCH_CODE)匹配,并add
         */
        uriMatcher.addURI(AUTHORITY, TABLE_PEOPLE_PATH_ALL_LINE, PEOPLE_DIR);
        uriMatcher.addURI(AUTHORITY, TABLE_PEOPLE_PATH_SINGLE_LINE, PEOPLE_ITEM);
    }

    @Override
    public boolean onCreate() {
        /**
         * 获取DatabaseHelper帮助类实例
         */
        mDbHelper = MyDatabaseHelper.getInstance(getContext());
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        final String[] xProjection = projection;
        final String xSelection = selection;
        final String[] xSelectionArgs = selectionArgs;
        final String xSortOrder = sortOrder;
        final SQLiteDatabase db = mDbHelper.getReadableDatabase();
        final Cursor xCursor = null;
        
        //匹配对应的uri并进行相应的操作
        switch (uriMatcher.match(uri)){
            case PEOPLE_DIR:
                ThreadPoolExecutors.getInstance().execute(new Runnable() {
                    @Override
                    public void run() {
                        Cursor cursor = xCursor;
                        try {
                            cursor = db.query(Constant.TABLE_NAME_PEOPLE, xProjection, xSelection, xSelectionArgs, null,null, xSortOrder);
                        } catch (Exception e) {
                            Log.e(TAG, Log.getStackTraceString(e));
                        }finally {
                            try {
                                cursor.close();
                            } catch (Exception e) {
                                Log.e(TAG, Log.getStackTraceString(e));
                            }
                        }
                    }
                }, ThreadPoolExecutors.ThreadType.FIXED_THREAD);
                break;
            case PEOPLE_ITEM:
                final String peopleId = uri.getPathSegments().get(1);
                ThreadPoolExecutors.getInstance().execute(new Runnable() {
                    @Override
                    public void run() {
                        Cursor cursor = xCursor;
                        try {
                            cursor = db.query(Constant.TABLE_NAME_PEOPLE, xProjection, "id = ?", new String[]{peopleId}, null, null, xSortOrder);
                        } catch (Exception e) {
                            Log.e(TAG, Log.getStackTraceString(e));
                        }finally {
                            try {
                                cursor.close();
                            } catch (Exception e) {
                                Log.e(TAG, Log.getStackTraceString(e));
                            }
                        }
                    }
                }, ThreadPoolExecutors.ThreadType.FIXED_THREAD);

        }
        return xCursor;
    }

    public PeopleDataBaseProvider() {
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // Implement this to handle requests to delete one or more rows.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public String getType(Uri uri) {
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO: Implement this to handle requests to insert a new row.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // TODO: Implement this to handle requests to update one or more rows.
        throw new UnsupportedOperationException("Not yet implemented");
    }
}
  1. 需要重写public boolean onCreate()、public Cursor query()、public Uri insert()、public int delete()、public int update()、public String getType()
  2. 在static静态代码块通过uriMatcher.addURI()将authority(包名.provider)、表名/可选的数据表中的标识与匹配数字(MATCH_CODE)匹配,并add
  3. 在onCreate()中获取DatabaseHelper帮助类实例
  4. 在相应重写的函数中实现增、删、改、查,在query、insert和delete方法中都是先调用uriMatcher.match(uri) 判断当前uri是不是匹配

其中以ContentProvider的public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)为例:

(注意这里的query是以Cursor为返回值,什么是Cursor?Cursor 是每行的集合。使用 moveToFirst() 定位第一行。你必须知道每一列的名称。你必须知道每一列的数据类型。Cursor 是一个随机的数据源。所有的数据都是通过下标取得)

uri:确定查询哪张表
projection:指定查询的列名
selection:查询哪些行(指定where约束条件)
selectionArgs:行关键字、范围(为where的占位符提供具体的值)
sortOrder:对结果的排序方式

--------------------在xml文件中声明:

<provider
            android:name=".PeopleDataBaseProvider"
            android:authorities="com.example.demodatabase.provider"
            android:enabled="true"
            android:exported="true"/>

----------------------在数据库发生变化的时候系统框架层会自动调用notifyChange方法

通过ContentResolver获取数据:

getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
uri:确定查询哪张表
projection:指定查询的列名
selection:查询哪些行(指定where约束条件)
selectionArgs:行关键字、范围(为where的占位符提供具体的值)
sortOrder:对结果的排序方式

private static final String URI = "content://com.example.demodatabase.provider/People";
final Uri uri = Uri.parse(URI);
Cursor cursor = null;
cursor = getContentResolver().query(uri, null, null, null, null, null);

ContentProvider的安全使用?

-------------通过在AndroidManifest.xml文件中指定android:exported属性为true,可以设置将ContentProvider公开给其它应用程序。对于API Level低于17的Android应用程序,Content Provider默认是public的,除非显式指定android:exported=“false”。例如:

<provider 
android:exported="true"
android:name="MyContentProvider" 
android:authorities="com.example.mycontentprovider" />

如果一个Content Provider为公开的,Content Provider中存储的数据就可以被其它应用程序访问。因此,它只能用于处理非敏感信息。

-------------通过在AndroidManifest. xml文件中指定android:exported属性为false,可以设置将Content Provider设置为私有的。从API Level17及以后,Content Provider默认是私有的,除非显式指定android:exported=“true”。例如:

<provider 
android:exported="false" 
android:name="MyContentProvider" 
android:authorities="com.example.mycontentprovider" />

-------------通过声明权限,并且定义ContentProvider其protectionLevel,限制部分应用访问
ContentReceiver访问是受限的,只有满足权限要求的才能通信
在AndroidManifest中声明provider以及定义相关访问权限
在注册ContentProvider的时候通过android:process属性设置provider运行在单独的进程里,模拟进程间通信。

<!-- student provider 访问权限声明 -->
<permission
    android:name="com.android.peter.provider.READ_PERMISSION"
    android:label="Student provider read permission"
    android:protectionLevel="normal"
    />
<permission
    android:name="com.android.peter.provider.WRITE_PERMISSION"
    android:label="Student provider read permission"
    android:protectionLevel="normal"
    />

<!-- 声明ContentProvider -->
<application
     ...
     <provider
        android:name=".StudentContentProvider"
        android:authorities="com.android.peter.provider"
        android:readPermission="com.android.peter.provider.READ_PERMISSION"
        android:writePermission="com.android.peter.provider.WRITE_PERMISSION"
        android:process=":provider"
        android:exported="true"/>
    ...
</application>

为了方便起见,权限声明时protectionLevel设置的是最低风险权限(normal),关于其他等级权限和说明如下:

权限等级说明:
在这里插入图片描述
使用带权限的ContentProvider
假如应用B要使用应用A中带权限的ContentProvider,需要在应用B的AndroidManifest.xml中加入权限的使用;

<uses-permission android:name="com.example.demodatabase.peopleDataBaseProvider.WRITE_PERMISSION"/>
<uses-permission android:name="com.example.demodatabase.peopleDataBaseProvider.READ_PERMISSION"/>

其中,标签中设置的android:name的值,就是应用A中对外声明的那个provider的权限值。

数据监听?

1、监听步骤
要监听指定URI上的数据库变化,在监听方需要两步:

(1) 生成一个类派生自ContentObserver(一般将ContentObserver定义成一个内部类或者匿名内部类

public class DataBaseObserver extends ContentObserver {
    public DataBaseObserver(Handler handler) {
        super(handler);
    }
 
    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);
        Log.d("harvic","received first database changed");
    }
}

注意这里会自动生成一个onChange()方法,当有改变到来时就会跑到onChange()里,所以我们在这里面打一个LOG,注意LOG内容:“received first database changed”,很明显这里我只用DataBaseObserver来监听"content://com.harvic.provider.PeopleContentProvider/frist "所对应的数据库进行监听,至于如何指定URI往下看

(2) 注册和反注册监听
-------注册:
在使用URI来操作第三方数据之前,先进行ContentObserver注册,这里我们在MainActivity的OnCreate()函数里注册:

private DataBaseObserver observer;
 
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    observer = new DataBaseObserver(new Handler());
    //注册指定URI 变动监听
    getContentResolver().registerContentObserver(CONTENT_URI_FIRST,true,observer);
}

这里先完整说完流程,等下再讲registerContentObserver()的参数

--------在OnDestory()时,反注册:

@Override
protected void onDestroy() {
    super.onDestroy();
    //取消监听
    getContentResolver().unregisterContentObserver(observer);
}

(3) 在ContentProvider中数据库变动通知

在ContentProvider中指定URI上有数据库数据变动时,框架层会及时回调notifyChange(uri, null),notifyChange会通知所有注册了该Uri的ContentObserver;

也可以:
在ContentProvider中指定URI上有数据库数据变动时,及时利用下面的函数来通知,在子类继承ContentProvider中的query()方法中调用:

getContext().getContentResolver().notifyChange(uri, null);

这个函数就是在第二篇中,我们提到的通知函数;
好了,到这数据库变动通知和监听的过程就讲完了,下面看看registerContentObserver()注册监听函数的用法:

public final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)

功能:为指定的Uri注册一个ContentObserver派生类实例,当给定的Uri发生改变时,回调该实例对象去处理。

参数:
-------uri:需要观察的Uri

-------notifyForDescendents: 为false 表示精确匹配,即只匹配该Uri; 为true 表示可以同时匹配其派生的Uri

举例如下:假设UriMatcher 里注册的Uri共有一下类型:
1、content://com.qin.cb/student (学生)
2、content://com.qin.cb/student/#
3、content://com.qin.cb/student/schoolchild(小学生,派生的Uri)

假设我们当前需要观察的Uri为content://com.qin.cb/student,如果发生数据变化的 Uri 为 content://com.qin.cb/student/schoolchild;那么当notifyForDescendents为 false,那么该ContentObserver会监听不到, 但是当notifyForDescendents 为ture,能捕捉该Uri的数据库变化。

----------observer: ContentObserver的派生类实例

增删改查注意点?多个进程通过ContentResolver对同一个Uri的ContentProvider进行增删改查的时候,是不是线程安全、原子的?如果不是,有什么方法可以避免?

  1. 首先不管Provider使用方是同一个进程的不同线程,还是不同的进程,Provider方实际上是同一个Provider对象实例

  2. 并发访问时,Provider方query()方法运行在不同的线程,实际上是运行在Provider方的进程的Binder线程池中;

  3. Provider方query()(增删改查等方法)方法不是原子操作。Android框架层并没有做什么以使数据操作原子化;

那么如何进行避免?
可以在子类继承ContentProvider重写增、删、改、查方法时,对增、删、改、查方法加锁;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值