Android ContentProvider介绍

时间:2024-04-03 09:26:48

一、定义

      ContentProvider即内容提供者,Android四大组件之一。一般为存储和获取数据提供统一的接口,可以在不同的应用程序之间共享数据,就是说可以通过ContentProvider把应用中的数据共享给其他应用访问,其他应用可以通过ContentProvider对你应用中的数据进行增删改查。就像我们手机的通话记录,当要查看某个通话时间,就需要调用通话记录中的数据,此时就该用到ContentProvider数据共享。

二、应用场景

       1.在不同的应用程序之间共享数据,就可以考虑用ContentProvider来实现。

     2.访问Android系统里的许多数据也是使用ContentProvider来访问,这些是系统提供的,比如供开发者调用的(视频,音频,图片,通讯录、短信等)。

三、ContentProvider相关概念介绍

   1.ContentProvider类

           ContentProvider类可以理解为一个Android应用对外开放的接口,只要是符合它所定义的Uri格式的请求,均可以正常访问执行操作。如果要对外部应用提供访问数据,就可以自定义自己的ContentProvider继承ContentProvider类并覆盖相关的方法就可以实现对其它应用提供访问数据。后面章节将详细介绍自定义ContentProvider。

      2.URI

         即统一资源标示符(Uniform Resource Identifier),唯一标识 ContentProvider & 其中的数据。

           主要包含了两部分信息:

           1)需要操作的ContentProvider 。

           2对ContentProvider中的什么数据进行操作

          一个Uri由以下几部分组成:

Android ContentProvider介绍

           ContentProvider(内容提供者)的scheme已经由Android所规定, scheme为:content://。

           授权信息(或叫Authority)用于唯一标识这个ContentProvider,外部调用者可以根据这个标识来找到它。

           表名(path)可以用来表示我们要操作的数据,路径的构建应根据业务而定,如下:

           要操作User表中id为10的记录,可以构建这样的路径:/User/10

           要操作User表中id为10的记录的name字段, User/10/name

           要操作User表中的所有记录,可以构建这样的路径:/User

           要操作xxx表中的记录,可以构建这样的路径:/xxx

           当然要操作的数据不一定来自数据库,也可以是文件、xml或网络等其他存储方式,如下:

           要操作xml文件中User节点下的name节点,可以构建这样的路径:/User/name

           如果要把一个字符串转换成Uri,可以使用Uri类中的parse()方法,如下:

           例如:

           // 设置URI

      Uri uri = Uri.parse("content://com.carson.provider/User/1")

           // 上述URI指向的资源是:名为 `com.hbq.provider`的`ContentProvider` 中表名 为`User` 中的 `id`为1的数据

           // 特别注意:URI模式存在匹配通配符* #

           // *:匹配任意长度的任何有效字符的字符串

           // 以下的URI 表示 匹配provider的任何内容

           content://com.example.app.provider/*

           // #:匹配任意长度的数字字符的字符串

           // 以下的URI 表示 匹配provider中的table表的所有行

           content://com.example.app.provider/table/#

   3.MIME数据类型

           多功能Internet 邮件扩充服务(Multipurpose Internet Mail Extensions),它是一种多用途网际邮件扩充协议,在1992年最早应用于电子邮件系统,但后来也应用到浏览器。MIME类型就是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。作用指定某个扩展名的文件用某种应用程序来打开,有类型和子类型两部分组成,比如:application/pdf。

          ContentProvider有两种形式:

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

           vnd.android.cursor.dir/自定义

    4.ContentResolver

         统一管理不同 ContentProvider间的操作,通过 URI 即可操作不同的ContentProvider中的数据,外部进程通过 ContentResolver类与ContentProvider类进行交互。有些人可能会疑惑,为什么我们不直接访问ContentProvider,而是又在上面加了一层ContentResolver来进行对其的操作,这样岂不是更复杂了吗?其实不然,大家要知道一台手机中可不是只有一个ContentProvider内容,它可能安装了很多含有ContentProvider的应用,比如联系人应用,日历应用,字典应用等等。有如此多的ContentProvider,如果你开发一款应用要使用其中多个,而让你去了解每个ContentProvider的不同实现,岂不是要头都大了。所以Android为我们提供了ContentResolver来统一管理与不同ContentProvider间的操作。 所以ContentProvider很多对外可以访问的方法,在ContentResolver中均有同名的方法,是一一对应的,如图:

Android ContentProvider介绍

    5.ContentUris类

         辅助ContentProvider的工具类,方便操作操作 URI,提供的核心方法有两个:withAppendedId() &parseId()。用法如下:

         // withAppendedId()作用:向URI追加一个id
         Uri uri = Uri.parse("content://cn.scu.myprovider/user") 
         Uri resultUri = ContentUris.withAppendedId(uri, 10);  
         // 最终生成后的Uri为:content://cn.scu.myprovider/user/10
         // parseId()作用:从URL中获取ID
         Uri uri = Uri.parse("content://cn.scu.myprovider/user/10") 
         long personid = ContentUris.parseId(uri); 
         //获取的结果为:10

    6.UriMatcher类

         辅助ContentProvider的工具类,在ContentProvider 中注册URI,根据 URI 匹配 ContentProvider 中对应的数据表。具体使用如下:
    private static final UriMatcher sMatcher;
    static{
        sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        sMatcher.addURI(DbUtil.AUTOHORITY, DbUtil.TNAME, DbUtil.ITEM);
        sMatcher.addURI(DbUtil.AUTOHORITY, DbUtil.TNAME+"/#", DbUtil.ITEM_ID);
    }

@Override
    public String getType(Uri uri) {
        // TODO Auto-generated method stub
        switch (sMatcher.match(uri)) {
            case DbUtil.ITEM:
                return DbUtil.CONTENT_TYPE;
            case DbUtil.ITEM_ID:
                return DbUtil.CONTENT_ITEM_TYPE;
            default:
                throw new IllegalArgumentException("Unknown URI"+uri);
        }
    }

    7.ContentObserver类

        ContentObserver翻译成中文就是内容观察者,目的是观察(捕捉)特定Uri引起的数据库的变化,继而做一些相应的处理。ContentObserver一般和系统或第三方程序提供的Provider一起使用,这些Provider一般情况下会有一个Uri,然后ContentObserver就去监听这些Uri数据的变化,然后做出相应的处理。

        使用ContentObserver的步骤我这里总结如下:

    1 首先创建一个ContentObserver的子类,然后实现里面的onChange方法,监听的Uri中的数据发生变化的时候,会调用onchange方法。

    2 注册ContentObserver

    下面简单的列出例子代码:

    ContentObserver的子类

package com.hbq.contentprovidertest.contentprovider;

import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Handler;
import android.util.Log;

public class SMSObserver extends ContentObserver {

    private Context mContext;
    private Handler mHandler ;  //此Handler用来更新UI线程

    public SMSObserver(Context context,Handler handler) {
        super(handler);
        mContext = context;
        mHandler = handler ;
    }


    @Override
    public void onChange(boolean selfChange) {

        Cursor cursor = mContext.getContentResolver().query(
                Uri.parse("content://sms/inbox"), null, null, null, null);

        while (cursor.moveToNext()) {
            StringBuilder sb = new StringBuilder();

            sb.append("address=").append(
                    cursor.getString(cursor.getColumnIndex("address")));

            sb.append(";subject=").append(
                    cursor.getString(cursor.getColumnIndex("subject")));

            sb.append(";body=").append(
                    cursor.getString(cursor.getColumnIndex("body")));

            sb.append(";time=").append(
                    cursor.getLong(cursor.getColumnIndex("date")));
            Log.e("---Receivered SMS::", "" + sb.toString());
        }
    }

}

    下面给出MainActivity.java类使用ContentObserver

package com.hbq.contentprovidertest.contentprovider;

import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;

import com.hbq.contentprovidertest.R;

public class MainActivity extends Activity {

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

        //注册观察者Observser
        this.getContentResolver().registerContentObserver(Uri.parse("content://sms"),
                true,new SMSObserver(this,new Handler()));

    }

}

    观察系统的短信息数据发生了变化。当监听到短信数据发生变化时,查询所有已发送的短信并在日志打印出来。

四、ContentProvider的自定义以及使用

      1.大体的实现步骤:

      (1).创建一个数据源,例如继承SQLiteOpenHelper创建一个SQLite数据库;
    (2).创建一个继承自ContentProvider的类,并重写insert、delete、query、update、getType、onCreate方法,在这些方法中实现对数据源的操作;
    (3).在AndroidManifest.xml文件中添加<provider>标签,两个必写的属性是android:name和android:authorities;
    (4).在本应用或者其它应用的Activity、Service等组件中使用ContentResolver通过对应的URI来操作该自定义ContentProvider。

     2.示例代码如下:

      (1)工具类DbUtil:

public class DbUtil {
    public static final String DBNAME = "hbqdb";
    public static final String TNAME = "user";
    public static final int VERSION = 1;
    public static String TID = "tid";
    public static final String EMAIL = "email";
    public static final String USERNAME = "username";
    public static final String DATE = "date";
    public static final String SEX = "sex";
    public static final String AUTOHORITY = "com.hbq.contentprovidertest";
    public static final int ITEM = 1;
    public static final int ITEM_ID = 2;
    public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.hbq.login";
    public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.hbq.login";
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTOHORITY + "/user");
}

      (2)数据源DBHelper类:

public class DBHelper extends SQLiteOpenHelper {

    public DBHelper(Context context) {
        super(context, DbUtil.DBNAME, null, DbUtil.VERSION);
        // TODO Auto-generated constructor stub
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        // TODO Auto-generated method stub
        db.execSQL("create table "+ DbUtil.TNAME+"(" +
                DbUtil.TID+" integer primary key autoincrement not null,"+
                DbUtil.EMAIL+" text not null," +
                DbUtil.USERNAME+" text not null," +
                DbUtil.DATE+" interger not null,"+
                DbUtil.SEX+" text not null);");
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // TODO Auto-generated method stub
    }
    public void add(String email,String username,String date,String sex){
        SQLiteDatabase db = getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put(DbUtil.EMAIL, email);
        values.put(DbUtil.USERNAME, username);
        values.put(DbUtil.DATE, date);
        values.put(DbUtil.SEX, sex);
        db.insert(DbUtil.TNAME,"",values);
    }
}

        (3)自定义的ContentProvider类MyProvider:

public class MyProvider extends ContentProvider {
    private DBHelper dBlite;
    private SQLiteDatabase db;

    private static final UriMatcher sMatcher;
    static{
        sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        sMatcher.addURI(DbUtil.AUTOHORITY, DbUtil.TNAME, DbUtil.ITEM);
        sMatcher.addURI(DbUtil.AUTOHORITY, DbUtil.TNAME+"/#", DbUtil.ITEM_ID);

    }
    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        // TODO Auto-generated method stub
        db = dBlite.getWritableDatabase();
        int count = 0;
        switch (sMatcher.match(uri)) {
            case DbUtil.ITEM:
                count = db.delete(DbUtil.TNAME,selection, selectionArgs);
                break;
            case DbUtil.ITEM_ID:
                String id = uri.getPathSegments().get(1);
                count = db.delete(DbUtil.TID, DbUtil.TID+"="+id+(!TextUtils.isEmpty(DbUtil.TID="?")?"AND("+selection+')':""), selectionArgs);
                break;
            default:
                throw new IllegalArgumentException("Unknown URI"+uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return count;
    }

    @Override
    public String getType(Uri uri) {
        // TODO Auto-generated method stub
        switch (sMatcher.match(uri)) {
            case DbUtil.ITEM:
                return DbUtil.CONTENT_TYPE;
            case DbUtil.ITEM_ID:
                return DbUtil.CONTENT_ITEM_TYPE;
            default:
                throw new IllegalArgumentException("Unknown URI"+uri);
        }
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        // TODO Auto-generated method stub

        db = dBlite.getWritableDatabase();
        long rowId;
        if(sMatcher.match(uri)!= DbUtil.ITEM){
            throw new IllegalArgumentException("Unknown URI"+uri);
        }
        rowId = db.insert(DbUtil.TNAME, DbUtil.TID,values);
        if(rowId>0){
            Uri noteUri= ContentUris.withAppendedId(DbUtil.CONTENT_URI, rowId);
            getContext().getContentResolver().notifyChange(noteUri, null);
            return noteUri;
        }
        throw new IllegalArgumentException("Unknown URI"+uri);
    }

    @Override
    public boolean onCreate() {
        // TODO Auto-generated method stub
        this.dBlite = new DBHelper(this.getContext());
//                db = dBlite.getWritableDatabase();
//                return (db == null)?false:true;
        return true;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
                        String[] selectionArgs, String sortOrder) {
        // TODO Auto-generated method stub
        db = dBlite.getWritableDatabase();
        Cursor c;
        Log.d("-------", String.valueOf(sMatcher.match(uri)));
        switch (sMatcher.match(uri)) {
            case DbUtil.ITEM:
                c = db.query(DbUtil.TNAME, projection, selection, selectionArgs, null, null, null);

                break;
            case DbUtil.ITEM_ID:
                String id = uri.getPathSegments().get(1);
                c = db.query(DbUtil.TNAME, projection, DbUtil.TID+"="+id+(!TextUtils.isEmpty(selection)?"AND("+selection+')':""),selectionArgs, null, null, sortOrder);
                break;
            default:
                Log.d("!!!!!!", "Unknown URI"+uri);
                throw new IllegalArgumentException("Unknown URI"+uri);
        }
        c.setNotificationUri(getContext().getContentResolver(), uri);
        return c;
    }
    @Override
    public int update(Uri uri, ContentValues values, String selection,
                      String[] selectionArgs) {
        // TODO Auto-generated method stub
        return 0;
    }
}

      (4)在AndroidManifest.xml文件里的配置:

<provider
    android:name=".contentprovider.MyProvider"
    android:authorities="com.hbq.contentprovidertest"
    android:enabled="true"
    android:exported="true"></provider>

         (5)在Activity中的调用

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        displayRecords();
        updateRecord(1, "zhangsan");
    }

    private void displayRecords() {
        //该数组中包含了所有要返回的字段
        String columns[] = new String[]{DbUtil.USERNAME, DbUtil.EMAIL};
        Uri mContacts = DbUtil.CONTENT_URI;
        Cursor cur = managedQuery(
                mContacts,
                columns,// 要返回的数据字段
                null, // WHERE子句
                null,// WHERE 子句的参数
                null// Order-by子句
        );

        if (cur.moveToFirst()) {
            String name = null;
            String phoneNo = null;
            do {
                // 获取字段的值
                name = cur.getString(cur.getColumnIndex(DbUtil.USERNAME));
                phoneNo = cur.getString(cur.getColumnIndex(DbUtil.EMAIL));
                Toast.makeText(this, name + "" + phoneNo, Toast.LENGTH_LONG).show();
            } while (cur.moveToNext());
        }
    }
    private void updateRecord(int recNo, String name) {
        Uri uri = ContentUris.withAppendedId(DbUtil.CONTENT_URI, recNo);
        ContentValues values = new ContentValues();
        values.put(DbUtil.USERNAME, name);

        getContentResolver().update(uri, values, null, null);
    }
}

 以上就是关于ContentProvider的一些相关知识,如果有不合理之处请多多指正!